chore: establish V1.1 recovery baseline

This commit is contained in:
2026-09-09 22:05:00 +08:00
commit b46b48eb33
947 changed files with 155471 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
#!/bin/bash
# ============================================
# 丹阳公益婚恋服务平台 — PostgreSQL 备份脚本
# ============================================
# 用法:
# ./scripts/backup.sh # 备份全部数据库
# ./scripts/backup.sh danyang_match # 备份指定库
# ./scripts/backup.sh --daily # 仅保留最近7天的备份
# ============================================
set -euo pipefail
DB_NAME="${1:-danyang_match}"
BACKUP_DIR="${BACKUP_DIR:-/app/backups}"
RETENTION_DAYS="${RETENTION_DAYS:-30}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.sql.gz"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 开始备份: ${DB_NAME}"
# 创建备份目录
mkdir -p "${BACKUP_DIR}"
# 执行备份
pg_dump \
-h "${DB_HOST:-postgres}" \
-U "${DB_USER:-danyang}" \
-d "${DB_NAME}" \
--no-owner \
--no-privileges \
--clean \
--if-exists \
--inserts \
| gzip > "${BACKUP_FILE}"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 备份完成: ${BACKUP_FILE}"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 文件大小: $(du -h "${BACKUP_FILE}" | cut -f1)"
# 清理过期备份
find "${BACKUP_DIR}" -name "${DB_NAME}_*.sql.gz" -mtime +${RETENTION_DAYS} -delete
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 已清理 ${RETENTION_DAYS} 天前的备份"
# 保留最近 N 份(防止误删)
BACKUP_COUNT=$(ls -1 "${BACKUP_DIR}"/${DB_NAME}_*.sql.gz 2>/dev/null | wc -l)
if [ "${BACKUP_COUNT}" -gt 10 ]; then
ls -1t "${BACKUP_DIR}"/${DB_NAME}_*.sql.gz | tail -n +11 | xargs rm -f
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 保留最近10份备份,其余已清理"
fi
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 备份任务完成"
+45
View File
@@ -0,0 +1,45 @@
#!/bin/bash
# ============================================
# 丹阳公益婚恋服务平台 — CI/CD 触发脚本
# ============================================
# 由 GitHub Actions 调用,执行以下步骤:
# 1. 构建 Backend Docker 镜像
# 2. 构建 Admin Web Docker 镜像
# 3. 推送镜像到 Docker Hub / 私有仓库
# 4. 触发远程部署
# ============================================
set -euo pipefail
IMAGE_REPO="${DOCKER_REGISTRY:-registry.cn-hangzhou.aliyuncs.com/danyang-match}"
VERSION="${GITHUB_SHA:-latest}"
BRANCH="${GITHUB_REF_NAME:-main}"
echo "============================================"
echo " 丹阳公益婚恋平台 — CI 构建"
echo " 分支: ${BRANCH} | 版本: ${VERSION}"
echo "============================================"
# 构建 Backend 镜像
echo "[1/3] 构建 Backend 镜像..."
docker build -t "${IMAGE_REPO}/backend:${VERSION}" -f backend/Dockerfile backend/
docker tag "${IMAGE_REPO}/backend:${VERSION}" "${IMAGE_REPO}/backend:latest"
# 构建 Admin Web 镜像
echo "[2/3] 构建 Admin Web 镜像..."
docker build -t "${IMAGE_REPO}/admin:${VERSION}" -f admin-web/Dockerfile admin-web/
docker tag "${IMAGE_REPO}/admin:${VERSION}" "${IMAGE_REPO}/admin:latest"
# 推送镜像
if [ "${BRANCH}" = "main" ] || [ "${BRANCH}" = "master" ]; then
echo "[3/3] 推送镜像到仓库..."
docker push "${IMAGE_REPO}/backend:${VERSION}"
docker push "${IMAGE_REPO}/backend:latest"
docker push "${IMAGE_REPO}/admin:${VERSION}"
docker push "${IMAGE_REPO}/admin:latest"
echo "✅ 镜像推送完成"
else
echo "⏭ 非主分支,跳过推送"
fi
echo ""
echo "✅ CI 完成!版本: ${VERSION}"
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# ============================================
# 丹阳公益婚恋服务平台 — 一键部署脚本
# ============================================
# 用法:
# ./scripts/deploy.sh # 部署最新镜像
# ./scripts/deploy.sh v1.2.3 # 部署指定版本
# ./scripts/deploy.sh --rollback # 回滚到上一版本
# ============================================
set -euo pipefail
VERSION="${1:-latest}"
ACTION="${2:-deploy}"
COMPOSE_FILE="${COMPOSE_FILE:-../infrastructure/docker-compose.prod.yml}"
GIT_REPO="${GIT_REPO:-https://github.com/danyang-match/platform.git}"
echo "============================================"
echo " 丹阳公益婚恋服务平台 — 部署脚本"
echo " 版本: ${VERSION} | 动作: ${ACTION}"
echo "============================================"
# 检查环境变量
if [ -z "${DOCKER_REGISTRY:-}" ]; then
echo "警告: DOCKER_REGISTRY 未设置,将使用本地构建"
fi
# 进入基础设施目录
cd "$(dirname "$0")/.."
case "${ACTION}" in
deploy)
echo "[1/4] 拉取最新代码..."
if [ -d ".git" ]; then
git fetch origin
git checkout main || git checkout master
git pull --ff-only
fi
echo "[2/4] 构建并启动服务..."
# 设置版本号
export APP_VERSION="${VERSION}"
docker compose -f "${COMPOSE_FILE}" up -d --build --pull always
echo "[3/4] 等待服务健康检查..."
sleep 10
docker compose -f "${COMPOSE_FILE}" ps | grep -E "backend|postgres|redis" || true
echo "[4/4] 验证健康状态..."
curl -sf "http://localhost/api/v1/health/live" && echo " ✅ 后端健康" || echo " ❌ 后端不健康"
curl -sf "http://localhost/version" && echo " ✅ 版本信息" || echo " ❌ 版本接口失败"
echo ""
echo "✅ 部署完成!版本: ${VERSION}"
echo " 访问地址: https://match.danyang.gov.cn/admin/"
;;
rollback)
echo "[1/2] 回滚到上一版本..."
# 查找上一个 image tag
PREV_VERSION=$(docker images --format '{{.Tag}}' danyang-match-backend 2>/dev/null | grep -v "^latest$" | sort -V | tail -2 | head -1)
if [ -z "${PREV_VERSION}" ]; then
echo "错误: 找不到可回滚的镜像版本"
exit 1
fi
echo " 回滚版本: ${PREV_VERSION}"
export APP_VERSION="${PREV_VERSION}"
docker compose -f "${COMPOSE_FILE}" up -d --no-build backend
echo "[2/2] 验证回滚..."
sleep 5
curl -sf "http://localhost/api/v1/health/live" && echo " ✅ 回滚成功" || echo " ❌ 回滚后不健康"
;;
*)
echo "用法: $0 [version] [deploy|rollback]"
echo "示例:"
echo " $0 v1.2.3 deploy"
echo " $0 rollback"
exit 1
;;
esac
@@ -0,0 +1,67 @@
#!/bin/bash
# ============================================
# 丹阳公益婚恋服务平台 — 上线首日监控检查
# ============================================
set -euo pipefail
echo "============================================"
echo " 丹阳公益婚恋平台 — 上线首日监控"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================"
echo ""
# 1. 服务健康
echo "【1. 服务健康】"
docker compose -f "${COMPOSE_FILE:-../infrastructure/docker-compose.prod.yml}" ps 2>/dev/null | grep -E "backend|postgres|redis|nginx" || echo " ⚠️ Docker Compose未运行,跳过容器检查"
# 2. 错误日志
echo ""
echo "【2. 错误日志检查(最近100行)】"
docker logs danyang-match-backend --tail 100 2>/dev/null | grep -i "error\|exception\|fatal" | tail -5 || echo " ✅ 无ERROR日志"
# 3. 数据库连接
echo ""
echo "【3. 数据库连接】"
docker exec danyang-match-postgres pg_isready -U "${DB_USER:-danyang}" 2>/dev/null && \
echo " ✅ PostgreSQL连接正常" || echo " ❌ PostgreSQL连接异常"
# 4. Redis连接
echo ""
echo "【4. Redis连接】"
docker exec danyang-match-redis redis-cli ping 2>/dev/null | grep -q PONG && \
echo " ✅ Redis连接正常" || echo " ⚠️ Redis不可用(可接受:非核心依赖)"
# 5. 磁盘空间
echo ""
echo "【5. 磁盘空间】"
DISK_USAGE=$(df -h / 2>/dev/null | awk 'NR==2{print $5}' || echo "unknown")
echo " 根分区使用: ${DISK_USAGE}"
if [ "${DISK_USAGE%"%"}" -gt 80 ] 2>/dev/null; then
echo " ⚠️ 磁盘使用超过80%,请关注"
fi
# 6. 内存使用
echo ""
echo "【6. 内存使用】"
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}" 2>/dev/null || true
# 7. 关键API响应时间
echo ""
echo "【7. API响应时间】"
for endpoint in "/api/v1/health/live" "/api/v1/users" "/api/v1/matches/recommendations"; do
TIME=$(curl -s -o /dev/null -w "%{time_total}s" "http://localhost:8080${endpoint}" 2>/dev/null || echo "N/A")
echo " ${endpoint}: ${TIME}"
done
# 8. 举报/申诉数量
echo ""
echo "【8. 业务指标(生产环境)】"
echo " ⚠️ 请通过后台管理界面查看实时统计数据"
echo ""
echo "============================================"
echo " 上线首日监控完成"
echo " 如发现异常,请立即执行:"
echo " docker compose -f infrastructure/docker-compose.prod.yml restart"
echo " 或联系技术负责人"
echo "============================================"
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
# ============================================
# 丹阳公益婚恋服务平台 — 灰度发布脚本
# ============================================
# 用法:
# ./scripts/gray-release.sh 10 # 10%流量灰度
# ./scripts/gray-release.sh 50 # 50%流量
# ./scripts/gray-release.sh 100 # 全量发布
# ============================================
set -euo pipefail
PERCENTAGE="${1:-10}"
COMPOSE_FILE="${COMPOSE_FILE:-../infrastructure/docker-compose.prod.yml}"
if [ "${PERCENTAGE}" -lt 1 ] || [ "${PERCENTAGE}" -gt 100 ]; then
echo "错误: 灰度比例应在1-100之间"
exit 1
fi
echo "============================================"
echo " 丹阳公益婚恋平台 — 灰度发布"
echo " 灰度比例: ${PERCENTAGE}%"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================"
# 步骤1: 启动新版本容器(不暴露端口)
echo "[1/4] 启动新版本容器(隐藏端口,不接收流量)..."
docker compose -f "${COMPOSE_FILE}" up -d --no-recreate backend-new \
-e APP_VERSION="gray-${PERCENTAGE}pct" || {
echo "❌ 新版本容器启动失败"
exit 1
}
# 步骤2: 健康检查新版本
echo "[2/4] 健康检查新版本..."
sleep 15
if curl -sf "http://localhost:8081/api/v1/health/ready" > /dev/null 2>&1; then
echo " ✅ 新版本健康检查通过"
else
echo " ⚠️ 新版本健康检查失败,回滚中..."
docker compose -f "${COMPOSE_FILE}" down backend-new
exit 1
fi
# 步骤3: 更新Nginx配置(通过环境变量控制流量比例)
echo "[3/4] 更新Nginx流量分配..."
# 实际生产环境使用环境变量或配置中心控制
echo " 灰度比例已设置为 ${PERCENTAGE}%"
echo " 请通过配置中心或Nginx upstream权重调整流量分配"
# 步骤4: 监控新版本指标
echo "[4/4] 监控新版本指标(30秒观察期)..."
sleep 30
echo " 观察期完成。如发现问题,执行以下命令回滚:"
echo " docker compose -f ${COMPOSE_FILE} down backend-new"
echo " docker compose -f ${COMPOSE_FILE} up -d backend"
echo ""
echo "✅ 灰度发布配置完成!比例: ${PERCENTAGE}%"
echo ""
echo "回滚指令:"
echo " ./scripts/gray-release.sh rollback"
+77
View File
@@ -0,0 +1,77 @@
#!/bin/bash
# ============================================
# 丹阳公益婚恋服务平台 — 健康检查与监控脚本
# ============================================
# 用法:
# ./scripts/health-check.sh # 检查所有服务
# ./scripts/health-check.sh --detail # 详细输出
# ============================================
set -euo pipefail
COMPOSE_FILE="${COMPOSE_FILE:-../infrastructure/docker-compose.prod.yml}"
DETAIL="${2:-}"
EXIT_CODE=0
check_service() {
local name="$1"
local check="$2"
local result
result=$(eval "$check" 2>/dev/null || echo "FAIL")
if [ "${result}" = "OK" ]; then
echo " ✅ ${name}: ${result}"
else
echo " ❌ ${name}: ${result}"
EXIT_CODE=1
fi
}
echo "============================================"
echo " 丹阳公益婚恋服务平台 — 健康检查"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================"
# 容器状态
echo ""
echo "【容器状态】"
docker compose -f "${COMPOSE_FILE}" ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null | head -10
# 服务检查
echo ""
echo "【服务检查】"
check_service "PostgreSQL" \
"pg_isready -h postgres -U ${DB_USER:-danyang} -d ${DB_NAME:-danyang_match} 2>/dev/null && echo OK || echo FAIL"
check_service "Redis" \
"redis-cli -h redis${REDIS_PASSWORD:+ -a ${REDIS_PASSWORD}} ping 2>/dev/null | grep -q PONG && echo OK || echo FAIL"
check_service "MinIO" \
"curl -sf http://minio:9000/minio/health/live 2>/dev/null | grep -q '\"status\":\"online\"' && echo OK || echo FAIL"
check_service "Backend API" \
"curl -sf http://localhost:8080/api/v1/health/live 2>/dev/null | grep -q success && echo OK || echo FAIL"
check_service "Backend Ready" \
"curl -sf http://localhost:8080/api/v1/health/ready 2>/dev/null | grep -q success && echo OK || echo FAIL"
check_service "Nginx" \
"curl -sf http://localhost/nginx-health 2>/dev/null && echo OK || echo FAIL"
# 详细输出
if [ "${DETAIL}" = "--detail" ]; then
echo ""
echo "【磁盘使用】"
docker system df 2>/dev/null || true
echo ""
echo "【内存使用】"
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" 2>/dev/null || true
fi
echo ""
if [ ${EXIT_CODE} -eq 0 ]; then
echo "✅ 所有服务正常"
else
echo "❌ 存在异常服务,请检查上方输出"
fi
exit ${EXIT_CODE}
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
# ============================================
# 丹阳公益婚恋服务平台 — PostgreSQL 恢复脚本
# ============================================
# 用法:
# ./scripts/restore.sh <backup-file>
# ./scripts/restore.sh backups/danyang_match_20260828_143000.sql.gz
# ============================================
set -euo pipefail
if [ $# -lt 1 ]; then
echo "用法: $0 <备份文件路径>"
echo "示例: $0 /app/backups/danyang_match_20260828_143000.sql.gz"
exit 1
fi
BACKUP_FILE="$1"
if [ ! -f "${BACKUP_FILE}" ]; then
echo "错误: 备份文件不存在: ${BACKUP_FILE}"
exit 1
fi
DB_NAME="${DB_NAME:-danyang_match}"
DB_USER="${DB_USER:-danyang}"
DB_HOST="${DB_HOST:-postgres}"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 开始恢复: ${BACKUP_FILE} -> ${DB_NAME}"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ⚠️ 将覆盖现有数据库,请确认!"
# 恢复数据
gunzip -c "${BACKUP_FILE}" | psql \
-h "${DB_HOST}" \
-U "${DB_USER}" \
-d "${DB_NAME}" \
--On-error-stop
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 恢复完成"
# 验证
TABLE_COUNT=$(psql -h "${DB_HOST}" -U "${DB_USER}" -d "${DB_NAME}" -tAc \
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public'")
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 验证: 公共 schema 下 ${TABLE_COUNT} 张表"
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# ============================================
# 丹阳公益婚恋服务平台 — 生产上线冒烟测试
# ============================================
set -euo pipefail
BASE_URL="${BASE_URL:-https://match.danyang.gov.cn}"
PASS=0
FAIL=0
check() {
local name="$1"
local url="$2"
local expected="$3"
local code
code=$(curl -sf -o /dev/null -w "%{http_code}" "${BASE_URL}${url}" 2>/dev/null || echo "000")
if [ "$code" = "$expected" ]; then
PASS=$((PASS + 1))
echo " ✅ $name (HTTP $code)"
else
FAIL=$((FAIL + 1))
echo " ❌ $name (期望$expected, 实际$code)"
fi
}
echo "============================================"
echo " 丹阳公益婚恋平台 — 生产冒烟测试"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "============================================"
echo ""
echo "【健康检查】"
check "后端live" "/api/v1/health/live" "200"
check "后端ready" "/api/v1/health/ready" "200"
check "版本信息" "/version" "200"
echo ""
echo "【认证流程】"
check "微信登录端点" "/api/v1/auth/wechat/login" "401" # 无code参数应400或401
echo ""
echo "【核心功能】"
check "用户资料接口" "/api/v1/profile" "401" # 未登录应401
check "推荐列表接口" "/api/v1/matches/recommendations" "401"
echo ""
echo "【安全接口】"
check "管理员接口无认证拒绝" "/api/v1/operator/users" "401"
check "举报提交接口" "/api/v1/risk/reports" "401"
echo ""
echo "【Nginx】"
check "Nginx首页" "/" "200"
check "HTTPS生效" "https://${BASE_URL##*://}/" "200"
echo ""
echo "============================================"
echo " 结果: PASS=${PASS} FAIL=${FAIL}"
echo "============================================"
[ ${FAIL} -eq 0 ] && echo "✅ 冒烟测试通过" && exit 0 || echo "❌ 存在${FAIL}个失败项" && exit 1