kt-template-online-api/docs/qqbot-bangdream-tsugu-global-refactor-plan.md

31 KiB
Raw Blame History

QQBot BangDream Tsugu 全局重构方案

生成日期2026-06-06

目标

基于 docs/qqbot-bangdream-tsugu-reference.md 的函数与变量清单,对 Tsugu 内嵌能力做一次全局重构设计。目标不是机械套用所有设计模式,而是把当前确实分散、硬编码、难验证的部分收口成稳定边界:

  • 去掉非必要硬编码:用户可感知文案、别名、映射、阈值、数据源、渲染主题、布局规格和命令路由不能继续散落在业务函数里。
  • 保留必要硬编码Bestdori 协议枚举、资源文件名约定、纯算法常量和极少量稳定模型字段可以保留在代码中,但必须有命名和归属。
  • 让能力可插拔:命令、数据源、搜索规则、渲染流程、输出后处理和日志观测都通过 registry/pipeline/hook 连接。
  • 让重构可验证:每个阶段保留现有命令行为,先补边界测试,再迁移实现。

当前事实

  • Tsugu 源码目录:src/qqbot/plugins/bangDream/tsugu
  • TS 文件92
  • 函数节点481其中稳定函数 410匿名/内联回调 71
  • 源码 JSDoc稳定函数 410/410 已覆盖
  • 变量声明1896
  • class/interface/type 字段716
  • 静态配置:static-config 下 9 个文件包含昵称、CN 修正、模糊搜索、玩家编号等数据
  • 现有硬编码集中点:models/bangdream-constants.ts 已承接服务器、难度、活动、卡牌、卡池、档位、国服预测等一部分常量

硬编码候选扫描

这次只把扫描结果作为重构入口,不直接等价为“全部挪走”。布局坐标、协议路径、图片资源名和业务字典的处理方式不同。

文件 字符串 数字 模板字符串 主要问题
render-blocks/song-chart-preview.ts 112 158 7 谱面预览布局、颜色、轨道、音符规格混在主流程内
models/bangdream-constants.ts 110 78 0 业务枚举已集中,但仍混合用户文案、协议值、配置阈值
models/cutoff.ts 29 115 5 档线数据源、预测规则和请求策略耦合
render-blocks/list-time.ts 29 108 0 时间格式、服务器时区、国服预估规则耦合
render-blocks/card-art.ts 41 87 3 卡牌布局尺寸、素材路径、颜色散落
render-blocks/detail-blocks.ts 13 91 10 通用详情块已收口,但尺寸/token 仍分散
canvas/text.ts 32 67 2 字体、字号、换行、画布兜底尺寸需要统一 token
models/player.ts 37 56 2 玩家 API、模式参数、头像/称号资源规则耦合
models/card.ts 53 32 5 卡面资源 URL、类型文案、发布逻辑耦合
render-blocks/list-frame.ts 13 77 0 通用列表布局已有基础,但宽高、间距、颜色仍散落

硬编码分类

类型 示例 目标归属 处理原则
协议常量 Server.jp = 0、Bestdori API 字段名 enum/types 保留代码内,但统一命名,不进入数据库
可运营映射 服务器别名、难度别名、命令别名、用户可见文案 字典表或静态配置加载器 优先可维护,允许缓存,不在渲染函数里写死
数据源地址 https://bestdori.comhttps://hhwx.org runtime options + provider registry 支持 env 覆盖和数据源策略
资源路径 assets/Card/star.png、谱面 note 图片名 asset manifest 保留文件约定,但用 manifest 管理,不散写路径
视觉 token 颜色、字体、间距、圆角、默认宽度 render theme 代码内 token 文件或主题 provider不进字典表
布局规格 80030、卡片宽高、谱面轨道尺寸 layout spec 从主流程抽出为命名规格,支持单元测试
算法阈值 缓存过期、重试次数、数据源切换阈值 runtime options 默认值代码内,生产可 env 配置
上游修正数据 cards-cn-fix.jsonskills-cn-fix.json data patch repository 保留静态文件或转迁移数据,统一读取入口

当前扁平结构

