From 88a99c858ce3251be1e69707bb3c1df6ec2651d2 Mon Sep 17 00:00:00 2001 From: sunlei Date: Sun, 7 Jun 2026 02:14:47 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=94=B6=E5=8F=A3=20BangDream=20?= =?UTF-8?q?=E4=B9=90=E9=98=9F=E8=B5=84=E6=BA=90=E4=BB=93=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ot-bangdream-tsugu-global-refactor-plan.md | 1 + .../tsugu/models/band-resource-repository.ts | 47 +++++++++++++++++++ .../plugins/bangDream/tsugu/models/band.ts | 12 ++--- .../tsugu/band-resource-repository.spec.ts | 45 ++++++++++++++++++ 4 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 src/qqbot/plugins/bangDream/tsugu/models/band-resource-repository.ts create mode 100644 test/qqbot/plugins/bangDream/tsugu/band-resource-repository.spec.ts diff --git a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md index 2732bf9..53ede60 100644 --- a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md +++ b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md @@ -303,6 +303,7 @@ export interface TsuguHook { - 已新增 `models/gacha-resource-repository.ts`,把 `Gacha` 的详情请求、首页横幅、screen 背景、`bg1` fallback 和 Logo 资源路径收口到 provider-backed repository;`gacha-resource-repository.spec.ts` 覆盖 `/api/gacha` 缓存参数、CN 优先 screen 路径、横幅缺失回退 Logo 和背景 fallback,本地 `/查卡池 259`、`/抽卡模拟 10 259` 图片 smoke 输出非空。 - 已新增 `models/event-stage-data-repository.ts`,把 `EventStage` 的 festival stages/rotationMusics 数据请求从 `bestdoriUrl + callAPIAndCacheResponse` 收口到 provider-backed repository;`event-stage-data-repository.spec.ts` 覆盖两个 festival API 路径和缓存参数,本地 `/查试炼 310` 保持拆成 5 张图片输出。 - 已新增 `models/character-resource-repository.ts`,把 `Character` 的详情请求、角色图标、KV 立绘和名称横幅资源路径从 `bestdoriUrl + callAPIAndCacheResponse/downloadFileCache` 收口到 provider-backed repository;`character-resource-repository.spec.ts` 覆盖详情缓存策略和三类资源路径,本地 `/查角色 1` 图片 smoke 输出非空。 +- 已新增 `models/band-resource-repository.ts`,把 `Band` 的乐队 Logo 和乐队图标 SVG 资源路径从 `bestdoriUrl + downloadFileCache` 收口到 provider-backed repository;`band-resource-repository.spec.ts` 覆盖两类资源路径和下载调用,本地 `/查角色 1` 图片 smoke 保持非空输出。 - 固化:Windows 下不要用反斜杠测试路径直接调用 Jest pattern,容易出现 `Pattern ... - 0 matches`;指定文件测试统一使用 `pnpm exec jest --runInBand --runTestsByPath test/qqbot/plugins/bangDream/tsugu/.spec.ts ...`,路径用正斜杠。 ### Phase 4:搜索 specification 和 matcher diff --git a/src/qqbot/plugins/bangDream/tsugu/models/band-resource-repository.ts b/src/qqbot/plugins/bangDream/tsugu/models/band-resource-repository.ts new file mode 100644 index 0000000..3480903 --- /dev/null +++ b/src/qqbot/plugins/bangDream/tsugu/models/band-resource-repository.ts @@ -0,0 +1,47 @@ +import { bangDreamBestdoriProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/bestdori-provider'; +import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider'; +import { formatNumber } from '@/qqbot/plugins/bangDream/tsugu/models/model-utils'; + +export class BandResourceRepository { + constructor( + private readonly provider: BangDreamDataProvider = bangDreamBestdoriProvider, + ) {} + + /** + * 获取乐队 Logo 资源路径。 + * + * @param bandId - 乐队 ID。 + */ + getLogoPath(bandId: number): string { + return `/assets/jp/band/logo/${formatNumber(bandId, 3)}_rip/logoL.png`; + } + + /** + * 获取乐队图标 SVG 资源路径。 + * + * @param bandId - 乐队 ID。 + */ + getIconSvgPath(bandId: number): string { + return `/res/icon/band_${bandId}.svg`; + } + + /** + * 下载乐队 Logo 资源。 + * + * @param bandId - 乐队 ID。 + */ + async getLogoBuffer(bandId: number): Promise { + return await this.provider.getAsset(this.getLogoPath(bandId)); + } + + /** + * 下载乐队图标 SVG 资源。 + * + * @param bandId - 乐队 ID。 + */ + async getIconSvgBuffer(bandId: number): Promise { + return await this.provider.getAsset(this.getIconSvgPath(bandId)); + } +} + +export const bandResourceRepository = new BandResourceRepository(); diff --git a/src/qqbot/plugins/bangDream/tsugu/models/band.ts b/src/qqbot/plugins/bangDream/tsugu/models/band.ts index cb62710..a3fb39c 100644 --- a/src/qqbot/plugins/bangDream/tsugu/models/band.ts +++ b/src/qqbot/plugins/bangDream/tsugu/models/band.ts @@ -1,10 +1,8 @@ import mainAPI from '@/qqbot/plugins/bangDream/tsugu/models/main-data-store'; import { Character } from '@/qqbot/plugins/bangDream/tsugu/models/character'; import { Image, loadImage } from 'skia-canvas'; -import { downloadFileCache } from '@/qqbot/plugins/bangDream/tsugu/data-clients/asset-cache-client'; -import { formatNumber } from '@/qqbot/plugins/bangDream/tsugu/models/model-utils'; -import { bestdoriUrl } from '@/qqbot/plugins/bangDream/tsugu/runtime/config'; import { convertSvgToPngBuffer } from '@/qqbot/plugins/bangDream/tsugu/canvas/image-utils'; +import { bandResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/models/band-resource-repository'; export class Band { bandId: number; @@ -61,9 +59,7 @@ export class Band { * @returns 异步处理结果。 */ async getLogo(): Promise { - const logoBuffer = await downloadFileCache( - `${bestdoriUrl}/assets/jp/band/logo/${formatNumber(this.bandId, 3)}_rip/logoL.png`, - ); + const logoBuffer = await bandResourceRepository.getLogoBuffer(this.bandId); return await loadImage(logoBuffer); } } @@ -80,9 +76,7 @@ export async function getBandIcon(bandId: number): Promise { if (bandIconCache[bandId]) { return bandIconCache[bandId]; } - const iconSvgBuffer = await downloadFileCache( - `${bestdoriUrl}/res/icon/band_${bandId}.svg`, - ); + const iconSvgBuffer = await bandResourceRepository.getIconSvgBuffer(bandId); const iconPngBuffer = await convertSvgToPngBuffer(iconSvgBuffer); const image = await loadImage(iconPngBuffer); bandIconCache[bandId] = image; diff --git a/test/qqbot/plugins/bangDream/tsugu/band-resource-repository.spec.ts b/test/qqbot/plugins/bangDream/tsugu/band-resource-repository.spec.ts new file mode 100644 index 0000000..6e131d8 --- /dev/null +++ b/test/qqbot/plugins/bangDream/tsugu/band-resource-repository.spec.ts @@ -0,0 +1,45 @@ +import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider'; +import { BandResourceRepository } from '@/qqbot/plugins/bangDream/tsugu/models/band-resource-repository'; + +function createProviderMock(): jest.Mocked { + return { + getAsset: jest.fn(), + getJson: jest.fn(), + getTracker: jest.fn(), + name: 'MockBestdori', + resolveUrl: jest.fn((pathOrUrl) => `https://bestdori.example${pathOrUrl}`), + }; +} + +describe('BangDream band resource repository', () => { + it('builds band logo and icon paths', () => { + const repository = new BandResourceRepository(createProviderMock()); + + expect(repository.getLogoPath(1)).toBe( + '/assets/jp/band/logo/001_rip/logoL.png', + ); + expect(repository.getIconSvgPath(1)).toBe('/res/icon/band_1.svg'); + }); + + it('downloads band logo and icon assets through the provider', async () => { + const provider = createProviderMock(); + const logoBuffer = Buffer.from('logo'); + const iconBuffer = Buffer.from(''); + provider.getAsset + .mockResolvedValueOnce(logoBuffer) + .mockResolvedValueOnce(iconBuffer); + const repository = new BandResourceRepository(provider); + + await expect(repository.getLogoBuffer(1)).resolves.toBe(logoBuffer); + await expect(repository.getIconSvgBuffer(1)).resolves.toBe(iconBuffer); + + expect(provider.getAsset).toHaveBeenNthCalledWith( + 1, + '/assets/jp/band/logo/001_rip/logoL.png', + ); + expect(provider.getAsset).toHaveBeenNthCalledWith( + 2, + '/res/icon/band_1.svg', + ); + }); +});