PayrollManagementSystem/scripts/backup_incremental.sh
liuwenxin 108bf87225 feat(核算)!: 添加 T+ 正式核算与采集托管
补齐源快照、资料预演、正式导入及工资核算的依赖链路。
新增版本化输入、逐段 FIFO、生成锁和结构化当月调整,
并提供默认关闭的周期采集、有限重试及 Windows 托管。

原始客户资料、私有配置及首页改动不纳入本次提交。

BREAKING CHANGE: 升级前需应用新增迁移,并通过
PAYROLL_MAKER_USER_ID 绑定实际工资制作人,否则业务只读。
旧单批次核算默认关闭;历史兼容需显式启用
PAYROLL_ALLOW_LEGACY_CALCULATION。生产启用和真实金额待验收。
2026-11-02 09:42:53 +08:00

90 lines
3.3 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
# 衍水工资系统 —— MySQL 增量备份(binlog 归档)
#
# 口径(澄清 #19 业务答复 2026-10-19):每日全量 + 有更新时增量补充备份。
# 增量 = 归档 MySQL binlog:自上次归档之后产生的新 binlog 文件逐个拷出压缩;
# 没有新变更时不产出文件(「有更新时」才补充)。
#
# 恢复顺序:最新全量(backup_full.sh 产物)→ 依次重放其后的 binlog 增量包
# (用 mysqlbinlog 重放,见 docs/备份恢复手册.md 增量章节)。
#
# 实现说明:mysql:8.0 官方镜像不含 mysqlbinlog 客户端,故不走
# --read-from-remote-server,直接从数据目录 tar 拷出原始 binlog 文件;
# 只归档「已写完」的文件(SHOW BINARY LOGS 列表最后一个为当前写入中,
# 留到下次收取,配合 30 天 binlog 过期窗口余量充足)。
#
# 用法:在项目根目录执行 bash scripts/backup_incremental.sh
# 产物:db/backups/binlog_NNNNNN.gz(NNNNNN 为 binlog 序号),永久保留,不自动清理。
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}"
BACKUP_DIR="$ROOT/db/backups"
STATE_FILE="$BACKUP_DIR/.incremental_state"
mkdir -p "$BACKUP_DIR"
# 容器名固定(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
# 列出 binlog:文件名 序号 大小(mysql -N 无表头,直接出数据行)
mapfile -t BINLOGS < <(
docker exec "$CONTAINER" sh -c \
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -N -e "SHOW BINARY LOGS"' 2>/dev/null \
| awk '{print $1}'
)
if [[ ${#BINLOGS[@]} -eq 0 ]]; then
echo "ERROR: SHOW BINARY LOGS 无输出(binlog 未开启或凭据不对)" >&2
exit 1
fi
if [[ ${#BINLOGS[@]} -eq 1 ]]; then
echo "仅有写入中的 binlog(${BINLOGS[0]}),无已写完文件可归档"
exit 0
fi
COMPLETED=("${BINLOGS[@]:0:${#BINLOGS[@]}-1}") # 去掉写入中的最后一个
LAST="$(cat "$STATE_FILE" 2>/dev/null || echo "")"
NEW=()
for f in "${COMPLETED[@]}"; do
# 与状态文件比较序号:无状态(首次)全收;有状态只收更大的
if [[ -z "$LAST" ]] || (( ${f##*.} > ${LAST##*.} )); then
NEW+=("$f")
fi
done
if [[ ${#NEW[@]} -eq 0 ]]; then
echo "无新增变更(上次归档至 ${LAST},无需补充增量)"
exit 0
fi
STAMP="$(date +%Y%m%d_%H%M%S)"
echo "归档 ${#NEW[@]} 个 binlog(${NEW[0]} ~ ${NEW[-1]})"
for f in "${NEW[@]}"; do
OUT="$BACKUP_DIR/${f}.gz"
docker exec "$CONTAINER" sh -c "tar cf - -C /var/lib/mysql '$f'" | gzip > "$OUT"
if [[ ! -s "$OUT" ]]; then
echo "ERROR: $OUT 为空" >&2
rm -f "$OUT"
exit 1
fi
echo " $(basename "$OUT") ($(du -h "$OUT" | cut -f1))"
done
echo "$STAMP ${NEW[-1]}" > "$STATE_FILE"
# 归档文件永久留存;服务器原始 binlog 过期与归档留存是不同配置,部署前验证归档链完整。
echo "增量归档完成,最新至 ${NEW[-1]}"