Files
dsh_mcn_agent/profiles/web/node_modules/dsh-plugin-mcn/lib/data.js
T
admin 856e632807 baseline: dsh 工作区基线快照(2026-09-10,全新历史起点)
本提交为 dsh_mcn_workshop 仓库的首个提交(旧仓 dsh_MCNProject 已停用),
完整固化 C:/Users/Administrator/.dsh 当前磁盘状态。

1) 临时数据清理:移出 sessions/(81M)、logs/、skill-backup-20260906/(14M)、
   scripts/__pycache__、storages/workspace.json.bak —— 合计 94M(.dsh 162M → 68M)
2) 凭据脱敏:.credentials.yaml(含明文 DEEPSEEK_API_KEY)移出 git 跟踪并加入
   .gitignore;mcn-data-insight/scripts/setup_redfox_key.py 中真实 key 示例改为占位符
3) .gitignore 增补 logs/、skill-backup-*/ 规则
4) 技能结构以当前磁盘状态为准:skills/ = mcn-short-video / impeccable / taste-skill /
   dsh-multi-user-migration;旧技能目录(mcn-dou-analysis、storyboard-prompt、
   short-video-script、browser-harness、mcn-data-insight)与
   profiles/web/node_modules/dsh-vision-router 本地已不存在,本提交记为删除
2026-09-10 22:31:45 +08:00

107 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// dsh-plugin-mcn 数据访问层(拆分自 index.js)
import { initDb } from "./db.js";
import { callMcpTool } from "./mcp.js";
import { STANDARD_TRACKS } from "./config.js";
async function ensureData() {
const d = initDb();
const row = d.prepare("SELECT COUNT(*) AS c FROM hot_accounts").get();
return { cached: row.c > 0, count: row.c };
}
async function refreshData() {
const d = initDb();
const result = await callMcpTool("list_hot_accounts", {});
// 返回结构:{ structured: {total, accounts: [...]} | null, text: "..." }
let accounts = null;
if (result?.structured && Array.isArray(result.structured.accounts)) {
accounts = result.structured.accounts;
}
if (!accounts) {
throw new Error("MCP 返回缺少结构化账号数组(可能未登录或服务异常),无法入库");
}
const insert = d.prepare(`INSERT OR REPLACE INTO hot_accounts
(id, account_name, content, track, tags, masterpiece, created_by, updated_by, created_time, updated_time, sort, del_flag, account_type)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'signed')`);
d.exec("BEGIN");
try {
for (const a of accounts) {
insert.run(
a.id, a.accountName ?? "", a.content ?? "", a.track ?? "",
a.tags ?? "", a.masterpiece ?? "", a.createdBy ?? "", a.updatedBy ?? "",
a.createdTime ?? "", a.updatedTime ?? "", a.sort ?? 0, a.delFlag ?? 0
);
}
d.exec("COMMIT");
} catch (e) {
d.exec("ROLLBACK");
throw e;
}
return { cached: false, count: accounts.length };
}
function queryAccounts({ page = 1, pageSize = 20, search = "", track = "", type = "all", sortBy = "id", order = "asc" }) {
const d = initDb();
const where = [];
const params = [];
// 软删账号不展示(同步 V1.0 工作台:批量删除=del_flag=1)
where.push("(del_flag IS NULL OR del_flag <> 1)");
if (search) {
where.push("(account_name LIKE ? OR content LIKE ? OR track LIKE ?)");
const like = `%${search}%`;
params.push(like, like, like);
}
if (track) {
where.push("track = ?");
params.push(track);
}
// 账号类型筛选:all / signed(签约)/ external(外部)
if (type === "signed" || type === "external") {
where.push("account_type = ?");
params.push(type);
}
const whereSql = where.length ? "WHERE " + where.join(" AND ") : "";
const allowedSort = ["id", "account_name", "track", "sort"];
const sortCol = allowedSort.includes(sortBy) ? sortBy : "id";
const orderSql = order === "desc" ? "DESC" : "ASC";
const total = d.prepare(`SELECT COUNT(*) AS c FROM hot_accounts ${whereSql}`).get(...params).c;
const limit = Math.max(1, Number(pageSize) || 20);
const offset = (Math.max(1, Number(page) || 1) - 1) * limit;
// 排序:收藏的账号排最前,收藏/未收藏各自内部按粉丝数降序(followers 支持 "万"/"亿" 格式解析为数值)
const followersExpr = `CASE WHEN h.followers LIKE '%亿%' THEN CAST(REPLACE(REPLACE(h.followers,'亿',''),'万','') AS REAL) * 100000000 WHEN h.followers LIKE '%万%' THEN CAST(REPLACE(h.followers,'万','') AS REAL) * 10000 ELSE CAST(h.followers AS REAL) END`;
const orderBySql = `h.favorite DESC, ${followersExpr} DESC, h.id ASC`;
// 附带视频数(account_videos 关联统计,供前端「视频数」列展示)与 AI脚本数(挂该账号视频的 ref 脚本 + video_id 空的原创脚本,同步 V1.0 账号列表「AI脚本」列口径)
const rows = d.prepare(`SELECT h.*,
(SELECT COUNT(*) FROM account_videos v WHERE v.account_id = h.id) AS video_count,
(SELECT COUNT(*) FROM rewrite_log r WHERE r.status='done' AND r.script_text IS NOT NULL AND r.script_text <> '' AND ((r.video_id > 0 AND r.video_id IN (SELECT id FROM account_videos v2 WHERE v2.account_id = h.id)) OR ((r.video_id IS NULL OR r.video_id = 0) AND r.account_id = h.id))) AS rewrite_count
FROM hot_accounts h ${whereSql} ORDER BY ${orderBySql} LIMIT ? OFFSET ?`)
.all(...params, limit, offset);
return { total, page: Math.max(1, Number(page) || 1), pageSize: limit, items: rows };
}
function listTracks() {
const d = initDb();
const dbTracks = d.prepare(`SELECT DISTINCT track FROM hot_accounts WHERE track IS NOT NULL AND track != '' ORDER BY track`).all().map((r) => r.track);
// 标准 28 个 + 库内非标值并集(保证旧数据可筛选)
const extras = dbTracks.filter((t) => !STANDARD_TRACKS.includes(t));
return STANDARD_TRACKS.concat(extras);
}
function queryAccountVideos(accountId, page = 1, pageSize = 20) {
const d = initDb();
const acc = d.prepare(`SELECT * FROM hot_accounts WHERE id=? AND (del_flag IS NULL OR del_flag <> 1)`).get(accountId);
if (!acc) throw new Error("账号不存在");
const total = d.prepare(`SELECT COUNT(*) AS c FROM account_videos WHERE account_id=?`).get(accountId).c;
const limit = Math.max(1, Math.min(Number(pageSize) || 20, 100));
const offset = (Math.max(1, Number(page) || 1) - 1) * limit;
const items = d.prepare(`
SELECT v.*,
(SELECT COUNT(*) FROM account_video_source s WHERE s.video_id = v.id OR s.aweme_id = v.aweme_id) AS has_source,
(SELECT COUNT(*) FROM account_video_analysis a WHERE a.video_id = v.id OR a.aweme_id = v.aweme_id) AS has_analysis
FROM account_videos v WHERE v.account_id=? ORDER BY v.id ASC LIMIT ? OFFSET ?
`).all(accountId, limit, offset);
return { account: acc, total, page: Math.max(1, Number(page) || 1), pageSize: limit, items };
}
export { ensureData, refreshData, queryAccounts, listTracks, queryAccountVideos };