保持当前插件目录不引入新顶层仓库或独立服务。Tsugu TS 源码统一压到 tsugu/<明确分组>/<文件>.ts,最大两级;资源目录 assetsstatic-config 不受 TS 源码层级限制。

src/qqbot/plugins/bangDream/
  commands/
    bangdream-command.definitions.ts
  renderer/
    qqbot-bangdream-renderer.service.ts
  tsugu/
    calculations/
    canvas/
    command-renderers/
    data-clients/
    models/
    render-blocks/
    runtime/
    search/

后续新增 registry、hook、provider、policy、spec、theme 或 manifest也只能落在这些二级目录下例如 runtime/operation-registry.tsruntime/hook-registry.tsdata-clients/bestdori-provider.tsmodels/server-policy.tsrender-blocks/theme.ts

设计模式和落点

模式/机制 当前问题 落地方式 首批目标
Strategy execute 的 switch 把 15 个 operation 堆在一个 service 每个 operation 暴露 execute(input, context) 歌曲、活动、档线、卡池分组拆出
Registry 命令定义、operation key、在线命令 SQL 需要保持一致 BANGDREAM_OPERATION_REGISTRY 单一数据源生成插件能力和 SQL 校验 替代手写 switch 与分散定义
Pipeline 解析、查数、渲染、输出、错误处理流程重复 parse -> resolve -> render -> output 标准步骤 查曲/查卡/查活动先接入
Hook 日志、耗时、图片压缩、数据源 fallback 不应侵入业务函数 lifecycle hookbeforeParseafterResolvebeforeRenderafterOutputonError 日志、metrics、fallback、CQ 输出摘要
Adapter Bestdori/HHWX/本地静态修正接口风格不同 provider adapter 统一 getJsongetAssetgetTracker 档线与素材下载
Repository mainAPI[...] 到处直接取值,难 mock SongRepositoryCardRepositoryEventRepository 包 mainAPI 和 patch 搜索与详情渲染
Specification 模糊搜索字段匹配和关系表达式容易继续膨胀 每个条件实现 matches(target) _number_relationStr_all、动态字段
Factory 输出图片和实体匹配器已有工厂雏形,仍不统一 createEntityMatchercreateImageOutputcreateOperation 保留现有柯里化方向
Builder Canvas detail block 由数组手工 push顺序难读 DetailBlockBuilder 组合标题、字段、图片区块 卡牌/活动详情
Template Method 列表页、详情页、档线页流程相似 基类或高阶函数封装“准备数据 + 画区块 + 输出” 列表类页面优先
Decorator 缓存、重试、耗时统计混在下载函数 withCachewithRetrywithTiming 包 provider downloadFilegetJsonAndSave
Facade Nest service 不应知道 Tsugu 内部所有函数 TsuguApplicationService 作为唯一入口 QqbotBangDreamRendererService 变薄
Policy 国服预估、服务器优先级、档位、时区是规则,不是工具函数 BangDreamServerPolicyCutoffPolicyCnEventEstimatePolicy render-blocks/list-time.tsmodels/cutoff.ts

场景到模式选择

后续实现时按场景选择封装,而不是为了使用模式而使用模式。每个改动点只选择一个主模式,确实有横切能力时再叠加 hook/decorator。

