SYSTEM ARCHITECTURE
这不是一个内容网站,而是一个数据产品:底层是演出档案库与预聚合视图,上面是节目单浏览与逐章数据分析。 本页回答三件事:数据从哪来、到页面怎么走;要改东西该动哪里;改完怎么验证没改坏。
这一节是全项目最容易被跳过、也最容易出问题的一段
/admin
056 触发器
pg_matviews 实际存在的视图逐个刷新。mv_program_stat 的一行是「某节目 · 某门类 · 某曲种」,不是「某节目」——
跨门类演过的节目在视图里是多行,直接取 cnt 拿到的是单行值而非合计。
2026-11-02 就因此让「节目热度」榜少算了(打灯谜 5004 而真实 5015)。
DB/refresh_mvs.sh
DB/server/app
web-src/src
*_name(归并后的规范名,统计一律用这个)与
*_raw(录入原样,仅用于搜索兜底)。按原始串分组会绕过归并,
是口径分裂的头号来源。
pg_matviews 读取本库实际有哪些视图,
逐个刷新。不要维护硬编码清单 —— 过去就是这么漏刷的。
| 层 | 负责 | 不负责(越界就是设计错误) |
|---|---|---|
| 前端 SPA | 呈现、路由、主题、自绘图表、展示层聚合 | 业务规则判断、直连数据库、口径计算 |
| 后端 API | 参数校验、鉴权、SQL 执行、结果整形 | 重计算(交给物化视图)、模板渲染 |
| 数据层 | 预聚合、维度归一、写入兜底 | 把口径规则写在视图注释里而不落成检查 |
| Nginx | TLS 终止、反向代理、静态缓存 | 任何业务路由(只有一条 SPA 回退规则) |
BEGIN; 和 COMMIT;。BEGIN 不写 COMMIT 时,psql 执行完关掉连接会
自动回滚未提交事务,数据全部丢失 —— 而输出里显示 INSERT 0 8、
(8 rows)、退出码 0,从头到尾没有任何错误信号。DB/check_migrations.py 在打包时拦。
IF NOT EXISTS / ON CONFLICT /
DROP TRIGGER IF EXISTS,重复执行不报错、不产生副作用。schema_migrations 记下脚本名与用途,
这是判断「哪些迁移跑过」的唯一依据 —— 漏登记就查不出「这个文件到底执行过没」。DB/schema.sql 开头是
DROP TABLE … CASCADE,禁止对生产库执行,仅供建测试库。
代码在 DB/server/app/,路由模块在 routers/
venue_name / troupe_name,
不要用原始串列(视图里根本没有裸的 venue / troupe,
引用会直接抛 column does not exist 让端点 500)。GROUP BY 与 ORDER BY 要对齐。
ORDER BY 里出现不在 GROUP BY 的列,Postgres 直接报错。column "x" does not exist),
不含 SQL 语句、连接串、路径、堆栈。介意暴露列名时在 .env 设
EXPOSE_ERROR_HINT=false 完全关掉。源码 web-src/src,构建产物 web-src/dist
这几页是手写 HTML,放在 web-src/public/,构建时原样拷进产物,
由后端直接返回(SPA 内没有它们的路由):
| 地址 | 给谁看 |
|---|---|
| /guide | 访客 —— 按「你想查什么」组织的使用指南 |
| /tree | 访客 / 维护者 —— 全站结构树:一级栏目一屏地图 + 自动同步的真实页面清单 + 逐层展开到最小控件(由 content/site-tree.json 生成;旧地址 /site-tree 仍可用) |
| /ops | 运营 —— 后台操作与异常处理 |
| /architecture | 开发者 —— 本页 |
| /admin | 运营后台(自包含,含登录) |
| /theme-preview | 洛基单主题的设计稿预览(深 / 浅两态,键名沿用旧版 loki / loki-dark)—— 不是全站 10 种外观的矩阵 |
| /offline.html | PWA 离线页 —— sw.js 里写死引用,刻意不做无后缀短路径 |
| /stats-classic.html | 旧版数据中心归档(页面顶部有「旧版」横幅);/stats 已被 SPA 占用,故不做短路径 |
| /person.html | 1K 跳转壳 —— 旧版独立人物页已废弃,一律 location.replace 到 SPA 的 /person/:code |
以上全部在 web-src/public/,构建时原样拷进产物。
同目录还有两个不属于页面的脚本:shell.js(同步主题 + 页脚补「数据覆盖至 X」)
与 umami.js(按 hostname 选统计站点 id,本地域名不统计)。
新增独立页记得在 <head> 末尾补一行 <script defer src="/umami.js"></script>,否则那一页的访问不会计入统计。
DB/server/app/main.py 的 PAGE_ROUTES
只登记了 /admin、/guide、/ops、/architecture、
/theme-preview、/tree、/site-tree(全站结构树的旧地址,
与 /tree 指向同一个文件);其余页面必须带 .html
(/offline.html 被 sw.js 写死,/stats-classic.html 回避 /stats,
/person.html 只是跳转壳 —— 三者都是刻意排除,不是漏配)。
写文档时把上表里带 .html 的地址省略后缀,就是一个点不动的死链 —— 这条由
scripts/check-docs-facts.mjs 自动盯着。
HTML5 history 模式。主要页面:/ 首页(数据驾驶舱)·
/schedule 节目单看板 · /discover 发现页(浏览与检索)·
/stats 数据中心 · /network 合作网络 ·
/yearbook/:year? 报告中心 · /person/:code 人物页 ·
/person/:code/partner/:other 两人合作页 ·
/program/:name 作品页 · /session/:id 场次页。
模块聚合页(2026-10-30 IA 落地 B 阶段):
/actors 演员 · /programs 节目 · /quzhongs 曲种 ·
/art/:cat 门类 · 另有剧场与演出队模块页、/metrics 指标口径表、
/about 数据说明。
/persons 是重定向,跳到
/actors(演员模块页);而 /programs 不是重定向,
它是独立的节目列表页。5 套主题 × 明暗 = 10 种外观(没有集中预览页:/theme-preview 只是洛基一个主题的设计稿)。
主题键的唯一出处是 src/composables/themeKeys.ts 的
THEMES,新增主题只改这一处。两个属性正交:
data-theme 管个性、data-mode 管亮度;
选择写入 localStorage,独立页用同一套约定跟随。
支持网址参数强制:/stats?theme=pop&dark=0。
这五条命令构成本项目的验证闭环,改后端必跑前两条
| 命令 | 回答什么问题 |
|---|---|
| python3 DB/api_sweep.py | 有没有端点报错?5xx 一律是真故障,能穿透前端把 500 显示成「不存在」的伪装 |
| python3 DB/check_invariants.py | 同一个概念在不同来源还对得上吗?不一致会直接告诉你差多少、差百分之几 |
| python3 DB/check_migrations.py | 新迁移会不会执行完就丢数据(BEGIN 无 COMMIT 静默回滚) |
| python3 DB/verify_dims.py | 维度归并还完整吗:原始串 / 编码 / 归并后三个数是否一致 |
| python3 DB/diag_program_500.py | 端点 500 时,直接打出真实 SQL 错误,并判断「代码部署了没」 |
web-src/tests/spa-smoke.spec.ts 把 19 个有快照的路由在 360 / 390 / 1440 三个宽度各开一遍,
断言「挂载成功、无横向溢出、没出现失败态、无控制台报错」——
在此之前所有宽度断言都只看静态页,SPA 一条都没有。web-src/tests/spa-failure.spec.ts 把接口全打成 500,要求每页都留下「这是一次失败」的证据
(重试入口或明确字样),不许静默显示 0 / 暂无。web-src/tests/utils/api-fixtures.ts 的快照 mock,不连真后端 ——
判据不能时红时绿。改页面跑 npm run check:smoke,全套仍是 npm run check。
core/constants.py 经 /api/meta 下发)。
bash scripts/pack_backend.sh vNN。
不要手敲 zip —— 手敲就是上面第 ⑥ 条那个坑。
打包前会跑迁移事务检查,打完会自检关键文件是否齐全。
/tmp/DB/server/app 与 web/dist 到
/www/backup/yqdy/deploy_时间戳/ —— 出问题就靠它回滚/www/wwwroot/yqdy_fastapi/(路径必须到这一层)DB/migrations/ 下根本没有这个文件,先跑迁移会报「找不到文件」。psql -h 127.0.0.1 -U postgres -d yqdy_db -f DB/migrations/NNN_xxx.sql/version.txt,
显示内容应与本地 web-src/dist/version.txt 完全一致。
不一致说明没传上去或传错目录。
| 要改的 | 去这里 |
|---|---|
| 某个数字算错了 | 先跑 check_invariants.py 定位是哪两个来源不一致,再顺着 SQL 找 |
| 接口 500 | diag_program_500.py(直接打真实错误,不要靠猜) |
| 新增/修改表字段 | 写迁移 → check_migrations.py → 执行 |
| 维度归并(剧场/演出队/曲种) | 对应维表与别名表的迁移;运行期由写入触发器兜底 |
| 页面界面 | web-src/src/views/;共用组件在 components/ |
| 新增主题 | composables/themeKeys.ts 一处,其余自动跟随 |
| 这三页对外文档 | web-src/public/{guide,architecture,ops}.html(手写,不在 Vue 里) |
| 节目单录入状态 | 已录完,没有补录进度或边界。状态常量在后端 DB/server/app/core/constants.py(DATA_ENTRY_STATUS),经 /api/meta 下发 data_entry_status;对外文案不得再出现任何补录进度说法 |
| 口径定义 | 数据库 metric_dict 表(文档 + 可执行 SQL + 恒等式) |
| 版本号 | 根目录 VERSION,构建时自动递增并注入 |