PayrollManagementSystem/scripts/restore_drill.sh
liuwenxin b5e27810fd feat(backup): mysqldump 定时备份/恢复演练/健康检查脚本与手册
- scripts/backup_full.sh:容器内 mysqldump --single-transaction,gzip 落盘
  db/backups/,保留 30 天;凭据 source docker/.env 不硬编码;实测备份成功
- scripts/restore_drill.sh:恢复到临时库 payroll_drill_* 并逐表核对行数
  (16 张关键表),默认不 DROP、--drop-exec 才删——绝不覆盖生产库;
  实测 16 表全 ✓。规避嵌套 shell 吞反引号(mysqladmin create/drop、
  双引号包裹表名)
- scripts/health_check.sh:六项检查(容器健康×2/磁盘水位/备份新鲜度/
  Redis 探活/24h 登录失败阈值),PASS/WARN/FAIL 报告,退出码供监控;
  Redis 密码经宿主机 .env 验证(容器内无该环境变量),实测六项全 PASS
- docs/备份恢复手册.md:备份/演练/生产恢复七步/定时 schtasks 两则,
  db/backups/ 已入 .gitignore

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-16 09:44:50 +08:00

89 lines
3.1 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# 恢复演练 —— 把最近一份备份恢复到「临时库」并核对行数(⑨:备份不演练 = 没有备份)
#
# 演练绝不直接覆盖生产库;临时库名 payroll_drill_<时间戳>,核对完由
# 调用方决定是否删除(本脚本默认不 DROP,安全优先)。
#
# 用法:在项目根目录执行 bash scripts/restore_drill.sh
# 产物:临时库 + 终端行数对比报告;--drop-exec 参数在核对通过后删除临时库。
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
if [[ -f docker/.env ]]; then
# shellcheck disable=SC1091
source docker/.env
else
echo "ERROR: docker/.env 不存在" >&2
exit 1
fi
: "${MYSQL_DATABASE:?docker/.env 缺 MYSQL_DATABASE}"
: "${MYSQL_ROOT_PASSWORD:?docker/.env 缺 MYSQL_ROOT_PASSWORD}"
CONTAINER="ys-mysql-dev"
if ! docker inspect "$CONTAINER" >/dev/null 2>&1; then
echo "ERROR: 容器 $CONTAINER 未运行" >&2
exit 1
fi
BACKUP_DIR="$ROOT/db/backups"
LATEST="$(ls -t "$BACKUP_DIR"/payroll_*.sql.gz 2>/dev/null | head -1)"
if [[ -z "$LATEST" ]]; then
echo "ERROR: 未找到备份文件(先 bash scripts/backup_full.sh)" >&2
exit 1
fi
DRILL_DB="payroll_drill_$(date +%Y%m%d_%H%M%S)"
echo "演练恢复:$(basename "$LATEST") → 临时库 $DRILL_DB"
# 1. 创建临时库(mysqladmin create 无反引号转义问题)
docker exec "$CONTAINER" sh -c \
"mysqladmin -uroot -p\"\$MYSQL_ROOT_PASSWORD\" create \"$DRILL_DB\""
# 2. 解压导入
gunzip -c "$LATEST" | docker exec -i "$CONTAINER" sh -c \
"mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\" \"$DRILL_DB\""
# 3. 行数核对:关键业务表
TABLES=(users roles user_roles param_definitions param_values businesses business_items personnel products customers shipments shipment_items receipts approval_orders approval_steps system_settings)
echo ""
echo "======== 行数对比(源库 vs 演练库)========"
printf "%-24s %12s %12s %6s\n" "表名" "源库" "演练库" "结果"
ALL_OK=1
for T in "${TABLES[@]}"; do
SRC=$(docker exec "$CONTAINER" sh -c \
"mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\" -N -B -e \"SELECT COUNT(*) FROM $MYSQL_DATABASE.$T;\" 2>/dev/null" || echo "?")
DST=$(docker exec "$CONTAINER" sh -c \
"mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\" -N -B -e \"SELECT COUNT(*) FROM $DRILL_DB.$T;\" 2>/dev/null" || echo "?")
if [[ "$SRC" == "$DST" ]]; then
MARK="✓"
else
MARK="✗"
ALL_OK=0
fi
printf "%-24s %12s %12s %6s\n" "$T" "$SRC" "$DST" "$MARK"
done
echo "=========================================="
if [[ "$ALL_OK" == "1" ]]; then
echo ""
echo "结果:全部表行数一致,备份可恢复。"
if [[ "${1:-}" == "--drop-exec" ]]; then
docker exec "$CONTAINER" sh -c \
"mysqladmin -uroot -p\"\$MYSQL_ROOT_PASSWORD\" -f drop \"$DRILL_DB\""
echo "已删除临时库 $DRILL_DB"
else
echo "临时库 $DRILL_DB 保留(核对用);确认无误后重跑本脚本加 --drop-exec 删除。"
fi
exit 0
else
echo ""
echo "结果:存在不一致,请检查备份文件与数据库结构。" >&2
echo "临时库 $DRILL_DB 保留供排查。" >&2
exit 1
fi