refactor: 收口公开码搜索计划
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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',
|
||||
],
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user