mirror of
https://github.com/KingHWJ/WeReadBoard.git
synced 2026-11-03 18:24:58 +08:00
刷新按钮支持数据同步:调用 /api/sync 拉取最新数据
- app.py 新增 /api/sync POST 接口:运行 fetch_data.py + migrate_to_mysql.py - refreshShelf() 先调用 /api/sync 同步数据,再重新加载书架 - 按钮文案三阶段:同步中... → 加载中... → 刷新 - CSS 增加 min-width 防止按钮宽度抖动 - launchd plist 和 start_server.sh 均配置 WEREAD_API_KEY 环境变量
This commit is contained in:
1 parent
e8552a9a4c
commit
7bc02075bd
5 files changed
+111
-1
No files matched your search
@@ -94,3 +94,23 @@
|
|||||||
- 删除 renderBooksGroupedByCategory 函数,loadShelf 始终用简单网格渲染
|
- 删除 renderBooksGroupedByCategory 函数,loadShelf 始终用简单网格渲染
|
||||||
- 后端 sort=category 仍生效(按分类排序),但前端不显示分组
|
- 后端 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 加载
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from datetime import datetime
|
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
|
||||||
|
|
||||||
|
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
# 启动入口
|
# 启动入口
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ start_daemon() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "启动服务(后台守护模式)..."
|
echo "启动服务(后台守护模式)..."
|
||||||
|
WEREAD_API_KEY="${WEREAD_API_KEY:-wrk-ZL_oy0woT5yKFaO1jn_15AAA}" \
|
||||||
nohup "$PYTHON" app.py > "$LOG_FILE" 2>&1 &
|
nohup "$PYTHON" app.py > "$LOG_FILE" 2>&1 &
|
||||||
local pid=$!
|
local pid=$!
|
||||||
echo "$pid" > "$PID_FILE"
|
echo "$pid" > "$PID_FILE"
|
||||||
|
|||||||
@@ -776,6 +776,8 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
min-width: 96px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shelf-refresh-btn:hover:not(:disabled) {
|
.shelf-refresh-btn:hover:not(:disabled) {
|
||||||
|
|||||||
+14
-1
@@ -324,10 +324,23 @@ async function refreshShelf() {
|
|||||||
const btn = document.getElementById("shelfRefreshBtn");
|
const btn = document.getElementById("shelfRefreshBtn");
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.classList.add("loading");
|
btn.classList.add("loading");
|
||||||
btn.textContent = "↻ 刷新中...";
|
btn.textContent = "↻ 同步中...";
|
||||||
|
|
||||||
try {
|
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 loadShelfCategories();
|
||||||
await loadShelf(1);
|
await loadShelf(1);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("刷新失败:", e);
|
||||||
|
alert("刷新失败: " + (e.message || "未知错误"));
|
||||||
} finally {
|
} finally {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.classList.remove("loading");
|
btn.classList.remove("loading");
|
||||||
|
|||||||
Reference in new issue
Block a user