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>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-16 09:44:50 +08:00
1 parent 927592b472
commit b5e27810fd
4 files changed
+321

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# 衍水工资系统 —— MySQL 全量备份(⑨ 备份恢复与运维检查)
#
# 思路:在跑中的容器内执行 mysqldump,免暴露 3306 端口给宿主机工具,
# 也避免宿主机装 MySQL 客户端。凭据从 docker/.env 读取(不入脚本、不入版本库)。
#
# 用法:在项目根目录执行 bash scripts/backup_full.sh
# 产物:db/backups/payroll_YYYYMMDD_HHMMSS.sql.gz,保留最近 30 天。
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 不存在(请先创建 docker/.env 并填入 MySQL 凭据)" >&2
exit 1
fi
: "${MYSQL_DATABASE:?docker/.env 缺 MYSQL_DATABASE}"
: "${MYSQL_ROOT_PASSWORD:?docker/.env 缺 MYSQL_ROOT_PASSWORD}"
BACKUP_DIR="$ROOT/db/backups"
mkdir -p "$BACKUP_DIR"
STAMP="$(date +%Y%m%d_%H%M%S)"
OUT="$BACKUP_DIR/payroll_${STAMP}.sql.gz"
# 容器名固定(compose 显式 container_name),与 docker/docker-compose.dev.yml 保持一致
CONTAINER="ys-mysql-dev"
if ! docker inspect "$CONTAINER" >/dev/null 2>&1; then
echo "ERROR: 容器 $CONTAINER 未运行(先 docker compose up -d)" >&2
exit 1
fi
echo "开始备份 $MYSQL_DATABASE → $(basename "$OUT")"
docker exec "$CONTAINER" sh -c \
"mysqldump -uroot -p\"\$MYSQL_ROOT_PASSWORD\" --single-transaction --routines --triggers \"$MYSQL_DATABASE\"" \
| gzip > "$OUT"
if [[ ! -s "$OUT" ]]; then
echo "ERROR: 备份文件为空(数据库名或凭据可能不对)" >&2
rm -f "$OUT"
exit 1
fi
SIZE="$(du -h "$OUT" | cut -f1)"
echo "备份完成:$(basename "$OUT") ($SIZE)"
# 保留 30 天
find "$BACKUP_DIR" -maxdepth 1 -name 'payroll_*.sql.gz' -mtime +30 -print -delete
echo "已清理 30 天前的旧备份"
echo ""
echo "最新备份:"
ls -lh "$BACKUP_DIR"/*.sql.gz 2>/dev/null | tail -3