diff --git a/.workbuddy/memory/2026-07-17.md b/.workbuddy/memory/2026-07-17.md index a1bb4d7..2762ce0 100644 --- a/.workbuddy/memory/2026-07-17.md +++ b/.workbuddy/memory/2026-07-17.md @@ -94,3 +94,23 @@ - 删除 renderBooksGroupedByCategory 函数,loadShelf 始终用简单网格渲染 - 后端 sort=category 仍生效(按分类排序),但前端不显示分组 - 分类筛选标签(可折叠层级)保留不变 + +## 13:11 添加书架刷新按钮 + 后端服务保活方案 + +### 书架刷新按钮 +- 书架筛选栏新增「↻ 刷新」按钮(shelfRefreshBtn) +- 新增 refreshShelf() 函数:点击后重新调用 loadShelfCategories() + loadShelf(1) +- 带 loading 状态:按钮禁用 + 文字变为"刷新中..." + +### 后端服务频繁断掉的原因 +- 之前用 Bash 工具 run_in_background 启动的进程,会随工具会话结束被回收 +- nohup 启动的进程在沙箱环境中也可能被清理 + +### 保活方案 +1. **start_server.sh** 脚本:支持 start/daemon/stop/restart/status 五种模式 + - daemon 模式用 nohup 后台运行,记录 PID 到 .server.pid +2. **launchd 服务** (com.weread-dashboard.plist): + - 路径: ~/Library/LaunchAgents/com.weread-dashboard.plist + - KeepAlive=true: 崩溃自动重启 + - RunAtLoad=true: 开机自动启动 + - 需要用户在终端手动执行 launchctl load 加载 diff --git a/app.py b/app.py index a62360a..d780e04 100644 --- a/app.py +++ b/app.py @@ -18,6 +18,7 @@ import json import os +import subprocess import time import urllib.request from datetime import datetime @@ -1183,6 +1184,79 @@ def api_search(conn): }) +# ====================================================================== +# 数据同步接口 — 从微信读书 API 拉取最新数据并迁移到 MySQL +# ====================================================================== + +@app.route("/api/sync", methods=["POST"]) +def sync_data(): + """运行 fetch_data.py + migrate_to_mysql.py,全量同步数据""" + project_dir = os.path.dirname(os.path.abspath(__file__)) + python_bin = os.path.join( + os.path.expanduser("~"), + ".workbuddy/binaries/python/envs/default/bin/python", + ) + env = os.environ.copy() + env["WEREAD_API_KEY"] = env.get("WEREAD_API_KEY", "") + if not env["WEREAD_API_KEY"]: + return jsonify({"ok": False, "error": "WEREAD_API_KEY 未设置"}), 500 + + results = {"steps": []} + try: + # Step 1: 采集数据 → SQLite + r1 = subprocess.run( + [python_bin, "fetch_data.py"], + cwd=project_dir, + env=env, + capture_output=True, + text=True, + timeout=120, + ) + results["steps"].append({ + "name": "fetch_data", + "ok": r1.returncode == 0, + "stdout": r1.stdout[-500:] if r1.stdout else "", + "stderr": r1.stderr[-500:] if r1.stderr else "", + }) + if r1.returncode != 0: + results["ok"] = False + results["error"] = "fetch_data.py 执行失败" + return jsonify(results), 500 + + # Step 2: SQLite → MySQL + r2 = subprocess.run( + [python_bin, "migrate_to_mysql.py"], + cwd=project_dir, + env=env, + capture_output=True, + text=True, + timeout=60, + ) + results["steps"].append({ + "name": "migrate_to_mysql", + "ok": r2.returncode == 0, + "stdout": r2.stdout[-500:] if r2.stdout else "", + "stderr": r2.stderr[-500:] if r2.stderr else "", + }) + if r2.returncode != 0: + results["ok"] = False + results["error"] = "migrate_to_mysql.py 执行失败" + return jsonify(results), 500 + + results["ok"] = True + results["message"] = "数据同步完成" + return jsonify(results) + + except subprocess.TimeoutExpired: + results["ok"] = False + results["error"] = "同步超时(超过120秒)" + return jsonify(results), 500 + except Exception as e: + results["ok"] = False + results["error"] = str(e) + return jsonify(results), 500 + + # ====================================================================== # 启动入口 # ====================================================================== diff --git a/start_server.sh b/start_server.sh index 81ca7fc..01c7e2e 100755 --- a/start_server.sh +++ b/start_server.sh @@ -71,7 +71,8 @@ start_daemon() { exit 1 fi echo "启动服务(后台守护模式)..." - nohup "$PYTHON" app.py > "$LOG_FILE" 2>&1 & + WEREAD_API_KEY="${WEREAD_API_KEY:-wrk-ZL_oy0woT5yKFaO1jn_15AAA}" \ + nohup "$PYTHON" app.py > "$LOG_FILE" 2>&1 & local pid=$! echo "$pid" > "$PID_FILE" sleep 2 diff --git a/static/css/style.css b/static/css/style.css index 605c4e0..aca87e5 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -776,6 +776,8 @@ body { cursor: pointer; transition: all 0.2s; white-space: nowrap; + min-width: 96px; + text-align: center; } .shelf-refresh-btn:hover:not(:disabled) { diff --git a/static/js/app.js b/static/js/app.js index 02fc466..62816b8 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -324,10 +324,23 @@ async function refreshShelf() { const btn = document.getElementById("shelfRefreshBtn"); btn.disabled = true; btn.classList.add("loading"); - btn.textContent = "↻ 刷新中..."; + btn.textContent = "↻ 同步中..."; + try { + // Step 1: 从微信读书 API 同步数据到 MySQL + const resp = await fetch(API_BASE + "/api/sync", { method: "POST" }); + const syncResult = await resp.json(); + if (!syncResult.ok) { + throw new Error(syncResult.error || "同步失败"); + } + + // Step 2: 重新加载分类和书架 + btn.textContent = "↻ 加载中..."; await loadShelfCategories(); await loadShelf(1); + } catch (e) { + console.error("刷新失败:", e); + alert("刷新失败: " + (e.message || "未知错误")); } finally { btn.disabled = false; btn.classList.remove("loading");