]> acesimba.cloud Git - studybase.git/commitdiff
自动同步:代码/数据/Media 更新(2026-09-19 16:51:47)
authorroot <root@localhost.localdomain>
Sat, 19 Sep 2026 08:51:47 +0000 (16:51 +0800)
committerroot <root@localhost.localdomain>
Sat, 19 Sep 2026 08:51:47 +0000 (16:51 +0800)
新增题目ID:无
新增试卷ID:无
反馈结果:无

auto_sync.sh
auto_sync_msg.py [new file with mode: 0644]

index e204e6715fb39189cbd7cdde117c4ff55ecf36e4..67f41e1bc6c0a069d5b7f5035ef14ad1dafa0e51 100644 (file)
@@ -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 (file)
index 0000000..ee51020
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+"""生成自动同步提交说明:对比上次同步的数据库快照与当前数据库。
+
+用法: auto_sync_msg.py <prev_db_path> <msg_output_path> [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")