场景 判断条件 主模式 封装形态 不使用时机
增加或维护在线命令 operation key、命令名、描述、SQL、执行函数必须一致 Registry + Strategy TsuguOperationDefinition 对象数组handler 用函数或小对象 只有一个命令且不会扩展时,不建 registry
多个命令共用同一执行流程 都经历解析参数、解析实体、渲染图片、输出 CQ、错误归一 Pipeline TsuguOperationPipeline.run(definition, input) 只有两处简单重复时,先抽公共函数
同类命令只有中间渲染不同 查曲、查卡、查活动都是“搜索或 ID 详情” Template Method 高阶函数 createSearchDetailOperation(options) 如果各命令分支差异大,不强行套模板
外部数据源格式不同 Bestdori、HHWX、本地 patch、缓存文件接口不一致 Adapter BestdoriProviderHhwxTrackerProviderStaticPatchProvider 实现统一接口 只是同一个 API 的不同 URL不建 adapter
访问主数据或静态修正 mainAPI[...]*-fix.json、昵称表需要统一读取和 mock Repository SongRepositoryCardRepositoryEventRepository 封装数据入口 纯值对象内部字段读取不绕 repository
判断某个对象是否符合规则 搜索关系式、发布服务器、活动状态、卡池类型过滤 Specification matches(target) 的小规则对象或纯函数集合 只有一行判断且不会复用时保留内联
业务策略会随服务器或环境变化 国服预估、服务器优先级、档位、时区、卡池选择 Policy BangDreamServerPolicyCutoffPolicyGachaPolicy 纯格式化函数不放 policy
创建复杂图片区块 详情页反复 push 标题、字段、图片区、spacer Builder DetailBlockBuilder.addTitle().addField().addImage() 简单两三个 canvas 拼接不建 builder
创建同类对象或输出收尾 实体 matcher、图片输出、operation 定义反复出现 Factory createEntityMatchercreateImageOutputcreateOperation 构造参数很少且只用一次时不用 factory
给数据请求加缓存、重试、耗时 下载函数和 provider 都需要同样外层能力 Decorator withCache(provider)withRetry(fn)withTiming(fn) 业务分支逻辑不能塞进 decorator
记录日志、指标、fallback、输出摘要 横切多个 operation且不改变主业务结果 Hook beforeParse/afterResolve/beforeRender/afterOutput/onError 需要决定业务结果时不用 hook交给 policy/strategy
Nest 边界需要变薄 controller/plugin service 不应 import 大量 Tsugu 内部函数 Facade TsuguApplicationService.execute(operationKey, input) Tsugu 内部模块之间不互相套 facade
用户可维护的映射或文案 别名、标签、命令名、用户可见中文 Dictionary Loader 启动加载字典,内存缓存,默认值兜底 协议字段、布局像素、资源文件名不进字典
颜色、尺寸、字体、资源路径 渲染 token 散落,改视觉需要扫很多文件 Theme/Spec/Manifest BangDreamThemelayoutSpecassetManifest 单个局部坐标且无复用价值时保留命名常量
纯计算和格式化 无外部依赖、无状态、无扩展点 Pure Function 具名函数 + 单测 不为了模式套 class

选择顺序

  1. 先判断它是不是用户可维护数据;是则走字典或 runtime options。
  2. 再判断它是不是外部系统边界;是则走 provider/adapter/repository。
  3. 再判断它是不是业务规则;是则走 policy/specification。
  4. 再判断它是不是重复流程;是则走 pipeline/template/factory。
  5. 再判断它是不是横切能力;是则走 hook/decorator。
  6. 如果只是一次性局部逻辑,保留具名纯函数,不新增模式封装。

封装粒度默认用函数和对象配置。只有需要 Nest 注入、运行态状态、缓存生命周期或多实现接口时,才引入 class。

不建议使用的模式Singleton、Abstract Factory、Event Sourcing、复杂插件热加载。当前问题不需要它们强行使用会增加维护成本。

Hook 设计

Hook 只做横切能力,不承载业务决策。

export interface TsuguHookContext {
  operationKey: QqbotBangDreamOperationKey;
  input: QqbotBangDreamCommandInput;
  options: TsuguRenderOptions;
  query?: string;
  entityIds?: number[];
  imageCount?: number;
  startedAt: number;
}

export interface TsuguHook {
  name: string;
  order?: number;
  beforeParse?(context: TsuguHookContext): void | Promise<void>;
  afterResolve?(context: TsuguHookContext): void | Promise<void>;
  beforeRender?(context: TsuguHookContext): void | Promise<void>;
  afterOutput?(context: TsuguHookContext): void | Promise<void>;
  onError?(context: TsuguHookContext, error: unknown): void | Promise<void>;
}

首批内置 hook

  • TsuguLogHook:记录 operation、query、耗时、图片数量、错误字符串。
  • TsuguDataSourceHook记录数据源命中、fallback 次数和切换原因。
  • TsuguOutputHook:统一 CQ 图片输出摘要,避免超长消息污染发送日志。
  • TsuguDictionaryHook:启动时加载字典,运行时只读缓存,避免每次命令查 DB。

