18 KiB
QQBot BangDream 模块化文件结构重构方案
生成日期:2026-06-07
目标
基于 docs/qqbot-bangdream-tsugu-reference.md 的重构后现状,继续推进文件结构治理。本方案已落地为源码、测试、脚本和文档迁移记录。
目标是:
- 去掉
src/qqbot/plugins/bangDream/tsugu这一层目录,BangDream 内嵌能力直接归入src/qqbot/plugins/bangDream。 - 不再按
models、render-blocks、command-renderers、canvas这类技术能力横向堆文件。 - 改成按 BangDream 业务模块聚合文件,例如
song、card、event、gacha、player、cutoff。 - 对真正跨模块的大能力单独抽出,例如
hook、provider、policy、registry、theme。 - 控制单个文件夹文件数,避免再次出现
render-blocks=66、models=46的大桶目录。 - 保持现有 15 个在线命令兼容,迁移期间不改变用户命令文本、返回图片数量和线上 smoke 方式。
迁移前问题
迁移前 tsugu 源码 168 个 TS 文件分布如下:
| 目录 | 文件数 | 问题 |
|---|---|---|
render-blocks |
66 | 渲染规格、列表块、详情块、图表和资源 repository 混在一起,目录过大 |
models |
46 | 领域模型、resource repository、policy、protocol、主数据 store 混在一起 |
command-renderers |
19 | 以命令输出能力聚合,但和业务模块、模型、渲染块分离太远 |
canvas |
11 | 底层画布能力独立存在合理,但和 theme/asset manifest 分散 |
data-clients |
10 | provider 能力已经清晰,可以作为横切大能力保留 |
runtime |
9 | registry、hook、dictionary、config、asset manifest 混在同一目录 |
search |
6 | 跨模块搜索能力清晰,可以作为横切能力保留 |
calculations |
1 | 只有档线预测,适合归入 cutoff 或 calculation |
迁移前最大的问题不是层级太深,而是“按技术能力横切后文件越来越多”。例如歌曲相关文件散落在 models/song.ts、song-resource-repository.ts、command-renderers/song-*、render-blocks/list-song-*、render-blocks/song-chart-preview-*。后续维护查歌或谱面时需要跨多个目录来回跳。
设计原则
bangDream插件目录即模块根目录,不再保留tsugu子目录。- 业务域模块按用户和上游数据实体命名,不按技术能力命名。
- 横切能力只有在多个业务模块共享时才单独成目录。
- 每个业务模块内文件直接平铺,默认不再建
model/render/repository子目录。 - 单个目录建议不超过 20 个 TS 文件,超过时优先拆业务子模块,不回退到
models或render-blocks大桶。 - 文件名用职责后缀表达角色,例如
song.model.ts、song.repository.ts、song-search.renderer.ts、song-chart.layout.ts。 - Jest 测试仍放在
test/qqbot/plugins/bangDream,按源码目标模块同步改路径,不放回源码目录。 - 静态资源和静态配置保留
assets、static-config,不跟 TS 源码迁移节奏绑定。
实际落地结构
完整文件职责以 docs/qqbot-bangdream-tsugu-reference.md 为准,当前源码已经去掉 tsugu 子目录,实际顶层结构如下:
src/qqbot/plugins/bangDream/
application/ Nest 客户端、应用服务、渲染 facade、operation pipeline
registry/ operation key、handlerName、在线命令别名、冷却和说明
hook/ 生命周期 hook 和命令执行日志 hook
provider/ Bestdori、HHWX、静态修正、缓存、重试和 URL 解析
policy/ 服务器、国服活动时间、档线、抽卡等跨模块规则
theme/ Canvas 基础能力、布局 token、资源 manifest 和视觉主题
config/ 运行时配置、环境变量 key、服务器默认值和档线 tier
dictionary/ 默认字典和 API 字典加载
search/ 模糊搜索、关系表达式、搜索字典和实体列表匹配
song/ 查曲、谱面、随机曲、分数表、歌曲资源 repository
card/ 查卡、卡面、卡牌图标、稀有度、技能、综合力渲染
character/ 查角色、角色详情、角色列表和角色资源 repository
event/ 查活动、活动详情、试炼、活动时间和活动数据 repository
gacha/ 查卡池、抽卡模拟、卡池概率、Pick Up 和卡池资源 repository
player/ 查玩家、卡组、乐队等级、角色等级、难度完成和排名
cutoff/ 档线、全档线、近期档线、图表和预测算法
catalog/ 服务器、乐队、属性、区域道具、服装、称号、道具、技能、颜色
shared/ 协议常量、主数据、通用详情块、数据块、列表框架
commands/ QQBot 在线命令定义桥接
assets/ 本地图片、字体和静态视觉资源
static-config/ 搜索配置、昵称表、CN 修正表和玩家编号表
测试目录按源码模块同步拆分:
test/qqbot/plugins/bangDream/
application/ card/ catalog/ character/ cutoff/ dictionary/ event/
gacha/ hook/ player/ policy/ provider/ registry/ search/ shared/ song/ theme/
资源和静态配置同步从旧层级上移,Nest 构建复制路径已改为 qqbot/plugins/bangDream/assets/**/* 和 qqbot/plugins/bangDream/static-config/**/*。
横切能力说明
application
承接 Nest 边界和插件内部 facade:
bangdream-client.service.ts:替代当前根部qqbot-bangdream-client.service.ts,只暴露execute、checkHealth和少量明确 API。bangdream-application.service.ts:替代renderer/tsugu-application.service.ts,唯一应用入口。bangdream-renderer.facade.ts:替代renderer/qqbot-bangdream-renderer.service.ts,只做 handler 分发、输入归一化、字典解析和 CQ 输出。
registry
只放注册表和注册表类型:
- operation key
- handlerName
- 在线命令别名
- 冷却
- 命令说明
不放字典、hook、provider 或 renderer。
hook
只放生命周期 hook:
- hook context
- hook registry
- log hook
- 后续 metrics/output summary hook
业务决策不放 hook。会改变业务结果的逻辑放 policy 或模块 renderer。
provider
只放外部数据源和缓存下载能力:
- Bestdori provider
- HHWX tracker provider
- static patch provider
- cache path/policy/client
- retry/cache/timing decorator
任何模块需要外部 JSON、asset 或 tracker 数据,都通过 provider 或本模块 repository 调用,不直接拼完整 URL。
policy
只放跨模块业务规则:
- 服务器时区和优先级
- 国服活动时间预估
- 档线预测窗口和档位规则
- 抽卡概率和卡池过滤
单模块私有规则可以先留在模块文件,只有跨模块复用或会独立测试时才移动到 policy。
theme
承接视觉基础能力:
- 渲染主题 token
- 本地 asset manifest
- 通用 layout token
- Canvas 基础工具
不再使用 canvas 和 render-blocks 作为顶层大桶。各业务模块自己的布局文件放回模块内部,例如 song/song-chart-preview.layout.ts、card/card-stat.layout.ts。
业务模块说明
song
聚合查曲、歌曲详情、随机曲、分数表和谱面预览。现有散落来源:
models/song.tsmodels/song-repository.tsmodels/song-resource-repository.tscommand-renderers/song-*render-blocks/list-song*render-blocks/list-difficulty*render-blocks/song-chart-preview*
card
聚合查卡、卡牌详情、卡面、卡牌图标、插画、稀有度、综合力、技能文字和 SD 缩略图。现有散落来源:
models/card.tsmodels/card-repository.tsmodels/card-resource-repository.tscommand-renderers/card-*render-blocks/card-*render-blocks/list-card-*render-blocks/list-rarity*render-blocks/list-stat*render-blocks/skill-text*
character
聚合角色查询、角色详情、角色列表和玩家详情里的角色等级列表。现有散落来源:
models/character.tsmodels/character-resource-repository.tscommand-renderers/character-*render-blocks/list-character*
event
聚合查活动、活动详情、活动列表、试炼和活动时间展示。现有散落来源:
models/event.tsmodels/event-repository.tsmodels/event-data-repository.tsmodels/event-stage.tsmodels/event-stage-data-repository.tscommand-renderers/event-*render-blocks/event-stage*render-blocks/list-event-stage.tsrender-blocks/list-time*
gacha
聚合查卡池、抽卡模拟、卡池列表、概率和 pick up 展示。抽卡规则本身放 policy/gacha.policy.ts,模块只消费 policy。
player
聚合玩家详情、玩家数据 repository、玩家卡组、乐队等级、角色等级、难度完成情况和排名展示。
cutoff
聚合档线模型、前十榜、单档线、全档线、历史档线、档线图表、时间线图表和预测算法。档线规则本身放 policy/cutoff.policy.ts,模块只消费 policy。
catalog
聚合不直接对应一个 QQBot 命令、但多个模块共享的静态目录实体:
- 服务器
- 乐队
- 属性
- 区域道具
- 服装
- 称号
- 道具
- 技能
- 颜色
这些不再放进 models 大桶。
shared
只放真正跨多个模块的轻量工具、协议和通用渲染块:
- Bestdori 协议枚举和兼容常量
- 主数据 store/repository
- 模型工具函数
- 通用详情块、数据块、列表框架
- 图片栈工具
shared 不能成为新的大桶。超过 20 个 TS 文件时必须继续拆模块。
当前到目标迁移映射
| 当前路径 | 目标路径 |
|---|---|
tsugu/runtime/operation-registry.ts |
registry/operation-registry.ts |
tsugu/runtime/hook-registry.ts |
hook/hook-registry.ts、hook/log-hook.ts |
tsugu/runtime/config.ts |
config/runtime-config.ts |
tsugu/runtime/runtime-options.ts |
config/runtime-options.ts |
tsugu/runtime/default-dictionary.ts |
dictionary/default-dictionary.ts |
tsugu/runtime/dictionary-loader.ts |
dictionary/dictionary-loader.ts |
tsugu/runtime/asset-manifest.ts |
theme/asset-manifest.ts |
tsugu/data-clients/* |
provider/* |
tsugu/models/*-policy.ts |
policy/*.policy.ts |
tsugu/search/* |
search/* |
tsugu/canvas/* |
theme/canvas-*.ts |
tsugu/models/song*、command-renderers/song-*、render-blocks/*song*、render-blocks/*difficulty* |
song/* |
tsugu/models/card*、command-renderers/card-*、render-blocks/card-*、render-blocks/list-card-*、render-blocks/list-rarity*、render-blocks/list-stat*、render-blocks/skill-text* |
card/* |
tsugu/models/character*、command-renderers/character-*、render-blocks/list-character* |
character/* |
tsugu/models/event*、command-renderers/event-*、render-blocks/event-stage*、render-blocks/list-event-stage.ts、render-blocks/list-time* |
event/* |
tsugu/models/gacha*、command-renderers/gacha-*、render-blocks/gacha-*、render-blocks/list-gacha-* |
gacha/* |
tsugu/models/player*、command-renderers/player-detail.ts、render-blocks/list-player-*、render-blocks/deck-rank-* |
player/* |
tsugu/models/cutoff*、command-renderers/cutoff-*、render-blocks/*cutoff*、render-blocks/timeline-chart*、calculations/cutoff-predictor.ts |
cutoff/* |
tsugu/models/attribute*、band*、area-item*、costume*、degree*、item*、server*、skill*、color.ts |
catalog/* |
tsugu/render-blocks/detail-block*、data-block*、list-frame*、list-entity*、image-stack.ts |
shared/* |
命名规则
| 后缀 | 含义 | 示例 |
|---|---|---|
.model.ts |
领域实体和值对象 | song.model.ts |
.repository.ts |
主数据、资源或外部数据读取 | song-resource.repository.ts |
.renderer.ts |
生成图片或组合图片区块 | song-chart.renderer.ts |
.layout.ts |
视觉布局规格、纯布局计算 | song-chart-preview.layout.ts |
.policy.ts |
跨模块业务规则 | cutoff.policy.ts |
.provider.ts |
外部数据源实现 | bestdori.provider.ts |
.client.ts |
缓存、下载或外部客户端 | asset-cache.client.ts |
.registry.ts |
注册表 | operation-registry.ts |
.types.ts |
类型集合 | fuzzy-search.types.ts |
不再新增:
*-spec.ts作为源码布局文件后缀。后续使用.layout.ts,避免和 Jest*.spec.ts语义混淆。models/、render-blocks/、command-renderers/、data-clients/、runtime/、canvas/顶层目录。- 巨型
index.tsbarrel。只允许模块内少量显式 re-export,避免循环依赖。
迁移批次
Phase 0:结构迁移准备
目标:只建立可验证的迁移边界,不移动文件。
任务:
- 用脚本生成当前文件清单和 import 图。
- 冻结 15 个 operation 的 smoke 用例。
- 新增结构守卫脚本:禁止新增
tsugu/引用,统计目标目录文件数。 - 在文档里确认目录预算和命名规则。
验证:
- 文件清单 168 个 TS 文件全部有目标位置。
- operation 表 15/15 保持一致。
git diff --check、global-review。
Phase 1:横切能力先迁移
目标:先迁移不会改变业务输出的共享能力。
迁移:
runtime/operation-registry.ts->registry/operation-registry.tsruntime/hook-registry.ts->hook/*runtime/config.ts、runtime/runtime-options.ts->config/*runtime/default-dictionary.ts、runtime/dictionary-loader.ts->dictionary/*runtime/asset-manifest.ts、canvas/*、render-blocks/theme.ts、render-blocks/layout-spec.ts->theme/*data-clients/*->provider/*models/*-policy.ts->policy/*search/*->search/*
验证:
pnpm run typecheck- registry、hook、dictionary、provider、policy、search 相关 Jest
- 本地
/查谱面 136 expert和/ycx 100 50 cnsmoke
Phase 2:低耦合业务模块迁移
目标:迁移文件数量可控、依赖较清晰的模块。
建议顺序:
songcharactergachacutoff
验证:
- 每迁移一个模块跑对应 Jest 和一条图片 smoke。
- 不推远程,等本批模块全部完成后一次性提交、构建、线上验证。
Phase 3:高耦合业务模块迁移
目标:迁移涉及共享块较多的模块。
建议顺序:
cardeventplayercatalogshared
验证:
card:/查卡 472event:/查活动 50、/查试炼 310player:/查玩家 26591455 jpcatalog/shared:跑上面三类 smoke 复验
Phase 4:删除旧层级
目标:彻底去掉 tsugu 目录和旧大桶目录。
任务:
- 删除空的
tsugu目录。 - 全局替换 import。
- 更新
scripts/bangdream-render-smoke.ps1。 - 更新测试路径。
- 更新 reference 文档。
- 更新本方案迁移结果。
验收:
rg -n "plugins/bangDream/tsugu|\\.\\/tsugu|\\.\\.\\/tsugu" src test scripts docs
源码、测试、脚本不能再依赖 tsugu 路径。历史文档允许在“旧路径说明”中出现,但必须明确是旧路径。
Phase 5:完整发布验证
目标:只在批量迁移完成后做一次发布闭环。
必跑:
git diff --checkpnpm run typecheck- BangDream scoped ESLint
- BangDream 全量 Jest
pnpm run build- local smoke:查曲、查卡、查活动、查试炼、抽卡、档线、谱面
- global-review
- push 后 Jenkins/K8s rollout
- 线上
/qqbot/command/testsmoke 拉图、展示图片、查 operation 日志、清理远程临时目录
目录预算
| 目录 | 预算 | 超过后的处理 |
|---|---|---|
song |
16 | 拆出 song-chart 作为业务子模块,不回退到 render-blocks |
card |
20 | 拆出 card-art 或 card-detail 作为业务子模块 |
event |
18 | 拆出 event-stage 作为业务子模块 |
gacha |
14 | 保持单模块 |
player |
16 | 拆出 player-ranking 作为业务子模块 |
cutoff |
16 | 保持单模块 |
catalog |
20 | 拆出 catalog-degree 或 catalog-server |
shared |
20 | 能归业务模块就归业务模块,不能扩成新大桶 |
provider |
14 | cache client 可拆 provider-cache |
theme |
18 | canvas 基础可拆 theme-canvas |
预算不是硬编译规则,但用于 review:如果一个目录继续增长,先问“这是业务模块太大,还是又按能力堆成大桶了”。
风险点
| 风险 | 影响 | 控制方式 |
|---|---|---|
| import 路径大规模替换出错 | typecheck/Jest 失败 | 分批迁移,每批只移动一个边界或少数模块 |
| 循环依赖被 barrel 放大 | 运行时 undefined | 不建巨型 index.ts,保留显式 import |
| layout 文件改名导致测试误判 | Jest pattern 或 import 失败 | 源码布局后缀改 .layout.ts,测试仍 .spec.ts |
| smoke 卡进程 | 验证耗时不可控 | 继续使用 bounded smoke 脚本 |
| 线上命令误匹配 | preview selfId 未绑定命令 | 线上 smoke 必须按 operationKey 查 commandId |
| 大图 OOM 回归 | /查试炼 310 风险最高 |
保留分页拆图测试和线上 imageCount=5 验证 |
不做的事
- 不拆独立 Tsugu 服务。
- 不引入新仓库。
- 不为了目录漂亮改业务逻辑。
- 不把布局像素、资源文件名和协议字段放进字典表。
- 不把
shared、theme或catalog变成新的models/render-blocks大桶。 - 不在结构迁移之外改 BangDream 命令文本、返回图片数量或业务结果。
完成标准
本次闭环完成时按以下标准验收:
src/qqbot/plugins/bangDream/tsugu不再存在。models、render-blocks、command-renderers、data-clients、runtime、canvas不再作为 BangDream 顶层源码目录存在。- 15 个 operation key、在线命令别名和 handler 绑定保持一致。
- 每个业务模块能在一个目录内看到模型、repository、renderer 和 layout。
hook、provider、policy、registry、theme是明确横切能力,不包含业务命令编排。- 没有目录明显超过预算且未解释。
- 本地全量验证和线上 smoke 均通过。