From 224caa3be20e7e0e393318b0a7b8932d14d0ff13 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 19 Sep 2026 16:51:47 +0800 Subject: [PATCH] =?utf8?q?=E8=87=AA=E5=8A=A8=E5=90=8C=E6=AD=A5=EF=BC=9A?= =?utf8?q?=E4=BB=A3=E7=A0=81/=E6=95=B0=E6=8D=AE/Media=20=E6=9B=B4=E6=96=B0?= =?utf8?q?=EF=BC=882026-09-19=2016:51:47=EF=BC=89=20=E6=96=B0=E5=A2=9E?= =?utf8?q?=E9=A2=98=E7=9B=AEID=EF=BC=9A=E6=97=A0=20=E6=96=B0=E5=A2=9E?= =?utf8?q?=E8=AF=95=E5=8D=B7ID=EF=BC=9A=E6=97=A0=20=E5=8F=8D=E9=A6=88?= =?utf8?q?=E7=BB=93=E6=9E=9C=EF=BC=9A=E6=97=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- auto_sync.sh | 25 +++++++++++- auto_sync_msg.py | 103 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 auto_sync_msg.py diff --git a/auto_sync.sh b/auto_sync.sh index e204e67..67f41e1 100644 --- a/auto_sync.sh +++ b/auto_sync.sh @@ -2,6 +2,8 @@ # StudyBase 每日自动同步:检查 代码/数据库(s/studybase.db)/Media 是否有变更, # 有变更则 git 提交并推送至 origin(本机 /var/lib/git/studybase.git) 与 github。 # 由 /etc/cron.d/studybase-sync 于每日 02:00 调用。 +# 提交说明由 auto_sync_msg.py 生成(对比 HEAD 中上次同步的数据库快照): +# 新增题目ID范围、新增试卷ID范围、反馈结果的试卷ID及统计。 # 安全约定:本脚本只执行 add/commit/push,绝不执行 reset/checkout/clean 等覆盖工作区的命令。 # 日志:/var/log/studybase-auto-sync.log(置于仓库外,避免日志本身进入提交) @@ -25,13 +27,32 @@ if [ -z "$(git status --porcelain)" ]; then exit 0 fi -git add -A || { echo "git add 失败,本次终止"; exit 1; } +# 生成提交说明(对比 HEAD 中的上次数据库快照) +MSGFILE=$(mktemp) || MSGFILE="" +PREVDB=$(mktemp) || PREVDB="" +if [ -n "$MSGFILE" ] && [ -n "$PREVDB" ] && git show HEAD:s/studybase.db > "$PREVDB" 2>/dev/null; then + if ! python3 "$REPO/auto_sync_msg.py" "$PREVDB" "$MSGFILE"; then + echo "提交说明生成失败,改用通用说明" + : > "$MSGFILE" + fi +else + echo "(无法提取上次数据库快照,改用通用说明)" + : > "$MSGFILE" +fi + +git add -A || { echo "git add 失败,本次终止"; rm -f "$PREVDB" "$MSGFILE"; exit 1; } if [ -z "$(git status --porcelain)" ]; then echo "无实质变更,无需提交" + rm -f "$PREVDB" "$MSGFILE" exit 0 fi -git commit -m "自动同步:代码/数据/Media 更新($(date '+%F %T'))" || { echo "git commit 失败,本次终止"; exit 1; } +if [ -s "$MSGFILE" ]; then + git commit -F "$MSGFILE" || { echo "git commit 失败,本次终止"; rm -f "$PREVDB" "$MSGFILE"; exit 1; } +else + git commit -m "自动同步:代码/数据/Media 更新($(date '+%F %T'))" || { rm -f "$PREVDB" "$MSGFILE"; exit 1; } +fi +rm -f "$PREVDB" "$MSGFILE" echo "--- push origin ---" if git push origin main; then diff --git a/auto_sync_msg.py b/auto_sync_msg.py new file mode 100644 index 0000000..ee51020 --- /dev/null +++ b/auto_sync_msg.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +"""生成自动同步提交说明:对比上次同步的数据库快照与当前数据库。 + +用法: auto_sync_msg.py [cur_db_override] +- prev_db_path: 上次同步时的 s/studybase.db(由调用方从 git HEAD 提取) +- 当前库默认通过 sqlite3 在线备份读取(不影响运行中的后端); + 提供第三个参数时改用指定文件(用于测试)。 + +说明内容包括: +- 新增题目ID(区间压缩,如 1015-1041, 1044) +- 新增试卷ID +- 反馈结果的试卷ID(判定行 result_type 有变化的试卷,附当前统计) +""" +import sqlite3, sys, tempfile, os +from datetime import datetime + +prev_path, msg_path = sys.argv[1], sys.argv[2] +cur_override = sys.argv[3] if len(sys.argv) > 3 else None +LIVE = 's/studybase.db' + + +def backup_live(): + fd, tmp = tempfile.mkstemp(suffix='.sqlite') + os.close(fd) + src = sqlite3.connect(LIVE) + dst = sqlite3.connect(tmp) + src.backup(dst) + dst.close() + src.close() + return tmp + + +def load(path): + c = sqlite3.connect(path) + qs = {r[0] for r in c.execute("SELECT id FROM questions")} + ps = {r[0] for r in c.execute("SELECT id FROM papers")} + pq = {} + for pid, qid, rt in c.execute("SELECT paper_id, question_id, result_type FROM paper_questions"): + pq.setdefault(pid, {})[qid] = rt + c.close() + return qs, ps, pq + + +try: + p_qs, p_ps, p_pq = load(prev_path) +except Exception: + p_qs, p_ps, p_pq = set(), set(), {} + +if cur_override: + cur_tmp = None + c_qs, c_ps, c_pq = load(cur_override) +else: + cur_tmp = backup_live() + c_qs, c_ps, c_pq = load(cur_tmp) + os.unlink(cur_tmp) + + +def ranges(ids): + ids = sorted(ids) + if not ids: + return "无" + parts = [] + s = e = ids[0] + for i in ids[1:]: + if i == e + 1: + e = i + else: + parts.append(str(s) if s == e else "%d-%d" % (s, e)) + s = e = i + parts.append(str(s) if s == e else "%d-%d" % (s, e)) + return ", ".join(parts) + + +def decided(pq): + return {pid: {q: rt for q, rt in m.items() if rt != 0} for pid, m in pq.items()} + + +new_q = c_qs - p_qs +new_p = c_ps - p_ps +p_dec, c_dec = decided(p_pq), decided(c_pq) +fb = {pid for pid in set(p_dec) | set(c_dec) + if (p_dec.get(pid) or {}) != (c_dec.get(pid) or {})} + + +def fb_detail(pid): + m = c_pq.get(pid, {}) + total = len(m) + n1 = sum(1 for v in m.values() if v == 1) + n2 = sum(1 for v in m.values() if v == 2) + n3 = sum(1 for v in m.values() if v == 3) + return "%d/%d题:正确%d、错误%d、部分错误%d" % (n1 + n2 + n3, total, n1, n3, n2) + + +lines = ["自动同步:代码/数据/Media 更新(%s)" % datetime.now().strftime('%Y-%m-%d %H:%M:%S')] +lines.append("新增题目ID:%s%s" % (ranges(new_q), "(共%d题)" % len(new_q) if new_q else "")) +lines.append("新增试卷ID:%s%s" % (ranges(new_p), "(共%d份)" % len(new_p) if new_p else "")) +if fb: + lines.append("反馈结果:" + "、".join("试卷%d(%s)" % (pid, fb_detail(pid)) for pid in sorted(fb))) +else: + lines.append("反馈结果:无") + +with open(msg_path, 'w', encoding='utf-8') as f: + f.write("\n".join(lines) + "\n") -- 2.43.0