字典与配置收口

字典表适合承接“运营会改、用户会看到、别名会增长”的内容,不适合承接协议字段和布局像素。

建议字典编码:

字典编码 内容 初始来源
BANGDREAM_SERVER_ALIAS 服务器别名国服、日服、cn、jp 等 BANGDREAM_SERVER_ALIASES
BANGDREAM_DIFFICULTY_ALIAS 难度别名expert、ex、专家等 BANGDREAM_DIFFICULTY_ALIASES
BANGDREAM_COMMAND_ALIAS /bd/邦邦 下具体命令别名 BANGDREAM_OPERATION_REGISTRY 和在线命令
BANGDREAM_ENTITY_NICKNAME 歌曲、卡牌、活动昵称 nickname-*.xlsxsearch/fuzzy-search-settings.json
BANGDREAM_LABEL 活动类型、卡牌类型、卡池类型、歌曲标签中文名 bangdream.enum.ts 中 label 映射

保留代码配置:

  • BangDreamServerCodeBangDreamServerIdBangDreamDifficultyId
  • Bestdori API 返回字段名
  • BANGDREAM_ITEM_TYPE_PREFIXES 这类资源协议前缀,除非后续确实需要后台维护
  • BANGDREAM_TIER_LIST_BY_SERVER 可先保留 enum确认运营维护需求后再迁字典

运行时 env/options

  • BANGDREAM_TSUGU_CACHE_ROOT
  • BANGDREAM_TSUGU_COMPRESS
  • BANGDREAM_TSUGU_USE_EASY_BG
  • BANGDREAM_TSUGU_DISPLAYED_SERVERS
  • BANGDREAM_TSUGU_MAIN_SERVER
  • BANGDREAM_TSUGU_BESTDORI_BASE_URL
  • BANGDREAM_TSUGU_HHWX_BASE_URL
  • BANGDREAM_TSUGU_REQUEST_TIMEOUT_MS
  • BANGDREAM_TSUGU_RETRY_COUNT

分阶段执行

Phase 0冻结行为和测试基线

目标:重构前先确认现有能力的稳定输出,不再靠线上报错倒逼。

任务:

  • 为 15 个 operation 建立 registry 一致性测试。
  • 固定查曲、查活动、查试炼、档线、抽卡模拟的 smoke 用例。
  • fuzzySearchcreateTsuguEntityMatcher、服务器/难度解析补全边界测试。
  • 保存关键图片命令的“图片数量 + 非空 Buffer + 尺寸范围”断言,不做像素级强绑定。

验证:

  • pnpm exec jest --runInBand test/qqbot/plugins/bangDream/**/*.spec.ts
  • pnpm run typecheck
  • BangDream scoped ESLint

Phase 1命令和 operation registry

目标:去掉 QqbotBangDreamRendererService.execute 的 switch命令能力以 registry 为单一来源。

任务:

  • 新增 TsuguOperationDefinitionkeynamedescriptionaliasesinputSchemaexecute
  • 将现有 BANGDREAM_OPERATION_DEFS 与执行器合并为 BANGDREAM_OPERATION_REGISTRY
  • SQL 初始化和测试改从 registry 校验,避免在线命令漏配或脏数据。
  • Renderer service 只保留 Nest 注入、健康检查和调用 facade。

验收:

  • 15 个 operation key 不变。
  • 在线命令 SQL 与 registry 一一对应。
  • /bd/邦邦 所有当前命令行为不变。

当前进度:

  • 已新增 runtime/operation-registry.ts,收口 15 个 operation 的 key、name、description、handlerName、在线命令别名、冷却和备注。
  • QqbotBangDreamRendererService.execute 已改为按 registry 查找 handler不再维护 operation switch。
  • QqbotBangDreamPluginService 和 SQL 一致性测试已改为直接读取 registry。

Phase 2配置、字典和协议常量分层

目标:把 bangdream.enum.ts 拆成协议常量、用户字典、运行策略。

