refactor: 收口公开码搜索计划

This commit is contained in:
2026-06-04 02:32:08 +08:00
parent 83ae363670
commit 4e8cac3856
7 changed files with 257 additions and 117 deletions

View File

@@ -512,6 +512,10 @@ import {
resolveMatch3DRuntimeGeneratedBackgroundAsset,
resolveMatch3DRuntimeGeneratedItemAssets,
} from './platformMatch3DRuntimeProfile';
import {
type PlatformPublicCodeSearchStep,
resolvePlatformPublicCodeSearchPlan,
} from './platformPublicCodeSearchModel';
import {
getPlatformPublicGalleryEntryKey,
getPlatformRecommendRuntimeKind,
@@ -13449,53 +13453,17 @@ export function PlatformEntryFlowShellImpl({
const handlePublicCodeSearch = useCallback(
async (keyword: string) => {
const normalizedKeyword = keyword.trim();
if (!normalizedKeyword) {
const searchPlan = resolvePlatformPublicCodeSearchPlan(keyword);
if (!searchPlan) {
return;
}
const { normalizedKeyword } = searchPlan;
setIsSearchingPublicCode(true);
setPublicSearchError(null);
setSearchedPublicUser(null);
const upperKeyword = normalizedKeyword.toUpperCase();
const shouldSearchUserIdFirst = /^user[_-][a-z0-9_-]+$/iu.test(
normalizedKeyword,
);
const shouldSearchBigFishFirst = upperKeyword.startsWith('BF');
const shouldSearchBabyObjectFirst = upperKeyword.startsWith('BO');
const shouldSearchJumpHopFirst = upperKeyword.startsWith('JH');
const shouldSearchWoodenFishFirst = upperKeyword.startsWith('WF');
const shouldSearchMatch3DFirst = upperKeyword.startsWith('M3');
const shouldSearchPuzzleFirst = upperKeyword.startsWith('PZ');
const shouldSearchSquareHoleFirst = upperKeyword.startsWith('SH');
const shouldSearchVisualNovelFirst = upperKeyword.startsWith('VN');
const shouldSearchBarkBattleFirst = upperKeyword.startsWith('BB');
const shouldSearchWorkFirst =
!shouldSearchUserIdFirst &&
!shouldSearchBabyObjectFirst &&
!shouldSearchBigFishFirst &&
!shouldSearchJumpHopFirst &&
!shouldSearchWoodenFishFirst &&
!shouldSearchMatch3DFirst &&
!shouldSearchPuzzleFirst &&
!shouldSearchSquareHoleFirst &&
!shouldSearchVisualNovelFirst &&
!shouldSearchBarkBattleFirst &&
(upperKeyword.startsWith('CW') || /^\d{1,8}$/u.test(normalizedKeyword));
const shouldSearchUserFirst =
shouldSearchUserIdFirst ||
upperKeyword.startsWith('SY') ||
(!shouldSearchWorkFirst &&
!shouldSearchBigFishFirst &&
!shouldSearchBabyObjectFirst &&
!shouldSearchJumpHopFirst &&
!shouldSearchWoodenFishFirst &&
!shouldSearchMatch3DFirst &&
!shouldSearchPuzzleFirst &&
!shouldSearchSquareHoleFirst &&
!shouldSearchVisualNovelFirst);
const tryOpenGalleryEntry = async () => {
const entry =
await getRpgEntryWorldGalleryDetailByCode(normalizedKeyword);
@@ -13717,95 +13685,66 @@ export function PlatformEntryFlowShellImpl({
);
};
try {
if (shouldSearchUserIdFirst) {
const user = await getPublicAuthUserById(normalizedKeyword);
setSearchedPublicUser(user);
return;
}
if (shouldSearchPuzzleFirst) {
await tryOpenPuzzleGalleryEntry();
return;
}
if (shouldSearchBigFishFirst) {
await tryOpenBigFishGalleryEntry();
return;
}
if (shouldSearchJumpHopFirst) {
await tryOpenJumpHopGalleryEntry();
return;
}
if (shouldSearchWoodenFishFirst) {
await tryOpenWoodenFishGalleryEntry();
return;
}
if (shouldSearchBabyObjectFirst) {
await tryOpenBabyObjectMatchGalleryEntry();
return;
}
if (shouldSearchMatch3DFirst) {
await tryOpenMatch3DGalleryEntry();
return;
}
if (shouldSearchSquareHoleFirst) {
await tryOpenSquareHoleGalleryEntry();
return;
}
if (shouldSearchVisualNovelFirst) {
await tryOpenVisualNovelGalleryEntry();
return;
}
if (shouldSearchBarkBattleFirst) {
await tryOpenBarkBattleGalleryEntry();
return;
}
if (shouldSearchWorkFirst) {
try {
await tryOpenGalleryEntry();
const runSearchStep = async (step: PlatformPublicCodeSearchStep) => {
switch (step) {
case 'user-id': {
const user = await getPublicAuthUserById(normalizedKeyword);
setSearchedPublicUser(user);
return;
} catch {
// 作品号优先时允许继续回退到用户号搜索。
}
}
if (shouldSearchUserFirst) {
try {
case 'public-user-code': {
const user = await getPublicAuthUserByCode(normalizedKeyword);
setSearchedPublicUser(user);
return;
} catch {
// 用户号优先时允许继续回退到作品号搜索。
}
}
if (!shouldSearchWorkFirst) {
try {
case 'rpg-work':
await tryOpenGalleryEntry();
return;
} catch {
// 常规作品未命中时继续尝试汪汪声浪作品号。
}
try {
case 'puzzle-work':
await tryOpenPuzzleGalleryEntry();
return;
case 'big-fish-work':
await tryOpenBigFishGalleryEntry();
return;
case 'jump-hop-work':
await tryOpenJumpHopGalleryEntry();
return;
case 'wooden-fish-work':
await tryOpenWoodenFishGalleryEntry();
return;
case 'baby-object-match-work':
await tryOpenBabyObjectMatchGalleryEntry();
return;
case 'match3d-work':
await tryOpenMatch3DGalleryEntry();
return;
case 'square-hole-work':
await tryOpenSquareHoleGalleryEntry();
return;
case 'visual-novel-work':
await tryOpenVisualNovelGalleryEntry();
return;
case 'bark-battle-work':
await tryOpenBarkBattleGalleryEntry();
return;
} catch {
// 汪汪声浪作品未命中时继续回退到陶泥号搜索。
default: {
const exhaustive: never = step;
return exhaustive;
}
}
};
const user = await getPublicAuthUserByCode(normalizedKeyword);
setSearchedPublicUser(user);
try {
for (const [index, step] of searchPlan.steps.entries()) {
try {
await runSearchStep(step);
return;
} catch (error) {
if (index === searchPlan.steps.length - 1) {
throw error;
}
}
}
} catch (error) {
if (selectionStage === 'work-detail') {
setSelectedPublicWorkDetail(null);

View File

@@ -0,0 +1,69 @@
import { describe, expect, test } from 'vitest';
import {
type PlatformPublicCodeSearchStep,
resolvePlatformPublicCodeSearchPlan,
} from './platformPublicCodeSearchModel';
function expectSearchSteps(
keyword: string,
steps: readonly PlatformPublicCodeSearchStep[],
) {
expect(resolvePlatformPublicCodeSearchPlan(keyword)?.steps).toEqual(steps);
}
describe('platformPublicCodeSearchModel', () => {
test('ignores empty public code search input', () => {
expect(resolvePlatformPublicCodeSearchPlan(' ')).toBeNull();
});
test('normalizes public code search keyword before planning', () => {
expect(resolvePlatformPublicCodeSearchPlan(' PZ-00000001 ')).toEqual({
normalizedKeyword: 'PZ-00000001',
steps: ['puzzle-work'],
});
});
test('searches internal user ids directly without work fallback', () => {
expectSearchSteps('user_00000001', ['user-id']);
expectSearchSteps('USER-profile-1', ['user-id']);
});
test('routes known public work prefixes to their play-specific lookup', () => {
const cases: Array<
[keyword: string, step: PlatformPublicCodeSearchStep]
> = [
['PZ-EPUBLIC1', 'puzzle-work'],
['BF-NPUBLIC1', 'big-fish-work'],
['JH-EPUBLIC1', 'jump-hop-work'],
['WF-EPUBLIC1', 'wooden-fish-work'],
['BO-EPUBLIC1', 'baby-object-match-work'],
['M3-EPUBLIC1', 'match3d-work'],
['M3D-LEGACY1', 'match3d-work'],
['SH-EPUBLIC1', 'square-hole-work'],
['VN-EPUBLIC1', 'visual-novel-work'],
['BB-EPUBLIC1', 'bark-battle-work'],
];
for (const [keyword, step] of cases) {
expectSearchSteps(keyword, [step]);
}
});
test('searches RPG public works before public user codes for CW and numeric codes', () => {
expectSearchSteps('CW-00000001', ['rpg-work', 'public-user-code']);
expectSearchSteps('12345678', ['rpg-work', 'public-user-code']);
});
test('keeps legacy user-code-first fallback for SY and ordinary keywords', () => {
const legacyFallbackSteps = [
'public-user-code',
'rpg-work',
'bark-battle-work',
'public-user-code',
] as const;
expectSearchSteps('SY-00000001', legacyFallbackSteps);
expectSearchSteps('月井守望', legacyFallbackSteps);
});
});

View File

@@ -0,0 +1,83 @@
export type PlatformPublicCodeSearchStep =
| 'user-id'
| 'public-user-code'
| 'rpg-work'
| 'puzzle-work'
| 'big-fish-work'
| 'jump-hop-work'
| 'wooden-fish-work'
| 'baby-object-match-work'
| 'match3d-work'
| 'square-hole-work'
| 'visual-novel-work'
| 'bark-battle-work';
export type PlatformPublicCodeSearchPlan = {
normalizedKeyword: string;
steps: readonly PlatformPublicCodeSearchStep[];
};
const PLATFORM_PUBLIC_USER_ID_PATTERN = /^user[_-][a-z0-9_-]+$/iu;
const PLATFORM_RPG_WORK_NUMERIC_CODE_PATTERN = /^\d{1,8}$/u;
const DIRECT_WORK_PREFIX_STEPS: ReadonlyArray<
readonly [prefix: string, step: PlatformPublicCodeSearchStep]
> = [
['PZ', 'puzzle-work'],
['BF', 'big-fish-work'],
['JH', 'jump-hop-work'],
['WF', 'wooden-fish-work'],
['BO', 'baby-object-match-work'],
['M3', 'match3d-work'],
['SH', 'square-hole-work'],
['VN', 'visual-novel-work'],
['BB', 'bark-battle-work'],
];
/** 收口公开码搜索顺序,壳层只按步骤执行网络读取与打开副作用。 */
export function resolvePlatformPublicCodeSearchPlan(
keyword: string,
): PlatformPublicCodeSearchPlan | null {
const normalizedKeyword = keyword.trim();
if (!normalizedKeyword) {
return null;
}
if (PLATFORM_PUBLIC_USER_ID_PATTERN.test(normalizedKeyword)) {
return {
normalizedKeyword,
steps: ['user-id'],
};
}
const upperKeyword = normalizedKeyword.toUpperCase();
const directWorkStep = DIRECT_WORK_PREFIX_STEPS.find(([prefix]) =>
upperKeyword.startsWith(prefix),
)?.[1];
if (directWorkStep) {
return {
normalizedKeyword,
steps: [directWorkStep],
};
}
if (
upperKeyword.startsWith('CW') ||
PLATFORM_RPG_WORK_NUMERIC_CODE_PATTERN.test(normalizedKeyword)
) {
return {
normalizedKeyword,
steps: ['rpg-work', 'public-user-code'],
};
}
return {
normalizedKeyword,
steps: [
'public-user-code',
'rpg-work',
'bark-battle-work',
'public-user-code',
],
};
}