diff --git a/db2/scripts/patch_001_notes_views_and_indexes.sql b/db2/scripts/patch_001_notes_views_and_indexes.sql new file mode 100644 index 0000000..b0f99ba --- /dev/null +++ b/db2/scripts/patch_001_notes_views_and_indexes.sql @@ -0,0 +1,16 @@ +-- 补丁 001:Notes 表新增观看人数列 + 补齐索引 +-- 数据库类型: SQLite +-- 依赖: create_sqlite3.sql 已执行(Users / Notes / Logs 表已存在) +-- 幂等性: 索引部分可重复执行(IF NOT EXISTS); +-- ADD COLUMN 为一次性迁移,重复执行会报 "duplicate column name: views", +-- 请按迁移编号顺序执行一次。 + +-- ── Notes 新增观看人数列 ──────────────────────────────────────────── +-- 观看人数(view count),非空、默认 0,保证旧数据与新插入兼容。 +ALTER TABLE Notes ADD COLUMN views INTEGER NOT NULL DEFAULT 0; + +-- ── 补齐此前缺失的索引 ────────────────────────────────────────────── +-- dnfs2 get_notes 按 pid 查询子节点,此前为全表扫描。 +CREATE INDEX IF NOT EXISTS idx_notes_pid ON Notes(pid); +-- dnfs2 / ca 按 owner 做所有权校验,此前为全表扫描。 +CREATE INDEX IF NOT EXISTS idx_notes_owner ON Notes(owner); diff --git a/dnfs2/src/dnfs2.cpp b/dnfs2/src/dnfs2.cpp index 67c6faf..c3952c9 100644 --- a/dnfs2/src/dnfs2.cpp +++ b/dnfs2/src/dnfs2.cpp @@ -651,6 +651,48 @@ private: return std::string(); } + // 观看人数自增(get_notes 打开节点 pid 时调用)。 + // db2 的 update 仅支持字面量绑定(SET col = ?),无法表达 views = views + 1, + // 故采用「读当前值 → 写 +1」。读改写非原子,高并发下可能少计,作为浏览计数可接受。 + // 任何失败仅记 WARNING 日志并吞掉,绝不影响 get_notes 主流程。 + void _bump_views(int64_t id) { + try { + json::object sel; + sel["action"] = "select"; + sel["table"] = "Notes"; + sel["columns"] = json::array{"views"}; + sel["where"] = json::object{{"id", id}}; + sel["limit"] = 1; + + DbResult r = _call_db(sel); + if (!r.success || !r.data.is_array() || r.data.as_array().empty()) + return; // 节点不存在或查询失败:静默跳过 + + const auto& note = r.data.as_array()[0]; + int64_t views = 0; + if (note.is_object() && note.as_object().contains("views") + && note.as_object().at("views").is_int64()) { + views = note.as_object().at("views").as_int64(); + } + + json::object upd; + upd["action"] = "update"; + upd["table"] = "Notes"; + upd["values"] = json::object{{"views", views + 1}}; + upd["where"] = json::object{{"id", id}}; + + DbResult ur = _call_db(upd); + if (!ur.success) { + _log("WARNING", "dnfs2", "views 自增失败 id=" + std::to_string(id) + + " err=" + ur.error); + } + } catch (const std::exception& e) { + // _call_db 网络异常会抛 DnfsExp(502),一并捕获,绝不影响主流程 + _log("WARNING", "dnfs2", std::string("views 自增异常 id=") + + std::to_string(id) + ": " + e.what()); + } + } + // ---- Token 验签(通过 gateway /whoami)--------------------------------- struct UserInfo { @@ -843,6 +885,12 @@ private: return make_json_obj(http::status::internal_server_error, err, req); } + // 打开节点 pid 时观看人数 +1(pid=0 为虚拟根节点,无对应行,跳过)。 + // best-effort:失败仅记日志,不影响返回结果。 + if (pid != 0) { + _bump_views(pid); + } + // 解析当前用户(可选):为每个节点附带 can_operate 标记 std::optional info = std::nullopt; std::string token = extract_token(req);