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
+43
View File
@@ -0,0 +1,43 @@
# ============================================
# 丹阳公益婚恋服务平台 — 生产环境环境变量
# ============================================
# ⚠️ 此文件为模板,请勿提交真实值到 Git
# ⚠️ 生产密钥必须通过 k8s secret / Vault / 密钥管理服务注入
# ============================================
# --- Database ---
DB_NAME=danyang_match
DB_USER=danyang
DB_PASSWORD_FILE=/run/secrets/db_password # Docker Swarm secret 文件路径
# PostgreSQL 连接参数(如不使用 Docker secrets 则直接设置 DB_PASSWORD)
# DB_PASSWORD=your-secure-password-here
# --- Redis ---
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=your-redis-password-here
# --- MinIO (对象存储) ---
MINIO_ENDPOINT=http://minio:9000
MINIO_ACCESS_KEY=your-minio-access-key
MINIO_SECRET_KEY=your-minio-secret-key
# --- JWT ---
JWT_ACCESS_SECRET=your-256-bit-jwt-access-secret-here-change-in-production
JWT_REFRESH_SECRET=your-256-bit-jwt-refresh-secret-here-change-in-production
# --- Checkin Token ---
CHECKIN_TOKEN_SECRET=your-hmac-checkin-secret-here-change-in-production
# --- AES-256 Encryption Key (32 bytes = 64 hex chars) ---
ENCRYPTION_KEY=32539f3d2fa8123dcec899cc3dbee7347034123a126ac22fcb4702e10c454d50
# --- WeChat Mini Program ---
WECHAT_APP_ID=your-wechat-app-id
WECHAT_APP_SECRET=your-wechat-app-secret
# --- Feature Flags ---
FEATURES_APPEAL_ENABLED=false
# --- Spring Boot ---
SPRING_PROFILES_ACTIVE=production
+95
View File
@@ -0,0 +1,95 @@
# Staging Docker Compose(预发环境,仅开发/测试)
version: '3.8'
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: danyang_match_staging
POSTGRES_USER: staging
POSTGRES_PASSWORD: staging_dev_password_change_me
ports:
- "5433:5432"
volumes:
- postgres_staging_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U staging -d danyang_match_staging"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6380:6379"
command: redis-server --appendonly yes --requirepass staging-redis-pass-change-me
volumes:
- redis_staging_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "staging-redis-pass-change-me", "ping"]
interval: 10s
timeout: 5s
retries: 5
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: staging
MINIO_ROOT_PASSWORD: staging_dev_password
ports:
- "9010:9000"
- "9011:9001"
volumes:
- minio_staging_data:/data
backend:
build:
context: ../backend
dockerfile: Dockerfile
container_name: danyang-match-staging-backend
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
SPRING_PROFILES_ACTIVE: staging
DB_HOST: postgres
DB_PORT: 5432
DB_NAME: danyang_match_staging
DB_USER: staging
DB_PASSWORD: staging_dev_password_change_me
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_PASSWORD: staging-redis-pass-change-me
MINIO_ENDPOINT: http://minio:9000
MINIO_ACCESS_KEY: staging
MINIO_SECRET_KEY: staging_dev_password
JWT_ACCESS_SECRET: staging-access-secret-dev-only
JWT_REFRESH_SECRET: staging-refresh-secret-dev-only
CHECKIN_TOKEN_SECRET: staging-checkin-secret-dev-only
ENCRYPTION_KEY: 32539f3d2fa8123dcec899cc3dbee7347034123a126ac22fcb4702e10c454d50
FEATURES_APPEAL_ENABLED: "true"
ports:
- "8081:8080"
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/health/ready || exit 1"]
interval: 30s
timeout: 10s
retries: 3
admin-web:
build:
context: ../admin-web
dockerfile: Dockerfile
container_name: danyang-match-staging-admin
depends_on:
- backend
ports:
- "3001:80"
volumes:
postgres_staging_data:
redis_staging_data:
minio_staging_data:
+152
View File
@@ -0,0 +1,152 @@
# Production Nginx Configuration
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 2048;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format production '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time';
access_log /var/log/nginx/access.log production;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=50r/s;
limit_req_zone $binary_remote_addr zone=auth:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=upload:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=ws:10m rate=20r/s;
# Maintenance mode flag
map $http_x_maintenance_mode $maintenance_mode {
default 0;
"1" 1;
}
upstream backend {
server backend:8080;
}
# HTTP -> HTTPS redirect (uncomment for production)
# server {
# listen 80;
# server_name match.danyang.gov.cn;
# return 301 https://$server_name$request_uri;
# }
server {
listen 443 ssl http2;
server_name match.danyang.gov.cn;
# SSL
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' wss:;" always;
# Maintenance mode
location = /maintenance {
return 503 '<h1>系统维护中</h1><p>请稍后再访问。</p>';
}
# Health checks (no auth needed)
location = /api/v1/health/live {
proxy_pass http://backend;
proxy_set_header Host $host;
}
location = /api/v1/health/ready {
proxy_pass http://backend;
proxy_set_header Host $host;
}
location = /version {
proxy_pass http://backend;
proxy_set_header Host $host;
}
# Admin Web (SPA)
location /admin/ {
alias /usr/share/nginx/html/;
try_files $uri $uri/ /admin/index.html;
}
# API proxy
location /api/ {
limit_req zone=api burst=100 nodelay;
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# WebSocket proxy
location /ws/ {
limit_req zone=ws burst=20 nodelay;
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s;
}
# Auth endpoints (strict rate limiting)
location ~* ^/api/v1/auth/ {
limit_req zone=auth burst=5 nodelay;
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Upload endpoints
location ~* ^/api/v1/photos/upload {
limit_req zone=upload burst=5 nodelay;
client_max_body_size 10M;
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# MinIO public bucket (read-only)
location /uploads/ {
proxy_pass http://backend/;
proxy_set_header Host $host;
expires 7d;
add_header Cache-Control "public, immutable";
}
}
}
+100
View File
@@ -0,0 +1,100 @@
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'request_id=$upstream_http_x_request_id';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# Rate limiting zone
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=upload:10m rate=10r/s;
upstream backend {
server backend:8080;
}
server {
listen 80;
server_name localhost;
# Health check for nginx itself
location /nginx-health {
return 200 'ok';
add_header Content-Type text/plain;
}
# API proxy
location /api/ {
limit_req zone=api burst=50 nodelay;
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# WebSocket proxy
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
}
# Auth endpoints - stricter rate limit
location /api/v1/auth/ {
limit_req zone=auth burst=10 nodelay;
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Admin web static files
location /admin/ {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /admin/index.html;
}
# Don't cache API responses
location /api/ {
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
add_header Pragma "no-cache";
}
}
}
+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