448 lines
18 KiB
Markdown
448 lines
18 KiB
Markdown
# 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-*`。后续维护查歌或谱面时需要跨多个目录来回跳。
|
||
|
||
## 设计原则
|
||
|
||
1. `bangDream` 插件目录即模块根目录,不再保留 `tsugu` 子目录。
|
||
2. 业务域模块按用户和上游数据实体命名,不按技术能力命名。
|
||
3. 横切能力只有在多个业务模块共享时才单独成目录。
|
||
4. 每个业务模块内文件直接平铺,默认不再建 `model/render/repository` 子目录。
|
||
5. 单个目录建议不超过 20 个 TS 文件,超过时优先拆业务子模块,不回退到 `models` 或 `render-blocks` 大桶。
|
||
6. 文件名用职责后缀表达角色,例如 `song.model.ts`、`song.repository.ts`、`song-search.renderer.ts`、`song-chart.layout.ts`。
|
||
7. Jest 测试仍放在 `test/qqbot/plugins/bangDream`,按源码目标模块同步改路径,不放回源码目录。
|
||
8. 静态资源和静态配置保留 `assets`、`static-config`,不跟 TS 源码迁移节奏绑定。
|
||
|
||
## 实际落地结构
|
||
|
||
完整文件职责以 `docs/qqbot-bangdream-tsugu-reference.md` 为准,当前源码已经去掉 `tsugu` 子目录,实际顶层结构如下:
|
||
|
||
```text
|
||
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 修正表和玩家编号表
|
||
```
|
||
|
||
测试目录按源码模块同步拆分:
|
||
|
||
```text
|
||
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.ts`
|
||
- `models/song-repository.ts`
|
||
- `models/song-resource-repository.ts`
|
||
- `command-renderers/song-*`
|
||
- `render-blocks/list-song*`
|
||
- `render-blocks/list-difficulty*`
|
||
- `render-blocks/song-chart-preview*`
|
||
|
||
### `card`
|
||
|
||
聚合查卡、卡牌详情、卡面、卡牌图标、插画、稀有度、综合力、技能文字和 SD 缩略图。现有散落来源:
|
||
|
||
- `models/card.ts`
|
||
- `models/card-repository.ts`
|
||
- `models/card-resource-repository.ts`
|
||
- `command-renderers/card-*`
|
||
- `render-blocks/card-*`
|
||
- `render-blocks/list-card-*`
|
||
- `render-blocks/list-rarity*`
|
||
- `render-blocks/list-stat*`
|
||
- `render-blocks/skill-text*`
|
||
|
||
### `character`
|
||
|
||
聚合角色查询、角色详情、角色列表和玩家详情里的角色等级列表。现有散落来源:
|
||
|
||
- `models/character.ts`
|
||
- `models/character-resource-repository.ts`
|
||
- `command-renderers/character-*`
|
||
- `render-blocks/list-character*`
|
||
|
||
### `event`
|
||
|
||
聚合查活动、活动详情、活动列表、试炼和活动时间展示。现有散落来源:
|
||
|
||
- `models/event.ts`
|
||
- `models/event-repository.ts`
|
||
- `models/event-data-repository.ts`
|
||
- `models/event-stage.ts`
|
||
- `models/event-stage-data-repository.ts`
|
||
- `command-renderers/event-*`
|
||
- `render-blocks/event-stage*`
|
||
- `render-blocks/list-event-stage.ts`
|
||
- `render-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.ts` barrel。只允许模块内少量显式 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.ts`
|
||
- `runtime/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 cn` smoke
|
||
|
||
### Phase 2:低耦合业务模块迁移
|
||
|
||
目标:迁移文件数量可控、依赖较清晰的模块。
|
||
|
||
建议顺序:
|
||
|
||
1. `song`
|
||
2. `character`
|
||
3. `gacha`
|
||
4. `cutoff`
|
||
|
||
验证:
|
||
|
||
- 每迁移一个模块跑对应 Jest 和一条图片 smoke。
|
||
- 不推远程,等本批模块全部完成后一次性提交、构建、线上验证。
|
||
|
||
### Phase 3:高耦合业务模块迁移
|
||
|
||
目标:迁移涉及共享块较多的模块。
|
||
|
||
建议顺序:
|
||
|
||
1. `card`
|
||
2. `event`
|
||
3. `player`
|
||
4. `catalog`
|
||
5. `shared`
|
||
|
||
验证:
|
||
|
||
- `card`:`/查卡 472`
|
||
- `event`:`/查活动 50`、`/查试炼 310`
|
||
- `player`:`/查玩家 26591455 jp`
|
||
- `catalog/shared`:跑上面三类 smoke 复验
|
||
|
||
### Phase 4:删除旧层级
|
||
|
||
目标:彻底去掉 `tsugu` 目录和旧大桶目录。
|
||
|
||
任务:
|
||
|
||
- 删除空的 `tsugu` 目录。
|
||
- 全局替换 import。
|
||
- 更新 `scripts/bangdream-render-smoke.ps1`。
|
||
- 更新测试路径。
|
||
- 更新 reference 文档。
|
||
- 更新本方案迁移结果。
|
||
|
||
验收:
|
||
|
||
```powershell
|
||
rg -n "plugins/bangDream/tsugu|\\.\\/tsugu|\\.\\.\\/tsugu" src test scripts docs
|
||
```
|
||
|
||
源码、测试、脚本不能再依赖 `tsugu` 路径。历史文档允许在“旧路径说明”中出现,但必须明确是旧路径。
|
||
|
||
### Phase 5:完整发布验证
|
||
|
||
目标:只在批量迁移完成后做一次发布闭环。
|
||
|
||
必跑:
|
||
|
||
- `git diff --check`
|
||
- `pnpm run typecheck`
|
||
- BangDream scoped ESLint
|
||
- BangDream 全量 Jest
|
||
- `pnpm run build`
|
||
- local smoke:查曲、查卡、查活动、查试炼、抽卡、档线、谱面
|
||
- global-review
|
||
- push 后 Jenkins/K8s rollout
|
||
- 线上 `/qqbot/command/test` smoke 拉图、展示图片、查 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 均通过。
|