refactor: 收口 BangDream 玩家数据请求
This commit is contained in:
parent
d429b799da
commit
68b55764c2
@ -313,6 +313,7 @@ export interface TsuguHook {
|
||||
- 已新增 `models/degree-resource-repository.ts`,把 `Degree` 的称号缩略图、称号框、称号图标、动态称号脚本和纹理素材路径从 `bestdoriUrl + downloadFile/downloadFileCache` 收口到 provider-backed repository;`degree-resource-repository.spec.ts` 覆盖旧/新缩略图 fallback、动态称号旧纹理白名单和 provider 调用,本地 `/ycx 10 100 cn` 图片 smoke 输出非空。
|
||||
- 已新增 `render-blocks/deck-rank-resource-repository.ts`,把玩家详情“乐队编成等级”Rank 图片从 `list-band-detail.ts` 的本地路径和 Bestdori URL 直拼收口到 repository,渲染层只负责 `Buffer -> Image`;`deck-rank-resource-repository.spec.ts` 覆盖本地素材优先、远端 `/res/icon/*.png` 兜底和 provider 调用,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
|
||||
- `models/event-data-repository.ts` 已改为 provider-injected repository,并为活动背景和 topscreen trim 增加路径工厂;活动详情背景现在跟随 `displayedServerList` 选服,不再无条件按全局默认服取底图,避免 `/查活动 ... jp` 这类指定服务器查询出现横幅和背景不一致。
|
||||
- 已新增 `models/player-data-repository.ts`,把 `Player` 的 `/api/player/{server}/{playerId}` 详情请求、mode/cacheTime 策略和 mode=1 后台刷新行为从领域模型中收口到 provider-backed repository;`player-data-repository.spec.ts` 覆盖玩家详情路径、缓存参数和后台刷新调用,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
|
||||
- 固化:Windows 下不要用反斜杠测试路径直接调用 Jest pattern,容易出现 `Pattern ... - 0 matches`;指定文件测试统一使用 `pnpm exec jest --runInBand --runTestsByPath test/qqbot/plugins/bangDream/tsugu/<file>.spec.ts ...`,路径用正斜杠。
|
||||
|
||||
### Phase 4:搜索 specification 和 matcher
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
import { bangDreamBestdoriProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/bestdori-provider';
|
||||
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
|
||||
import { logger } from '@/qqbot/plugins/bangDream/tsugu/runtime/logger';
|
||||
import { Server } from './server';
|
||||
|
||||
export type PlayerDetailMode = 0 | 1 | 2 | 3;
|
||||
|
||||
export interface PlayerDetailResponse {
|
||||
data?: {
|
||||
cache?: boolean;
|
||||
profile?: Record<string, any> | null;
|
||||
time?: number;
|
||||
};
|
||||
result?: boolean;
|
||||
}
|
||||
|
||||
export class PlayerDataRepository {
|
||||
constructor(
|
||||
private readonly provider: BangDreamDataProvider = bangDreamBestdoriProvider,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 构建玩家资料 API 路径。
|
||||
*
|
||||
* @param playerId - 玩家 ID。
|
||||
* @param server - 目标服务器。
|
||||
* @param mode - Bestdori 玩家查询模式。
|
||||
*/
|
||||
getDetailPath(playerId: number, server: Server, mode: PlayerDetailMode) {
|
||||
return `/api/player/${Server[server]}/${playerId}?mode=${mode}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取玩家详情,并在缓存模式 1 下保留后台刷新行为。
|
||||
*
|
||||
* @param playerId - 玩家 ID。
|
||||
* @param server - 目标服务器。
|
||||
* @param useCache - 是否只读取缓存。
|
||||
* @param mode - Bestdori 玩家查询模式。
|
||||
*/
|
||||
async getDetail(
|
||||
playerId: number,
|
||||
server: Server,
|
||||
useCache: boolean,
|
||||
mode: PlayerDetailMode,
|
||||
): Promise<PlayerDetailResponse> {
|
||||
const cacheTime = useCache ? Infinity : 0;
|
||||
const path = this.getDetailPath(playerId, server, mode);
|
||||
const playerData = await this.provider.getJson<PlayerDetailResponse>(path, {
|
||||
cacheTime,
|
||||
retryCount: 1,
|
||||
});
|
||||
if (mode === 1 && !Number.isFinite(cacheTime)) {
|
||||
this.refreshCache(playerId, server, mode);
|
||||
}
|
||||
return playerData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台刷新玩家缓存,失败只记日志,不影响当前查询结果。
|
||||
*
|
||||
* @param playerId - 玩家 ID。
|
||||
* @param server - 目标服务器。
|
||||
* @param mode - Bestdori 玩家查询模式。
|
||||
*/
|
||||
refreshCache(playerId: number, server: Server, mode: PlayerDetailMode): void {
|
||||
this.provider
|
||||
.getJson(this.getDetailPath(playerId, server, mode), {
|
||||
cacheTime: 300,
|
||||
retryCount: 1,
|
||||
})
|
||||
.catch((err) => {
|
||||
logger('InitPlayer', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const playerDataRepository = new PlayerDataRepository();
|
||||
@ -1,4 +1,3 @@
|
||||
import { bangDreamBestdoriProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/bestdori-provider';
|
||||
import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||
import {
|
||||
Card,
|
||||
@ -8,7 +7,11 @@ import {
|
||||
import { AreaItem } from '@/qqbot/plugins/bangDream/tsugu/models/area-item';
|
||||
import { Event } from '@/qqbot/plugins/bangDream/tsugu/models/event';
|
||||
import { difficultyNameList } from '@/qqbot/plugins/bangDream/tsugu/models/song';
|
||||
import { logger } from '@/qqbot/plugins/bangDream/tsugu/runtime/logger';
|
||||
import {
|
||||
playerDataRepository,
|
||||
type PlayerDataRepository,
|
||||
type PlayerDetailMode,
|
||||
} from './player-data-repository';
|
||||
|
||||
/*
|
||||
- mode=0 只从缓存取,无需等待队列立即返回缓存数据
|
||||
@ -185,7 +188,11 @@ export class Player {
|
||||
* @param playerId - 玩家ID参数。
|
||||
* @param server - 目标服务器。
|
||||
*/
|
||||
constructor(playerId: number, server: Server) {
|
||||
constructor(
|
||||
playerId: number,
|
||||
server: Server,
|
||||
private readonly dataRepository: PlayerDataRepository = playerDataRepository,
|
||||
) {
|
||||
this.playerId = playerId;
|
||||
this.server = server;
|
||||
}
|
||||
@ -195,43 +202,34 @@ export class Player {
|
||||
* @param useCache - use缓存参数,未传入时使用默认值。
|
||||
* @param mode - mode参数,未传入时使用默认值。
|
||||
*/
|
||||
async initFull(useCache: boolean = false, mode: 0 | 1 | 2 | 3 = 2) {
|
||||
async initFull(useCache: boolean = false, mode: PlayerDetailMode = 2) {
|
||||
if (this.isInitfull) {
|
||||
return;
|
||||
}
|
||||
const cacheTime = useCache ? 1 / 0 : 0;
|
||||
|
||||
let playerData;
|
||||
try {
|
||||
playerData = await bangDreamBestdoriProvider.getJson(
|
||||
`/api/player/${Server[this.server]}/${this.playerId}?mode=${mode}`,
|
||||
{ cacheTime, retryCount: 1 },
|
||||
playerData = await this.dataRepository.getDetail(
|
||||
this.playerId,
|
||||
this.server,
|
||||
useCache,
|
||||
mode,
|
||||
);
|
||||
if (mode == 1 && !isFinite(cacheTime)) {
|
||||
// 后台更新玩家信息,确保下一次用最新的缓存去显示
|
||||
bangDreamBestdoriProvider
|
||||
.getJson(
|
||||
`/api/player/${Server[this.server]}/${this.playerId}?mode=${mode}`,
|
||||
{ cacheTime: 300, retryCount: 1 },
|
||||
)
|
||||
.catch((err) => {
|
||||
logger('InitPlayer', err);
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
this.isExist = false;
|
||||
this.initError = true;
|
||||
return;
|
||||
}
|
||||
if (!playerData['result'] || playerData['data']['profile'] == null) {
|
||||
const responseData = playerData.data;
|
||||
if (!playerData.result || responseData?.profile == null) {
|
||||
this.isExist = false;
|
||||
this.initError = true;
|
||||
return;
|
||||
}
|
||||
this.isExist = true;
|
||||
this.cache = playerData['data']['cache'];
|
||||
this.time = playerData['data']['time'];
|
||||
this.profile = playerData['data']['profile'];
|
||||
this.cache = responseData.cache as boolean;
|
||||
this.time = responseData.time as number;
|
||||
this.profile = responseData.profile as typeof this.profile;
|
||||
//卡牌列表
|
||||
this.profile.cardList = [];
|
||||
for (
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
import type { BangDreamDataProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/data-provider';
|
||||
import { PlayerDataRepository } from '@/qqbot/plugins/bangDream/tsugu/models/player-data-repository';
|
||||
import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||
|
||||
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 player data repository', () => {
|
||||
it('builds player detail paths from server and mode', () => {
|
||||
const repository = new PlayerDataRepository(createProviderMock());
|
||||
|
||||
expect(repository.getDetailPath(26591455, Server.jp, 3)).toBe(
|
||||
'/api/player/jp/26591455?mode=3',
|
||||
);
|
||||
});
|
||||
|
||||
it('routes player detail requests through provider cache policy', async () => {
|
||||
const provider = createProviderMock();
|
||||
provider.getJson.mockResolvedValue({ result: true });
|
||||
const repository = new PlayerDataRepository(provider);
|
||||
|
||||
await repository.getDetail(26591455, Server.jp, false, 3);
|
||||
await repository.getDetail(26591455, Server.jp, true, 0);
|
||||
|
||||
expect(provider.getJson).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'/api/player/jp/26591455?mode=3',
|
||||
{ cacheTime: 0, retryCount: 1 },
|
||||
);
|
||||
expect(provider.getJson).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'/api/player/jp/26591455?mode=0',
|
||||
{ cacheTime: Infinity, retryCount: 1 },
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps Bestdori mode 1 background refresh behavior', async () => {
|
||||
const provider = createProviderMock();
|
||||
provider.getJson.mockResolvedValue({ result: true });
|
||||
const repository = new PlayerDataRepository(provider);
|
||||
|
||||
await repository.getDetail(26591455, Server.jp, true, 1);
|
||||
|
||||
expect(provider.getJson).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'/api/player/jp/26591455?mode=1',
|
||||
{ cacheTime: Infinity, retryCount: 1 },
|
||||
);
|
||||
expect(provider.getJson).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'/api/player/jp/26591455?mode=1',
|
||||
{ cacheTime: 300, retryCount: 1 },
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user