任务:

  • 建立 models/bangdream-protocol.ts:服务器 ID、难度 ID、Bestdori 字段和资源协议。
  • 建立 runtime/default-dictionary.ts:默认 label/alias。
  • 建立 runtime/dictionary-loader.ts:优先读取 API 字典缓存,失败回落默认字典。
  • renderer 的服务器/难度解析改走 loader不直接 import alias object。
  • 为字典缓存加启动加载和按需刷新入口,避免每条命令查 DB。

验收:

  • 删除 renderer 对 BANGDREAM_SERVER_ALIASESBANGDREAM_DIFFICULTY_ALIASES 的直接依赖。
  • 本地无 DB 时仍能使用默认字典。
  • 字典项更新后可通过刷新入口生效。

当前进度:

  • 已新增 models/bangdream-protocol.ts 收口服务器 ID、难度 ID、Bestdori API path、资源协议前缀和 API 枚举值。
  • 已新增 runtime/default-dictionary.ts 收口默认服务器/难度别名和用户可见 labelmodels/bangdream-constants.ts 改为兼容 re-export。
  • 已新增 runtime/runtime-options.ts 收口 env key、默认服务器策略、档线策略和布尔/list 解析工具。
  • 已新增 runtime/dictionary-loader.ts,启动或按需刷新时合并 API 字典项,失败时回落默认字典,不在每条命令里查 DB。
  • QqbotBangDreamRendererService 已移除对 server/difficulty alias object 的直接依赖,服务器、难度和 displayed server 解析统一走 loader。
  • sql/qqbot-init.sql 已补 BANGDREAM_SERVER_ALIASBANGDREAM_DIFFICULTY_ALIAS 默认字典项,后台可直接维护别名。

Phase 3数据源 provider 和 repository

目标数据下载、Bestdori、HHWX、静态修正不再散落在 models 中。

任务:

  • 定义 BangDreamDataProvidergetJsongetAssetgetTracker
  • data-clients 实现 bestdori-provider.tshhwx-tracker-provider.tsstatic-patch-provider.ts
  • withCachewithRetrywithTiming 包装 provider不在业务函数里写重试循环。
  • models 建立 repository 文件:song-repository.tscard-repository.tsevent-repository.tsgacha-repository.tsplayer-repository.ts
  • domain model 只承载字段和轻量行为,不直接拼 URL。

验收:

  • models/song.tsmodels/card.tsmodels/event.ts 中 Bestdori URL 拼接明显减少。
  • provider 可单测 mock。
  • 档线数据源 fallback 有日志 evidence。

当前进度:

  • 已新增 data-clients/data-provider.ts,定义 BangDreamDataProvider、JSON/Asset/Tracker 请求参数和 provider URL 解析。
  • 已新增 data-clients/provider-decorators.ts,提供 withCachewithRetrywithTiming,缓存默认值、重试和耗时日志不再要求业务函数内手写循环。
  • 已新增 data-clients/bestdori-provider.tsdata-clients/hhwx-tracker-provider.tsdata-clients/static-patch-provider.ts主数据、素材、Tracker 和本地静态修正分别走 provider。
  • models/main-data-store.ts 已改为通过 Bestdori provider 加载主数据,通过 static patch provider 读取 cards-cn-fix.jsonskills-cn-fix.jsonarea-item-fix.jsonnickname-song.xlsx
  • 已新增 models/main-data-repository.tssong-repository.tscard-repository.tsevent-repository.tsgacha-repository.tsplayer-repository.ts
  • command-renderers/song-list.tsevent-list.tscard-list.tsgacha-detail.tsplayer-detail.tscutoff-detail.ts 已开始改走 repository 创建模型或读取主数据。
  • models/song.tscard.tsevent.tsgacha.tsplayer.ts 的 Bestdori API/素材请求已开始改走 Bestdori providermodels/cutoff.ts 的 Bestdori/HHWX Tracker fallback 已改走 provider并在 fallback 时输出数据源切换日志。
  • 已新增 models/event-data-repository.ts 收口活动详情、横幅、背景、轮播、Logo、奖励表情和装饰素材请求models/event.ts 不再直接依赖 Bestdori provider 或 loadImage
  • drawEventDetail 的活动真实底图改为轻量图片背景,避免长图走模糊三角纹理导致超时;活动底图由 bg_eventtop.pngtrim_eventtop.png 合成,避免只渲染模糊背景底图;活动搜索默认遵循 BANGDREAM_TSUGU_USE_EASY_BG=false,不再强制简易背景。
  • 已接入 BANGDREAM_TSUGU_REQUEST_TIMEOUT_MSBANGDREAM_TSUGU_MAIN_DATA_READY_TIMEOUT_MSHTTP 请求和主数据首次 ready 等待都有硬超时BangDream 命令执行前会等待关键主数据集合可用。
  • 已新增 scripts/bangdream-render-smoke.ps1BangDream 图片 smoke 通过父进程限时、子进程 PID 清理、生成后显式退出,避免本地调试命令卡住进程。
  • 已新增 test/qqbot/plugins/bangDream/tsugu/data-provider.spec.ts,覆盖 provider URL 解析、Bestdori/HHWX mock 数据源、retry/cache wrapper。
  • 本地图片烟测已生成查歌 136、查活动 50 简易背景和真实活动背景图片,证明 provider/repository 第一段迁移后仍能输出非空图片。

