feat(P2): 实现质量保障类5项功能(无障碍/视觉回归/通知渠道/漏洞扫描/灾备)
## 新增功能 ### 1. 屏幕阅读器兼容性增强(a11y) - 无障碍工具库:src/shared/lib/a11y.ts - aria-live Hook:src/shared/hooks/use-aria-live.ts - a11y 组件:skip-link/visually-hidden/focus-trap/aria-status - 增强 UI:table.tsx 系统性 ARIA role,dialog.tsx aria-modal - 审计文档:docs/accessibility/a11y-audit.md(WCAG 2.1 AA 清单) ### 2. 视觉回归测试 - 测试套件:tests/visual/(homepage + 3 个 dashboard) - 3 视口(desktop/tablet/mobile)× 2 主题(light/dark) - 动态元素遮罩,避免误报 - playwright.config.ts 新增 visual-chromium 项目 - 文档:docs/testing/visual-regression.md ### 3. 短信/微信推送渠道集成 - 新模块:src/modules/notifications/ - 4 个渠道:SMS(阿里云/腾讯云)、WeChat(公众号)、Email(SMTP)、In-App - 分发器按用户偏好并行多渠道发送 - 外部 SDK 动态 import,Mock 模式开发可用 - 文档:docs/notifications/channels.md ### 4. 漏洞扫描 CI 集成 - CI security-scan job:npm audit + Snyk + Trivy FS + OWASP ZAP - 独立工作流 security.yml:每周一深度扫描 + 容器镜像扫描 - 配置:suppressions.json + .trivyignore - 本地脚本:security-scan.sh/ps1 - 文档:docs/security/scanning.md(SLA 分级) ### 5. 灾备方案 - 脚本:backup-verify/backup-offsite-sync/dr-drill/failover/health-check - CI 增强:备份后校验+异地同步,每周灾备演练 - 独立工作流 dr-drill.yml:每周一凌晨 4 点自动演练 - 文档:docs/dr/dr-plan.md(RTO 4h/RPO 24h)+ dr-runbook.md(6 故障场景) ## 验证 - npx tsc --noEmit:0 错误 - npm run lint:0 错误 0 警告
This commit is contained in:
221
scripts/backup-verify.sh
Normal file
221
scripts/backup-verify.sh
Normal file
@@ -0,0 +1,221 @@
|
||||
#!/bin/bash
|
||||
# 备份完整性校验脚本
|
||||
# 用法: ./backup-verify.sh [backup_file] [--min-size BYTES]
|
||||
# 不传参数时校验最新备份
|
||||
|
||||
set -u
|
||||
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
用法: $0 [backup_file] [选项]
|
||||
备份完整性校验脚本
|
||||
|
||||
参数:
|
||||
backup_file 要校验的备份文件路径(不传时校验最新备份)
|
||||
|
||||
选项:
|
||||
--min-size BYTES 最小文件大小阈值(字节),默认 1024
|
||||
--no-sql-check 跳过 SQL 语法校验(不连接数据库)
|
||||
--help, -h 显示帮助信息
|
||||
|
||||
环境变量:
|
||||
BACKUP_DIR 备份目录(默认 ./backups)
|
||||
DATABASE_URL 数据库连接 URL(用于 SQL 语法校验)
|
||||
BACKUP_VERIFY_MIN_SIZE 最小文件大小(字节,默认 1024)
|
||||
|
||||
退出码:
|
||||
0 校验通过
|
||||
1 校验失败
|
||||
EOF
|
||||
}
|
||||
|
||||
# 解析参数
|
||||
BACKUP_FILE=""
|
||||
MIN_SIZE="${BACKUP_VERIFY_MIN_SIZE:-1024}"
|
||||
NO_SQL_CHECK=0
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--help|-h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
--min-size)
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "ERROR: --min-size requires an argument" >&2
|
||||
exit 1
|
||||
fi
|
||||
MIN_SIZE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-sql-check)
|
||||
NO_SQL_CHECK=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
BACKUP_FILE="$1"
|
||||
else
|
||||
echo "ERROR: Unknown argument: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
BACKUP_DIR="${BACKUP_DIR:-./backups}"
|
||||
|
||||
# 如果未指定文件,查找最新备份
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
BACKUP_FILE=$(ls -t "$BACKUP_DIR"/db_backup_*.sql.gz 2>/dev/null | head -1)
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
echo "ERROR: No backup file found in $BACKUP_DIR" >&2
|
||||
echo "Hint: Run scripts/backup-db.sh first or specify a file path" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "=== Backup Verification Report ==="
|
||||
echo "File: $BACKUP_FILE"
|
||||
echo "Time: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
echo ""
|
||||
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
|
||||
# 步骤 1: 检查文件存在
|
||||
echo "[1/4] Checking file existence..."
|
||||
if [ ! -f "$BACKUP_FILE" ]; then
|
||||
echo " FAIL: File does not exist: $BACKUP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
echo " PASS: File exists"
|
||||
|
||||
# 步骤 2: 检查文件大小
|
||||
echo "[2/4] Checking file size..."
|
||||
FILE_SIZE=$(stat -c%s "$BACKUP_FILE" 2>/dev/null || stat -f%z "$BACKUP_FILE" 2>/dev/null)
|
||||
if [ -z "$FILE_SIZE" ]; then
|
||||
echo " WARN: Could not determine file size"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
elif [ "$FILE_SIZE" -lt "$MIN_SIZE" ]; then
|
||||
echo " FAIL: File size ${FILE_SIZE} bytes is below threshold ${MIN_SIZE} bytes"
|
||||
echo " This may indicate a corrupted or empty backup"
|
||||
exit 1
|
||||
else
|
||||
echo " PASS: File size ${FILE_SIZE} bytes (threshold: ${MIN_SIZE} bytes)"
|
||||
fi
|
||||
|
||||
# 步骤 3: 校验 gzip 完整性
|
||||
echo "[3/4] Verifying gzip integrity..."
|
||||
if ! gunzip -t "$BACKUP_FILE" 2>/dev/null; then
|
||||
echo " FAIL: gzip integrity check failed - file may be corrupted"
|
||||
exit 1
|
||||
fi
|
||||
echo " PASS: gzip integrity verified"
|
||||
|
||||
# 步骤 4: 校验 SQL 内容
|
||||
echo "[4/4] Verifying SQL content..."
|
||||
TEMP_SQL=$(mktemp 2>/dev/null || echo "/tmp/backup_verify_$$.sql")
|
||||
trap "rm -f \"$TEMP_SQL\" /tmp/backup_verify_errors_$$.txt 2>/dev/null" EXIT
|
||||
|
||||
if ! gunzip -c "$BACKUP_FILE" > "$TEMP_SQL" 2>/dev/null; then
|
||||
echo " FAIL: Could not decompress backup file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查文件非空
|
||||
SQL_SIZE=$(stat -c%s "$TEMP_SQL" 2>/dev/null || stat -f%z "$TEMP_SQL" 2>/dev/null)
|
||||
if [ -z "$SQL_SIZE" ] || [ "$SQL_SIZE" -eq 0 ]; then
|
||||
echo " FAIL: Decompressed SQL file is empty"
|
||||
exit 1
|
||||
fi
|
||||
echo " PASS: Decompressed size: ${SQL_SIZE} bytes"
|
||||
|
||||
# 检查 mysqldump 头部
|
||||
if grep -q "MySQL dump" "$TEMP_SQL" 2>/dev/null || grep -q "mysqldump" "$TEMP_SQL" 2>/dev/null; then
|
||||
echo " PASS: mysqldump header found"
|
||||
else
|
||||
echo " WARN: mysqldump header not found (may not be a standard mysqldump file)"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
fi
|
||||
|
||||
# 检查 SQL 语句数量
|
||||
STMT_COUNT=$(grep -c ";" "$TEMP_SQL" 2>/dev/null || echo 0)
|
||||
if [ "$STMT_COUNT" -lt 10 ]; then
|
||||
echo " WARN: Low statement count (${STMT_COUNT} semicolons)"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
else
|
||||
echo " PASS: Found ${STMT_COUNT} SQL statements"
|
||||
fi
|
||||
|
||||
# 检查 CREATE TABLE 数量
|
||||
CREATE_COUNT=$(grep -ci "CREATE TABLE" "$TEMP_SQL" 2>/dev/null || echo 0)
|
||||
echo " INFO: Found ${CREATE_COUNT} CREATE TABLE statements"
|
||||
|
||||
# 检查明显的语法错误标记
|
||||
if grep -qi "ERROR at line" "$TEMP_SQL" 2>/dev/null; then
|
||||
echo " FAIL: Found error markers in SQL file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# SQL 语法校验(可选,需要 DATABASE_URL)
|
||||
if [ "$NO_SQL_CHECK" -eq 1 ]; then
|
||||
echo " SKIP: SQL syntax check skipped (--no-sql-check)"
|
||||
elif [ -z "${DATABASE_URL:-}" ]; then
|
||||
echo " SKIP: DATABASE_URL not set, skipping SQL syntax check"
|
||||
else
|
||||
echo " Performing SQL syntax check via mysql..."
|
||||
# 解析 DATABASE_URL
|
||||
DB_USER=$(echo "$DATABASE_URL" | sed -n 's/.*:\/\/\([^:]*\):.*/\1/p')
|
||||
DB_PASS=$(echo "$DATABASE_URL" | sed -n 's/.*:\/\/[^:]*:\([^@]*\)@.*/\1/p')
|
||||
DB_HOST=$(echo "$DATABASE_URL" | sed -n 's/.*@\([^:]*\):.*/\1/p')
|
||||
DB_PORT=$(echo "$DATABASE_URL" | sed -n 's/.*:\([0-9]*\)\/.*/\1/p')
|
||||
|
||||
if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ]; then
|
||||
echo " WARN: Could not parse DATABASE_URL, skipping SQL syntax check"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
else
|
||||
# 创建临时数据库进行语法校验(不影响生产数据)
|
||||
TEMP_DB="verify_$(date +%s)_$$"
|
||||
ERROR_FILE="/tmp/backup_verify_errors_$$.txt"
|
||||
|
||||
if mysql -h "$DB_HOST" -P "${DB_PORT:-3306}" -u "$DB_USER" -p"$DB_PASS" \
|
||||
-e "CREATE DATABASE \`$TEMP_DB\`;" 2>"$ERROR_FILE"; then
|
||||
# 使用 --force 继续执行,捕获所有语法错误
|
||||
mysql -h "$DB_HOST" -P "${DB_PORT:-3306}" -u "$DB_USER" -p"$DB_PASS" \
|
||||
--force "$TEMP_DB" < "$TEMP_SQL" > /dev/null 2>"$ERROR_FILE" || true
|
||||
|
||||
# 检查是否有语法错误(区分语法错误和执行错误)
|
||||
SYNTAX_ERRORS=$(grep -i "You have an error in your SQL syntax" "$ERROR_FILE" 2>/dev/null | wc -l || echo 0)
|
||||
if [ "$SYNTAX_ERRORS" -gt 0 ]; then
|
||||
echo " FAIL: Found $SYNTAX_ERRORS SQL syntax errors"
|
||||
grep -i "You have an error in your SQL syntax" "$ERROR_FILE" | head -3
|
||||
# 清理临时数据库
|
||||
mysql -h "$DB_HOST" -P "${DB_PORT:-3306}" -u "$DB_USER" -p"$DB_PASS" \
|
||||
-e "DROP DATABASE IF EXISTS \`$TEMP_DB\`;" 2>/dev/null || true
|
||||
exit 1
|
||||
else
|
||||
echo " PASS: SQL syntax check passed (no syntax errors)"
|
||||
fi
|
||||
|
||||
# 清理临时数据库
|
||||
mysql -h "$DB_HOST" -P "${DB_PORT:-3306}" -u "$DB_USER" -p"$DB_PASS" \
|
||||
-e "DROP DATABASE IF EXISTS \`$TEMP_DB\`;" 2>/dev/null || true
|
||||
else
|
||||
echo " WARN: Could not create temp database for syntax check, skipping"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Verification Summary ==="
|
||||
echo "Errors: $ERRORS"
|
||||
echo "Warnings: $WARNINGS"
|
||||
if [ "$ERRORS" -gt 0 ]; then
|
||||
echo "Result: FAILED"
|
||||
exit 1
|
||||
fi
|
||||
echo "Result: PASSED"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user