PayrollManagementSystem/scripts/restore_drill.sh
liuwenxin 45c268b146 fix(m3): code-review P2——金额校验、参数解析与运维脚本容错
- 调整单金额恒正:schema 正则拦格式(非负两位小数、≤12 位整数防
  NUMERIC(14,2) 溢出)+ 服务层 Decimal>0 语义校验——负数会反转补发/
  扣减语义,'abc' 原样入库会以 500 暴露
- 分界天数参数解析:int(parse_fraction(...)) 替代 int(参数原文)——
  '360.0' 是 INT 参数的合法入库值(Fraction 分母为 1),直接 int(str)
  抛 ValueError 会让整月核算 500
- logo 旧文件清理容错:OSError 只记 warning——裸抛会把成功的 logo 更新
  记成失败审计并 500(库中 logo_path 却已是新值,审计与事实相反)
- health_check.sh:Redis 探活改容器内环境变量展开(密码不进宿主进程
  命令行,模式同 backup_full.sh);compose redis 补 REDIS_PASSWORD
- restore_drill.sh:行数核对扩到全部 32 张业务表(原 16 张漏 V7 四表、
  二次分配等核心表,备份漏表演练全绿);查询失败回显 '?' 一律判 ✗
  (原 '?'=='?' 判一致,表缺失时误报「备份可恢复」)
- 并入 88 会话的 #12 实现:manager.personal.included 开关消费
  (_build_manager_aggregates 参数化,测试见 828b573)
- 测试:调整单金额恒正 5 稿、INT 参数小数原文核算 200 回归;单测 469

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-19 12:10:29 +08:00

117 lines
4.6 KiB
Bash
Raw Permalink 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)"
# 0. --drop-exec:演练前先清掉**全部**历史遗留 drill 库(含上一轮「保留供
# 核对」的那份——否则每演练一轮净增一份整库拷贝,磁盘被无人认领的
# drill 库吃满),演练完成后本轮临时库也随之删除
if [[ "${1:-}" == "--drop-exec" ]]; then
OLD_DBS=$(docker exec "$CONTAINER" sh -c \
"mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\" -N -B -e \"SHOW DATABASES LIKE 'payroll_drill_%';\"" 2>/dev/null || true)
for OLD in $OLD_DBS; do
docker exec "$CONTAINER" sh -c \
"mysqladmin -uroot -p\"\$MYSQL_ROOT_PASSWORD\" -f drop \"$OLD\""
echo "已清理历史演练库 $OLD"
done
fi
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. 行数核对:**全部业务表**(新增表必须同步补入本清单——备份漏表时
# 演练要能发现;alembic_version 不列,行数恒 1 无核对价值)
TABLES=(
adjustment_orders approval_orders approval_steps business_items business_roles
business_secondary_allocations business_status_logs businesses customer_rating_grades
customers login_logs monthly_settlements operation_logs ownership_change_logs
param_change_batches param_definitions param_values performance_ledgers permissions
personnel personnel_plans personnel_regions products receipts role_permissions roles
settlement_items shipment_items shipments system_settings user_roles users
)
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="✗"
ALL_OK=0
elif [[ "$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,"
echo "会先清掉包括它在内的全部演练库再演练,本轮完成后同样即删。"
fi
exit 0
else
echo ""
echo "结果:存在不一致,请检查备份文件与数据库结构。" >&2
echo "临时库 $DRILL_DB 保留供排查。" >&2
exit 1
fi