Phase 4搜索 specification 和 matcher

目标:模糊搜索规则可以新增/禁用,不再在一个文件里继续堆 if。

任务:

  • 将 keyword parse、config hit、relation match、field match 拆成 specification。
  • 建立 FuzzySearchRuleRegistry,每条规则只关心 canHandlematch
  • search/fuzzy-search-settings.json 与字典昵称统一成 SearchDictionaryRepository
  • entity-list-matcher.ts 只依赖 matcher 接口,不直接知道配置来源。

验收:

  • search/fuzzy-search.ts 文件长度和分支继续下降。
  • 新增昵称或难度别名无需改 matcher 代码。
  • 旧 search/fuzzy-search 测试全部通过。

当前进度:

  • 已新增 search/fuzzy-search-types.tssearch/search-dictionary-repository.tssearch/relation-matcher.tssearch/fuzzy-search-rule-registry.ts,把搜索类型、搜索字典读取、关系表达式和规则注册表从 fuzzy-search.ts 中拆出。
  • fuzzy-search.ts 保留关键词拆分、结果校验和目标匹配的兼容入口,关键词解析改由 FuzzySearchRuleRegistry 按 number、level、relation、config、fallback 顺序处理,文件长度从 516 行降到 281 行。
  • entity-list-matcher.ts 改为只依赖搜索结果类型和关系匹配器,并支持延迟读取 source,修复 mainAPI 异步加载前捕获空数据源导致查歌/查卡/查活动搜索不到的问题。
  • song-list.tscard-list.tsevent-list.ts 的实体列表匹配器已改为运行时读取 repository source查歌 夏祭り 和查活动 summer 本地图片 smoke 均生成非空图片。

Phase 5渲染 theme、layout spec 和 section builder

目标Canvas 绘制仍保留函数式性能,但颜色、尺寸、字体和区块顺序收口。

