- 调整单金额恒正: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>
117 lines
4.6 KiB
Bash
117 lines
4.6 KiB
Bash
#!/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
|