refactor: 收口 BangDream 乐队资源仓储

This commit is contained in:
sunlei 2026-06-07 02:14:47 +08:00
parent 00ff99d2b6
commit 88a99c858c
4 changed files with 96 additions and 9 deletions

View File

@ -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/<file>.spec.ts ...`,路径用正斜杠。
### Phase 4搜索 specification 和 matcher

View File

@ -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<Buffer> {
return await this.provider.getAsset(this.getLogoPath(bandId));
}
/**
* SVG
*
* @param bandId - ID
*/
async getIconSvgBuffer(bandId: number): Promise<Buffer> {
return await this.provider.getAsset(this.getIconSvgPath(bandId));
}
}
export const bandResourceRepository = new BandResourceRepository();

View File

@ -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<Image> {
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<Image> {
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;

View File

@ -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<BangDreamDataProvider> {
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('<svg />');
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',
);
});
});