补齐游戏分发发布恢复链路并修掉双击提交建两份游戏
Project CI / AI game creator shell Rust crates (push) Successful in 1m37s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m7s
Project CI / Backend tests (push) Successful in 4m12s
Project CI / Frontend tests (push) Successful in 2m9s
Project CI / Native shell tests (push) Successful in 6m20s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m28s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 9m31s
Project CI / AI game creator shell web tests (push) Successful in 1m40s
Project CI / Repository checks (push) Successful in 2m25s
Project CI / AI game creator shell Rust crates (push) Successful in 1m37s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m7s
Project CI / Backend tests (push) Successful in 4m12s
Project CI / Frontend tests (push) Successful in 2m9s
Project CI / Native shell tests (push) Successful in 6m20s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m28s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 9m31s
Project CI / AI game creator shell web tests (push) Successful in 1m40s
Project CI / Repository checks (push) Successful in 2m25s
- 新增 scripts/check-game-distribution-web-publish-recovery-e2e.mjs:上传中断留草稿、关窗/登录失效后重登续传沿用原 versionId、双击只落一份、换账号不显示他人草稿,共 10 项 PASS - GamePublishPage 加同步 submitInFlightRef 在途守卫与按次稳定的 Idempotency-Key(失败重试复用、成功后清空),双击不再创建第二份游戏 - 游戏分发里程碑阶段 C 第 5 条改为已勾选 - pitfalls 记录「点一次会建资源的入口需要同步守卫或稳定幂等键」
This commit is contained in:
@@ -196,6 +196,19 @@ export function GamePublishPage({
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [publishedGameId, setPublishedGameId] = useState('');
|
||||
/**
|
||||
* 同步的在途提交标记与稳定的幂等键。
|
||||
*
|
||||
* `isSubmitting` 是 React 状态,双击发生在同一次渲染窗口里时还没生效;再叠上
|
||||
* `prepareGamePackage` 的压缩耗时,两次点击会各建一份游戏。这里用 ref 同步拦截,
|
||||
* 并让同一次发布(含失败后重试)复用同一组 Idempotency-Key,服务端按幂等重放。
|
||||
*/
|
||||
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);
|
||||
@@ -420,10 +433,12 @@ export function GamePublishPage({
|
||||
setError('请选择上次那个发行包 ZIP 继续上传');
|
||||
return;
|
||||
}
|
||||
if (submitInFlightRef.current) return;
|
||||
submitInFlightRef.current = true;
|
||||
setIsSubmitting(true);
|
||||
setError('');
|
||||
try {
|
||||
const resumeKey = createPublishKey();
|
||||
const resumeKey = resolvePublishKey();
|
||||
if (draftDetail.version.recoveryAction !== 'submit') {
|
||||
const prepared = await prepareGamePackage(packageFile as File);
|
||||
await uploadGamePackage(
|
||||
@@ -444,11 +459,13 @@ export function GamePublishPage({
|
||||
setDraftDetail(null);
|
||||
setIsResumingDraft(false);
|
||||
setPublishedGameId(draftDetail.game.id);
|
||||
publishKeyRef.current = null;
|
||||
} catch (resumeError: unknown) {
|
||||
setError(
|
||||
resumeError instanceof Error ? resumeError.message : '继续发布失败',
|
||||
);
|
||||
} finally {
|
||||
submitInFlightRef.current = false;
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
@@ -482,11 +499,13 @@ export function GamePublishPage({
|
||||
setError('请选择发行包 ZIP');
|
||||
return;
|
||||
}
|
||||
if (submitInFlightRef.current) return;
|
||||
submitInFlightRef.current = true;
|
||||
setIsSubmitting(true);
|
||||
setError('');
|
||||
try {
|
||||
const prepared = await prepareGamePackage(packageFile);
|
||||
const updateKey = createPublishKey();
|
||||
const updateKey = resolvePublishKey();
|
||||
const inputModes: GameDistributionInputMode[] = ['keyboard', 'mouse'];
|
||||
if (deviceSupport.touch) inputModes.push('touch');
|
||||
const tags = tagsInput
|
||||
@@ -535,11 +554,13 @@ export function GamePublishPage({
|
||||
? { ...current, versionNumber: version.versionNumber }
|
||||
: current,
|
||||
);
|
||||
publishKeyRef.current = null;
|
||||
} catch (updateError: unknown) {
|
||||
setError(
|
||||
updateError instanceof Error ? updateError.message : '发布新版本失败',
|
||||
);
|
||||
} finally {
|
||||
submitInFlightRef.current = false;
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
@@ -573,6 +594,8 @@ export function GamePublishPage({
|
||||
setError('请选择发行包 ZIP');
|
||||
return;
|
||||
}
|
||||
if (submitInFlightRef.current) return;
|
||||
submitInFlightRef.current = true;
|
||||
setIsSubmitting(true);
|
||||
setError('');
|
||||
try {
|
||||
@@ -596,7 +619,7 @@ export function GamePublishPage({
|
||||
inputModes,
|
||||
orientation,
|
||||
};
|
||||
const publishKey = createPublishKey();
|
||||
const publishKey = resolvePublishKey();
|
||||
const game = await createGame(gameMetadata, `${publishKey}:game`);
|
||||
const version = await createGameVersion(
|
||||
game.id,
|
||||
@@ -631,11 +654,13 @@ export function GamePublishPage({
|
||||
);
|
||||
clearPublishDraft();
|
||||
setPublishedGameId(game.id);
|
||||
publishKeyRef.current = null;
|
||||
} catch (submitError: unknown) {
|
||||
setError(
|
||||
submitError instanceof Error ? submitError.message : '发布游戏失败',
|
||||
);
|
||||
} finally {
|
||||
submitInFlightRef.current = false;
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user