fix(网页发布): 恢复路径改用 versionId 派生幂等键
- handleResumeSubmit 原先用每会话随机的 resolvePublishKey(),与提交模块的 `${versionId}:upload`/`:submit` 不一致,刷新页面后重试无法被服务端去重
- 统一成同一组 versionId 派生键,并删除不再使用的 createPublishKey / publishKeyRef / publishKeySeed
- 补 GamePublishPage 断言恢复上传与送审确实发送 ${versionId}:upload / :submit
This commit is contained in:
@@ -523,7 +523,13 @@ test('按统一发布、上传、送审顺序提交并展示审核中', async ()
|
||||
'shot-2.png',
|
||||
]);
|
||||
expect(vi.mocked(uploadGamePackage).mock.calls[0]?.[0]).toBe('gamever-1');
|
||||
expect(vi.mocked(uploadGamePackage).mock.calls[0]?.[2]).toBe(
|
||||
'gamever-1:upload',
|
||||
);
|
||||
expect(vi.mocked(submitGameVersion).mock.calls[0]?.[1]).toBe(0);
|
||||
expect(vi.mocked(submitGameVersion).mock.calls[0]?.[2]).toBe(
|
||||
'gamever-1:submit',
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText('已提交审核,审核通过后会自动出现在游戏广场。'),
|
||||
@@ -586,6 +592,10 @@ test('同一账号回到发布页可以沿用原版本继续送审', async () =>
|
||||
await waitFor(() => expect(submitGameVersion).toHaveBeenCalledTimes(1));
|
||||
expect(vi.mocked(submitGameVersion).mock.calls[0]?.[0]).toBe('gamever-1');
|
||||
expect(vi.mocked(submitGameVersion).mock.calls[0]?.[1]).toBe(2);
|
||||
// 恢复送审与提交模块共用同一套 versionId 派生键,刷新页面后重试也能被服务端去重。
|
||||
expect(vi.mocked(submitGameVersion).mock.calls[0]?.[2]).toBe(
|
||||
'gamever-1:submit',
|
||||
);
|
||||
expect(
|
||||
await screen.findByText('已提交审核,审核通过后会自动出现在游戏广场。'),
|
||||
).toBeTruthy();
|
||||
|
||||
@@ -68,16 +68,6 @@ type GameUpdateContext = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
let publishKeySeed = 0;
|
||||
function createPublishKey() {
|
||||
publishKeySeed += 1;
|
||||
const random =
|
||||
typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
||||
? crypto.randomUUID()
|
||||
: `${Date.now()}-${publishKeySeed}`;
|
||||
return `web-publish-${random}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网页发布编排:UI 只组输入与展示结果,定 key、调接口、写草稿、传包、送审都在提交模块内。
|
||||
* 依赖在模块加载时注入一次,单测替换 `gameDistributionClient` 即可覆盖整条链路。
|
||||
@@ -150,19 +140,14 @@ export function GamePublishPage({
|
||||
const [error, setError] = useState('');
|
||||
const [publishedGameId, setPublishedGameId] = useState('');
|
||||
/**
|
||||
* 同步的在途提交标记与恢复上传/送审用的稳定幂等键。
|
||||
* 同步的在途提交标记。
|
||||
*
|
||||
* `isSubmitting` 是 React 状态,双击发生在同一次渲染窗口里时还没生效;再叠上
|
||||
* `prepareGamePackage` 的压缩耗时,两次点击会各建一份游戏。这里用 ref 同步拦截。
|
||||
* 新建/更新发布的自然幂等由提交模块冻结的 `projectKey`/`versionNumber` 负责;
|
||||
* 恢复已有版本继续上传时仍复用同一组 `Idempotency-Key`。
|
||||
* 幂等键全部由提交模块按不可变 `versionId` 派生(`{versionId}:upload` / `:submit`),
|
||||
* 恢复路径沿用同一组,不再用每会话随机键,刷新页面后重试也能被服务端去重。
|
||||
*/
|
||||
const submitInFlightRef = useRef(false);
|
||||
const publishKeyRef = useRef<string | null>(null);
|
||||
const resolvePublishKey = () => {
|
||||
if (!publishKeyRef.current) publishKeyRef.current = createPublishKey();
|
||||
return publishKeyRef.current;
|
||||
};
|
||||
const [draft, setDraft] = useState<GamePublishDraft | null>(null);
|
||||
const [draftDetail, setDraftDetail] =
|
||||
useState<GameDistributionVersionDetail | null>(null);
|
||||
@@ -390,28 +375,27 @@ export function GamePublishPage({
|
||||
setIsSubmitting(true);
|
||||
setError('');
|
||||
try {
|
||||
const resumeKey = resolvePublishKey();
|
||||
const resumeVersionId = draftDetail.version.versionId;
|
||||
if (draftDetail.version.recoveryAction !== 'submit') {
|
||||
const prepared = await prepareGamePackage(packageFile as File);
|
||||
await uploadGamePackage(
|
||||
draftDetail.version.versionId,
|
||||
resumeVersionId,
|
||||
new Blob([prepared.bytes as unknown as BlobPart], {
|
||||
type: 'application/zip',
|
||||
}),
|
||||
`${resumeKey}:upload`,
|
||||
`${resumeVersionId}:upload`,
|
||||
);
|
||||
}
|
||||
await submitGameVersion(
|
||||
draftDetail.version.versionId,
|
||||
resumeVersionId,
|
||||
draftDetail.game.publicationRevision,
|
||||
`${resumeKey}:submit`,
|
||||
`${resumeVersionId}:submit`,
|
||||
);
|
||||
clearPublishDraft();
|
||||
setDraft(null);
|
||||
setDraftDetail(null);
|
||||
setIsResumingDraft(false);
|
||||
setPublishedGameId(draftDetail.game.id);
|
||||
publishKeyRef.current = null;
|
||||
} catch (resumeError: unknown) {
|
||||
setError(
|
||||
resumeError instanceof Error ? resumeError.message : '继续发布失败',
|
||||
|
||||
Reference in New Issue
Block a user