From cf1924154405dc93b96291e63b1d797b7fbadf90 Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 23 Sep 2026 21:33:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=B8=E6=88=8F=E5=8F=91=E8=A1=8C=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=E6=94=B9=E4=B8=BA=E6=9C=8D=E5=8A=A1=E7=AB=AF=E6=8C=89?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E6=B4=BE=E7=94=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后台审核页删除「发行入口」输入框与前端 HTTPS 校验,审核请求只提交结论与公开修订号 - api-server 新增 GENARRATIVE_GAME_DISTRIBUTION_RELEASE_ENTRY_TEMPLATE 模板配置,审核通过时读版本取 gameId 并派生该游戏独立来源地址 - 模板缺 {gameId}、生产未配置模板、gameId 非主机安全字符或派生结果非法时审核通过失败关闭,非生产未配置时回落本地回环发行网关 - 同步更新发行来源 nginx 模板与说明、两份 api-server 环境变量样例、平台与运维主规范、发行里程碑实施计划和决策记录 - 真实栈 e2e 脚本不再传 entryUrl,并断言审核结果里的入口为服务端派生值 --- apps/admin-web/src/api/adminApiClient.test.ts | 2 - apps/admin-web/src/api/adminApiTypes.ts | 1 - .../AdminGameDistributionReviewPage.test.tsx | 29 +--- .../pages/AdminGameDistributionReviewPage.tsx | 53 +------- deploy/container/api-server.env.example | 4 + deploy/env/api-server.env.example | 5 + deploy/nginx/README.md | 2 +- deploy/nginx/genarrative-release-origin.conf | 3 +- ...施计划】游戏分发阶段A领域合同-2026-09-19.md | 6 + .../shared-memory/decision-log.md | 9 ++ ...发运维】本地开发验证与生产运维-2026-05-15.md | 4 +- ...玩法创作】平台入口与玩法链路-2026-05-15.md | 11 +- scripts/check-game-distribution-media-e2e.mjs | 10 +- server-rs/crates/api-server/src/config.rs | 7 + .../src/modules/game_distribution.rs | 128 ++++++++++++++++-- 15 files changed, 172 insertions(+), 102 deletions(-) diff --git a/apps/admin-web/src/api/adminApiClient.test.ts b/apps/admin-web/src/api/adminApiClient.test.ts index 05cfdc10e..63fd94567 100644 --- a/apps/admin-web/src/api/adminApiClient.test.ts +++ b/apps/admin-web/src/api/adminApiClient.test.ts @@ -501,7 +501,6 @@ test('游戏审核列表与审核动作使用约定的 URL、方法和幂等键' { decision: 'approve', expectedPublicationRevision: 3, - entryUrl: 'https://games.example.test/releases/game_1/index.html', }, ); @@ -521,7 +520,6 @@ test('游戏审核列表与审核动作使用约定的 URL、方法和幂等键' body: JSON.stringify({ decision: 'approve', expectedPublicationRevision: 3, - entryUrl: 'https://games.example.test/releases/game_1/index.html', }), }), ); diff --git a/apps/admin-web/src/api/adminApiTypes.ts b/apps/admin-web/src/api/adminApiTypes.ts index e42df5be4..a85b4129f 100644 --- a/apps/admin-web/src/api/adminApiTypes.ts +++ b/apps/admin-web/src/api/adminApiTypes.ts @@ -1110,7 +1110,6 @@ export interface AdminGameDistributionReviewRequest { decision: 'approve' | 'reject'; expectedPublicationRevision: number; reviewReason?: string; - entryUrl?: string; } export interface AdminGameDistributionReviewResponse { diff --git a/apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx b/apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx index ae00212d9..85164f6ad 100644 --- a/apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx +++ b/apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx @@ -9,10 +9,7 @@ import { suspendAdminGameDistributionGame, } from '../api/adminApiClient'; import type { AdminGameDistributionReviewEntry } from '../api/adminApiTypes'; -import { - AdminGameDistributionReviewPage, - resolveGameReleaseEntryUrlError, -} from './AdminGameDistributionReviewPage'; +import { AdminGameDistributionReviewPage } from './AdminGameDistributionReviewPage'; vi.mock('../api/adminApiClient', () => ({ isAdminApiError: vi.fn( @@ -53,23 +50,7 @@ beforeEach(() => { }); }); -test('发行入口必须是带完整来源的 HTTPS 地址', () => { - expect(resolveGameReleaseEntryUrlError('')).toBe('请填写发行入口'); - expect( - resolveGameReleaseEntryUrlError('http://games.test/a/index.html'), - ).toBe('发行入口必须以 https:// 开头'); - expect( - resolveGameReleaseEntryUrlError('https://games.test/a/index.html?token=1'), - ).toBe('发行入口不能包含 query 或 fragment'); - expect( - resolveGameReleaseEntryUrlError('https://u:p@games.test/a/index.html'), - ).toBe('发行入口不能包含凭据'); - expect( - resolveGameReleaseEntryUrlError('https://games.test/a/index.html'), - ).toBe(''); -}); - -test('通过审核时提交当前 publicationRevision 与发行入口并刷新列表', async () => { +test('通过审核只提交当前 publicationRevision 并刷新列表', async () => { vi.mocked(reviewAdminGameDistributionVersion).mockResolvedValue({ version: { ...entry, status: 'published' }, replayed: false, @@ -83,9 +64,8 @@ test('通过审核时提交当前 publicationRevision 与发行入口并刷新 ); await screen.findByText('game_1'); - fireEvent.change(screen.getByLabelText('发行入口'), { - target: { value: 'https://games.test/releases/game_1/index.html' }, - }); + expect(screen.queryByLabelText('发行入口')).toBeNull(); + expect(screen.getByText('通过后由系统分配发行地址')).toBeTruthy(); fireEvent.click(screen.getByRole('button', { name: '通过' })); await waitFor(() => @@ -99,7 +79,6 @@ test('通过审核时提交当前 publicationRevision 与发行入口并刷新 expect(payload).toEqual({ decision: 'approve', expectedPublicationRevision: 4, - entryUrl: 'https://games.test/releases/game_1/index.html', }); await waitFor(() => expect(vi.mocked(listAdminGameDistributionReviews)).toHaveBeenCalledTimes( diff --git a/apps/admin-web/src/pages/AdminGameDistributionReviewPage.tsx b/apps/admin-web/src/pages/AdminGameDistributionReviewPage.tsx index b1e993ed7..787bce570 100644 --- a/apps/admin-web/src/pages/AdminGameDistributionReviewPage.tsx +++ b/apps/admin-web/src/pages/AdminGameDistributionReviewPage.tsx @@ -47,26 +47,6 @@ function createReviewIdempotencyKey(versionId: string) { return `game-review-${versionId}-${random}`.slice(0, 128); } -export function resolveGameReleaseEntryUrlError(value: string) { - const normalized = value.trim(); - if (!normalized) return '请填写发行入口'; - if (!normalized.startsWith('https://')) { - return '发行入口必须以 https:// 开头'; - } - if (normalized.includes('?') || normalized.includes('#')) { - return '发行入口不能包含 query 或 fragment'; - } - try { - const parsed = new URL(normalized); - if (parsed.username || parsed.password) { - return '发行入口不能包含凭据'; - } - } catch { - return '发行入口不是合法 URL'; - } - return ''; -} - export function AdminGameDistributionReviewPage({ token, onUnauthorized, @@ -78,9 +58,6 @@ export function AdminGameDistributionReviewPage({ const [busyVersionId, setBusyVersionId] = useState(''); const [errorMessage, setErrorMessage] = useState(''); const [statusMessage, setStatusMessage] = useState(''); - const [entryUrlByVersion, setEntryUrlByVersion] = useState< - Record - >({}); const [reasonByVersion, setReasonByVersion] = useState< Record >({}); @@ -111,15 +88,8 @@ export function AdminGameDistributionReviewPage({ entry: AdminGameDistributionReviewEntry, decision: 'approve' | 'reject', ) { - const entryUrl = (entryUrlByVersion[entry.versionId] ?? '').trim(); const reason = (reasonByVersion[entry.versionId] ?? '').trim(); - if (decision === 'approve') { - const invalid = resolveGameReleaseEntryUrlError(entryUrl); - if (invalid) { - setErrorMessage(invalid); - return; - } - } else if (!reason) { + if (decision === 'reject' && !reason) { setErrorMessage('拒绝审核必须填写理由'); return; } @@ -135,7 +105,6 @@ export function AdminGameDistributionReviewPage({ ? { decision, expectedPublicationRevision: entry.publicationRevision, - entryUrl, } : { decision, @@ -269,23 +238,9 @@ export function AdminGameDistributionReviewPage({
- - - setEntryUrlByVersion((current) => ({ - ...current, - [entry.versionId]: event.target.value, - })) - } - disabled={busy} - /> + + 通过后由系统分配发行地址 +