任务:

  • 建立 render-blocks/theme.ts:颜色、字体、默认宽度、列表间距、背景配置。
  • 建立 render-blocks/*-spec.ts:歌曲列表、活动列表、卡牌详情、谱面预览规格。
  • render-blocks/song-chart-preview.ts 先拆 token再拆音符策略不直接改视觉结果。
  • DetailBlockBuilder 统一 title、data block、image block、spacer。
  • 资源路径由 asset-manifest.ts 管理,统一 asset('Card.star') 形式。

验收:

  • render-blocks/song-chart-preview.ts 字面量数量显著下降,主函数仍低复杂度。
  • 关键图片命令输出非空,尺寸在既有范围。
  • 视觉 token 修改只改 theme/spec 文件。

当前进度:

  • 已新增 render-blocks/theme.ts,先收口公共文字颜色、分割线颜色、简易背景色、图表背景/文字色和默认字体名。
  • 已新增 render-blocks/layout-spec.ts,统一横向/纵向虚线分割规格,歌曲列表、活动列表、活动详情和通用列表框架不再重复维护分割线宽高/颜色。
  • 已新增 runtime/asset-manifest.ts,收口本地 BGCardSkillSongChart、字体和标题资源路径;canvas/text.tscanvas/rect.tscanvas/output.tscanvas/background.tscard-art.tslist-rarity.tsskill-text.tstitle.tssong-chart-preview.ts 已改走 manifest。
  • 已生成查曲、查活动和查谱面 smoke 图片,验证 theme/spec/manifest 第一段迁移后本地图片输出非空,谱面预览资源路径未断。
  • 线上 /qqbot/command/test smoke 已固化为先按 operationKey 查询启用命令、传 commandId,并保留完整命令文本;避免默认 preview selfId 未绑定命令时误报“未匹配到命令”。

Phase 6策略 policy 和时间/档线规则

目标:服务器优先级、国服预估、档位、时区和活动状态从工具函数变成可测试规则。

任务:

  • BangDreamServerPolicy:默认展示服、主服务器、优先级、时区。
  • CnEventEstimatePolicy:国服预估起始活动、跳过活动、无邦日。
  • CutoffPolicy:档位列表、预测线规则、近期活动选择规则。
  • GachaPolicy:生日卡池过滤、抽卡次数默认值、概率选择。

验收:

  • render-blocks/list-time.ts 不再持有国服预估常量。
  • models/cutoff.ts 的请求、预测、展示规则分层。
  • policy 均有纯函数测试。

当前进度:

  • 已新增 models/server-policy.ts,收口服务器 UTC 偏移、服务器时区 Date 转换、时间戳规范化和档线日增 checkpoint 服务器规则;render-blocks/list-time.ts 保留兼容入口但不再维护时区 switch。
  • 已新增 models/cn-event-estimate-policy.ts,把国服预估起始活动、跳过活动和无邦日规则从 list-time.ts 移出,提供 calculateCnEventEstimateStartAt 纯函数测试入口;list-time.tsevent.ts 改走 policy不再让 Event 排序依赖渲染层预估实现。
  • 已新增 models/cutoff-policy.ts,收口档位列表、档位支持判断、国服缺失活动时间预估、档线活动状态、预测窗口、日增天数和最近同类型活动选择规则;models/cutoff.tscutoff-all.ts 已接入。
  • 已新增 test/qqbot/plugins/bangDream/tsugu/policy.spec.ts,覆盖服务器时区、国服预估纯计算、档位判断、状态/预测窗口、日增 checkpoint、活动天数和最近活动选择policy 单测 mock main-data-store,避免纯测试启动主数据定时器。
  • 已生成 phase6-policy-event-50.jpgphase6-policy-cutoff-detail-100-50-cn.jpgphase6-policy-cutoff-recent-100-50-cn.jpg,验证查活动、单档线和历史档线图片输出非空。

Phase 7Facade 和 hook 接入

目标Nest 边界稳定Tsugu 内部可观测。

任务:

  • TsuguApplicationService 作为唯一执行入口。
  • TsuguOperationPipeline 串起 registry、parser、resolver、renderer、output。
  • TsuguHookRegistry 注入日志、metrics、数据源 fallback、输出摘要 hook。
  • 错误统一成字符串,保持前端/QQBot 链路可解析。

验收:

  • QqbotBangDreamRendererService 只保留薄 facade 或被替代。
  • 每次命令执行能记录 operation、耗时、图片数、错误。
  • 线上日志能定位 BangDream 命令的具体失败阶段。

当前进度:

  • 已新增 tsugu/runtime/hook-registry.ts,定义 TsuguHookContextTsuguHookTsuguHookRegistry 和默认 TsuguLogHook,命令执行日志包含 operationstagehandlerqueryimageCountdurationMs 和错误字符串。
  • 已新增 renderer/tsugu-application.service.ts 作为 BangDream Tsugu 应用入口,统一执行主数据 ready 等待、operation registry 查找、handler 调度、hook 触发和错误字符串化。
  • QqbotBangDreamClientService 已改为注入 TsuguApplicationService,不再直接调用 rendererQqbotBangDreamRendererService 移除 operation key 调度,只保留字典刷新、健康检查、渲染选项解析和 15 个具体 handler。
  • scripts/bangdream-render-smoke.ps1 已切到 TsuguApplicationService,本地 smoke 继续走真实应用入口,避免 facade 改造后验证脚本回退到旧链路。
  • 已新增 hook-registry.spec.tstsugu-application.service.spec.ts,覆盖 hook 顺序、错误 hook、应用入口执行、字典刷新和错误字符串化本地 Phase 7 smoke 已生成 phase7-hook-event-50.jpgphase7-hook-cutoff-detail-100-50-cn.jpgphase7-hook-cutoff-recent-100-50-cn.jpg

文件迁移优先级

优先级 文件/目录 原因
P0 renderer/qqbot-bangdream-renderer.service.ts 当前 operation 调度中心,影响在线命令一致性
P0 commands/qqbot-bangdream-command.definitions.ts 命令定义和 SQL 校验源头
P0 search/fuzzy-search.tssearch/entity-list-matcher.ts 搜索规则和查实体能力最容易继续膨胀
P1 runtime/config.tsmodels/bangdream-constants.ts 硬编码分层入口
P1 data-clients/asset-cache-client.tsdata-clients/file-cache-client.tsdata-clients/api-cache-client.ts 缓存/重试/数据源切换横切能力
P1 models/song.tsmodels/card.tsmodels/event.tsmodels/gacha.ts URL 拼接、mainAPI、patch、模型行为耦合
P2 render-blocks/song-chart-preview.ts 字面量最高,但视觉风险高,必须测试保护后再拆
P2 render-blocks/list-frame.tsrender-blocks/detail-blocks.tscanvas/text.ts theme/spec 收口收益大
P3 其他 command-renderers/* 等 builder/pipeline 稳定后逐个迁移

不做的事

  • 不把所有常量都塞进数据库。布局像素、颜色 token、协议字段和资源文件名进入数据库只会降低可维护性。
  • 不做动态脚本 hook。hook 只允许代码内注册,避免线上不可控执行。
  • 不拆独立 Tsugu 服务。用户已明确不希望增加分离部署和管理成本。
  • 不一次性重命名/移动 92 个文件。先建立边界,再按风险迁移。
  • 不追求像素完全重画。当前需求是结构和硬编码治理,视觉只做等价保护。

最小可执行第一步

第一步建议只做三个文件级变更:

  1. 新增 runtime/operation-registry.ts,把 15 个 operation 的 key/name/description/handler 统一。
  2. 修改 QqbotBangDreamRendererService.execute,从 registry 查 handler不改每个 handler 内部逻辑。
  3. 新增 registry 一致性测试,保证 operation defs、SQL 初始化、TypeScript union 三者一致。

这一步收益明确,风险可控,也能为后续 hook/pipeline 打基础。

验证矩阵

阶段 必跑
文档/配置阶段 git diff --check、global-review
registry/pipeline operation registry Jest、BangDream command SQL Jest、typecheck
字典/配置 字典加载单测、无 DB 回落单测、server/difficulty parse 单测
data provider provider mock 单测、真实 Bestdori smoke、缓存命中单测
search search/fuzzy-search 全量单测、查曲/查活动/查卡 smoke
render 非空 Buffer、图片尺寸范围、关键命令 smoke
push 发布 Jenkins 构建、K8s rollout、新 Pod 日志、真实 QQBot 命令 smoke

成功标准

  • QqbotBangDreamRendererService 不再承担 15 个 operation 的巨大 switch。
  • 用户可维护的别名、文案和命令映射从源码硬编码中移出。
  • 数据源、缓存、重试、fallback 和日志通过 provider/decorator/hook 接入。
  • 搜索规则、服务器策略、档线规则、国服时间预估都有独立可测入口。
  • 渲染层的颜色、尺寸、字体、资源路径有统一 token/spec/manifest。
  • 现有 15 个在线命令保持兼容,查曲、查活动、查试炼、档线、抽卡模拟都能输出图片。