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 输出非空。
|
- 已新增 `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 输出非空。
|
- 已新增 `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/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 ...`,路径用正斜杠。
|
- 固化:Windows 下不要用反斜杠测试路径直接调用 Jest pattern,容易出现 `Pattern ... - 0 matches`;指定文件测试统一使用 `pnpm exec jest --runInBand --runTestsByPath test/qqbot/plugins/bangDream/tsugu/<file>.spec.ts ...`,路径用正斜杠。
|
||||||
|
|
||||||
### Phase 4:搜索 specification 和 matcher
|
### 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 { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -8,7 +7,11 @@ import {
|
|||||||
import { AreaItem } from '@/qqbot/plugins/bangDream/tsugu/models/area-item';
|
import { AreaItem } from '@/qqbot/plugins/bangDream/tsugu/models/area-item';
|
||||||
import { Event } from '@/qqbot/plugins/bangDream/tsugu/models/event';
|
import { Event } from '@/qqbot/plugins/bangDream/tsugu/models/event';
|
||||||
import { difficultyNameList } from '@/qqbot/plugins/bangDream/tsugu/models/song';
|
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 只从缓存取,无需等待队列立即返回缓存数据
|
- mode=0 只从缓存取,无需等待队列立即返回缓存数据
|
||||||
@ -185,7 +188,11 @@ export class Player {
|
|||||||
* @param playerId - 玩家ID参数。
|
* @param playerId - 玩家ID参数。
|
||||||
* @param server - 目标服务器。
|
* @param server - 目标服务器。
|
||||||
*/
|
*/
|
||||||
constructor(playerId: number, server: Server) {
|
constructor(
|
||||||
|
playerId: number,
|
||||||
|
server: Server,
|
||||||
|
private readonly dataRepository: PlayerDataRepository = playerDataRepository,
|
||||||
|
) {
|
||||||
this.playerId = playerId;
|
this.playerId = playerId;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
}
|
}
|
||||||
@ -195,43 +202,34 @@ export class Player {
|
|||||||
* @param useCache - use缓存参数,未传入时使用默认值。
|
* @param useCache - use缓存参数,未传入时使用默认值。
|
||||||
* @param mode - mode参数,未传入时使用默认值。
|
* @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) {
|
if (this.isInitfull) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const cacheTime = useCache ? 1 / 0 : 0;
|
|
||||||
|
|
||||||
let playerData;
|
let playerData;
|
||||||
try {
|
try {
|
||||||
playerData = await bangDreamBestdoriProvider.getJson(
|
playerData = await this.dataRepository.getDetail(
|
||||||
`/api/player/${Server[this.server]}/${this.playerId}?mode=${mode}`,
|
this.playerId,
|
||||||
{ cacheTime, retryCount: 1 },
|
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 {
|
} catch {
|
||||||
this.isExist = false;
|
this.isExist = false;
|
||||||
this.initError = true;
|
this.initError = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!playerData['result'] || playerData['data']['profile'] == null) {
|
const responseData = playerData.data;
|
||||||
|
if (!playerData.result || responseData?.profile == null) {
|
||||||
this.isExist = false;
|
this.isExist = false;
|
||||||
this.initError = true;
|
this.initError = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.isExist = true;
|
this.isExist = true;
|
||||||
this.cache = playerData['data']['cache'];
|
this.cache = responseData.cache as boolean;
|
||||||
this.time = playerData['data']['time'];
|
this.time = responseData.time as number;
|
||||||
this.profile = playerData['data']['profile'];
|
this.profile = responseData.profile as typeof this.profile;
|
||||||
//卡牌列表
|
//卡牌列表
|
||||||
this.profile.cardList = [];
|
this.profile.cardList = [];
|
||||||
for (
|
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