补齐源快照、资料预演、正式导入及工资核算的依赖链路。 新增版本化输入、逐段 FIFO、生成锁和结构化当月调整, 并提供默认关闭的周期采集、有限重试及 Windows 托管。 原始客户资料、私有配置及首页改动不纳入本次提交。 BREAKING CHANGE: 升级前需应用新增迁移,并通过 PAYROLL_MAKER_USER_ID 绑定实际工资制作人,否则业务只读。 旧单批次核算默认关闭;历史兼容需显式启用 PAYROLL_ALLOW_LEGACY_CALCULATION。生产启用和真实金额待验收。
90 lines
3.3 KiB
Bash
90 lines
3.3 KiB
Bash
#!/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]}"
|