退役旧创作模板业务

保留 SpacetimeDB 历史表、迁移白名单与旧业务源码
切换前端 active 入口并解除旧创作页面和路由编译链
移除旧后端路由、worker 与纯业务 crate 依赖
收敛 SpacetimeDB 模块为历史数据壳
同步 Nginx、Pingora、验证门禁与架构文档
This commit is contained in:
2026-07-17 22:07:52 +08:00
parent 387a1c26e3
commit e9c3dc1120
253 changed files with 125232 additions and 4727 deletions
+174 -8
View File
@@ -71,12 +71,6 @@ module.exports = {
'simple-import-sort/exports': 'off', 'simple-import-sort/exports': 'off',
}, },
}, },
{
files: ['src/components/match3d-runtime/Match3DPhysicsBoard.tsx'],
rules: {
'react-refresh/only-export-components': 'off',
},
},
], ],
plugins: [ plugins: [
'@typescript-eslint', '@typescript-eslint',
@@ -96,22 +90,191 @@ module.exports = {
'dist_check', 'dist_check',
'dist_check_monster_position', 'dist_check_monster_position',
'coverage', 'coverage',
'build',
'output',
'retired/**',
'node_modules', 'node_modules',
'server-rs/target', 'server-rs/target',
'server-rs/target-*', 'server-rs/target-*',
'apps/desktop-shell/src-tauri/target', 'apps/desktop-shell/src-tauri/target',
'target', 'target',
'src/main.tsx',
'src/App.tsx',
'src/routing/appPageRoutes.ts',
'src/routing/appPageRoutes.test.ts',
'src/routing/appRoutes.tsx',
'src/routing/appRoutes.test.ts',
'src/services/appTitle.ts',
'src/services/appTitle.test.ts',
'src/components/CharacterAnimator.tsx', 'src/components/CharacterAnimator.tsx',
'src/AdventurePanel.tsx',
'src/AdventureEntityModal.tsx',
'src/App.tsx',
'src/App.test.tsx',
'src/CharacterCreation.tsx',
'src/RpgRuntimeApp.tsx',
'src/routing/appPageRoutes.ts',
'src/routing/appPageRoutes.test.ts',
'src/routing/appRoutes.tsx',
'src/routing/appRoutes.test.ts',
'src/*PlaygroundApp.tsx',
'src/ChildMotionDemoApp.tsx',
'src/Match3DPlaygroundApp.tsx',
'src/components/child-motion-demo/**',
'src/components/asset-studio/**',
'src/components/bark-battle-creation/**',
'src/components/big-fish-creation/**',
'src/components/big-fish-result/**',
'src/components/big-fish-runtime/**',
'src/components/creation-agent/**',
'src/components/creation-home/**',
'src/components/creative-agent/**',
'src/components/custom-world-agent/**',
'src/components/custom-world-home/**',
'src/components/edutainment-creation/**',
'src/components/edutainment-result/**',
'src/components/edutainment-runtime/**',
'src/components/game-canvas/**',
'src/components/jump-hop-result/**',
'src/components/jump-hop-runtime/**', 'src/components/jump-hop-runtime/**',
'src/components/match3d-result/**',
'src/components/match3d-runtime/**', 'src/components/match3d-runtime/**',
'src/components/puzzle-clear-creation/**',
'src/components/puzzle-clear-result/**',
'src/components/puzzle-clear-runtime/**',
'src/components/puzzle-gallery/**',
'src/components/puzzle-result/**',
'src/components/puzzle-runtime/**',
'src/components/rpg-creation-asset-studio/**',
'src/components/rpg-creation-result/**',
'src/components/rpg-runtime-panels/**',
'src/components/square-hole-creation/**',
'src/components/square-hole-result/**',
'src/components/square-hole-runtime/**',
'src/components/unified-creation/**',
'src/components/visual-novel-creation/**',
'src/components/visual-novel-result/**',
'src/components/visual-novel-runtime/**',
'src/components/wooden-fish-result/**',
'src/components/wooden-fish-runtime/**',
'src/components/common/PublishShare*',
'src/components/common/publishShare*',
'src/components/platform-entry/PlatformEntryCreation*',
'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx',
'src/components/platform-entry/platformEntryTypes.ts',
'src/components/platform-entry/PlatformEntryHome*',
'src/components/platform-entry/PlatformEntryWorld*',
'src/components/platform-entry/PlatformMobileHome*',
'src/components/platform-entry/PlatformWork*',
'src/components/platform-entry/PlatformDraft*',
'src/components/platform-entry/PlatformTask*',
'src/components/platform-entry/PlatformError*',
'src/components/platform-entry/barkBattle*',
'src/components/platform-entry/platformCreation*',
'src/components/platform-entry/platformDialog*',
'src/components/platform-entry/platformDraft*',
'src/components/platform-entry/platformEdutainment*',
'src/components/platform-entry/platformEntryCreation*',
'src/components/platform-entry/platformExternal*',
'src/components/platform-entry/platformGeneration*',
'src/components/platform-entry/platformHost*',
'src/components/platform-entry/platformMiniGame*',
'src/components/platform-entry/platformPlayed*',
'src/components/platform-entry/platformPublic*',
'src/components/platform-entry/platformPuzzle*',
'src/components/platform-entry/platformRecommend*',
'src/components/platform-entry/platformRpg*',
'src/components/platform-entry/platformSelection*',
'src/components/platform-entry/puzzleDraft*',
'src/components/platform-entry/usePlatformCreation*',
'src/components/platform-entry/usePlatformEntry*',
'src/components/platform-entry/PlatformEntryFlowShellImpl/**',
'src/components/platform-entry/platformMatch3DRuntimeProfile*',
'src/components/unified-creation/workspaces/JumpHopCreationWorkspace*',
'src/components/unified-creation/workspaces/Match3DCreationWorkspace*',
'src/components/rpg-creation-editor/**', 'src/components/rpg-creation-editor/**',
'src/components/rpg-entry/**', 'src/components/rpg-entry/**',
'!src/components/rpg-entry/',
'!src/components/rpg-entry/rpgEntryProfileFundsViewModel.ts',
'src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx',
'src/components/custom-world-home/CustomWorldCreationHub.test.tsx',
'src/components/custom-world-home/CustomWorldCreationHub.testAdapter.tsx',
'src/components/custom-world-home/creationWorkShelf.test.ts',
'src/components/rpg-runtime-shell/**', 'src/components/rpg-runtime-shell/**',
'src/hooks/rpg-runtime-story/**', 'src/hooks/rpg-runtime-story/**',
'src/hooks/rpg-session/**',
'src/hooks/combat/**',
'src/prompts/customWorldPrompts.ts', 'src/prompts/customWorldPrompts.ts',
'src/services/ai.ts', 'src/services/ai.ts',
'src/services/appTitle.ts',
'src/services/appTitle.test.ts',
'src/services/miniGameDraftGenerationProgress.ts', 'src/services/miniGameDraftGenerationProgress.ts',
'src/services/bark-battle-creation/**',
'src/services/bark-battle-runtime/**',
'src/services/big-fish-creation/**',
'src/services/big-fish-gallery/**',
'src/services/big-fish-runtime/**',
'src/services/big-fish-works/**',
'src/services/creation-agent/**',
'src/services/creation-audio/**',
'src/services/creative-agent/**',
'src/services/edutainment-baby-drawing/**',
'src/services/edutainment-baby-object/**',
'src/services/child-motion-demo/**',
'src/services/jump-hop/**',
'src/services/match3d-creation/**',
'src/services/match3d-runtime/**',
'src/services/match3d-works/**',
'src/services/match3dGeneratedModelCache*',
'src/services/match3dSpritesheetParser*',
'src/services/puzzle-agent/**',
'src/services/puzzle-gallery/**',
'src/services/puzzle-onboarding/**',
'src/services/puzzle-runtime/**',
'src/services/puzzle-works/**',
'src/services/rpg-creation/**',
'src/services/rpg-entry/index.ts',
'src/services/rpg-entry/rpgEntryLibraryClient.ts',
'src/services/rpg-entry/rpgProfileClient.test.ts',
'src/services/rpg-runtime/**',
'!src/services/rpg-runtime/',
'!src/services/rpg-runtime/rpgRuntimeRequest.ts',
'src/services/square-hole-creation/**',
'src/services/square-hole-runtime/**',
'src/services/square-hole-works/**',
'src/services/storyEngine/**',
'src/services/visual-novel-creation/**',
'src/services/visual-novel-runtime/**',
'src/services/visual-novel-works/**',
'src/services/wooden-fish/**',
'packages/shared/src/contracts/jumpHop.ts',
'packages/shared/src/contracts/match3dAgent.ts',
'packages/shared/src/contracts/match3dRuntime.ts',
'packages/shared/src/contracts/match3dWorks.ts',
'packages/shared/src/contracts/barkBattle*',
'packages/shared/src/contracts/bigFish*',
'packages/shared/src/contracts/creationAgent*',
'packages/shared/src/contracts/creationAudio*',
'packages/shared/src/contracts/creativeAgent*',
'packages/shared/src/contracts/customWorld*',
'packages/shared/src/contracts/edutainment*',
'packages/shared/src/contracts/playTypes*',
'packages/shared/src/contracts/publicWork*',
'packages/shared/src/contracts/puzzle*',
'packages/shared/src/contracts/rpg*',
'packages/shared/src/contracts/squareHole*',
'packages/shared/src/contracts/story*',
'packages/shared/src/contracts/visualNovel*',
'packages/shared/src/contracts/woodenFish*',
'scripts/export-match3d-resource-pipeline*',
'scripts/generate-child-motion-demo-assets.mjs',
'src/services/puzzle-clear/**', 'src/services/puzzle-clear/**',
'src/games/**',
'src/data/**',
'src/prompts/**',
'apps/admin-web/src/pages/AdminCreationEntrySwitchPage*',
'apps/admin-web/src/pages/AdminGrayReleaseConfigPage*',
'apps/admin-web/src/pages/AdminWorkVisibilityPage*',
'src/services/recommendedRuntimeGuestLaunch.test.ts', 'src/services/recommendedRuntimeGuestLaunch.test.ts',
'src/data/sceneEncounterPreviews.ts', 'src/data/sceneEncounterPreviews.ts',
'public/Icons', 'public/Icons',
@@ -130,7 +293,10 @@ module.exports = {
rules: { rules: {
'@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-var-requires': 'off',
'react-hooks/rules-of-hooks': 'error', 'react-hooks/rules-of-hooks': 'error',
'react-refresh/only-export-components': ['error', {allowConstantExport: true}], 'react-refresh/only-export-components': [
'error',
{ allowConstantExport: true },
],
'simple-import-sort/imports': 'error', 'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error', 'simple-import-sort/exports': 'error',
'@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': 'off',
@@ -143,7 +309,7 @@ module.exports = {
}, },
], ],
'no-constant-condition': 'error', 'no-constant-condition': 'error',
'no-console': ['error', {allow: ['warn', 'error']}], 'no-console': ['error', { allow: ['warn', 'error'] }],
'no-useless-escape': 'error', 'no-useless-escape': 'error',
'prefer-const': 'error', 'prefer-const': 'error',
}, },
+28 -128
View File
@@ -4,7 +4,6 @@ import type {
AdminCreateAccountResponse, AdminCreateAccountResponse,
AdminCreateEditorShowcaseCampaignImageUploadTicketRequest, AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
AdminCreateEditorShowcaseCampaignImageUploadTicketResponse, AdminCreateEditorShowcaseCampaignImageUploadTicketResponse,
AdminCreationEntryConfigResponse,
AdminDashboardQuery, AdminDashboardQuery,
AdminDashboardResponse, AdminDashboardResponse,
AdminDatabaseTableListResponse, AdminDatabaseTableListResponse,
@@ -23,7 +22,6 @@ import type {
AdminEditorShowcaseListQuery, AdminEditorShowcaseListQuery,
AdminEditorShowcaseListResponse, AdminEditorShowcaseListResponse,
AdminEditorShowcaseReviewRequest, AdminEditorShowcaseReviewRequest,
AdminFeatureGateConfigResponse,
AdminLoginResponse, AdminLoginResponse,
AdminMeResponse, AdminMeResponse,
AdminOverviewResponse, AdminOverviewResponse,
@@ -40,24 +38,17 @@ import type {
AdminTrackingEventListResponse, AdminTrackingEventListResponse,
AdminUpdateAccountRequest, AdminUpdateAccountRequest,
AdminUpdateAccountResponse, AdminUpdateAccountResponse,
AdminUpdateWorkVisibilityRequest,
AdminUpdateWorkVisibilityResponse,
AdminUploadedEditorShowcaseCampaignImage, AdminUploadedEditorShowcaseCampaignImage,
AdminUpsertCreationEntryEventBannersRequest,
AdminUpsertCreationEntryTypeConfigRequest,
AdminUpsertEditorShowcaseCampaignRequest, AdminUpsertEditorShowcaseCampaignRequest,
AdminUpsertFeatureGateConfigRequest,
AdminUpsertProfileInviteCodeRequest, AdminUpsertProfileInviteCodeRequest,
AdminUpsertProfileRechargeProductRequest, AdminUpsertProfileRechargeProductRequest,
AdminUpsertProfileRedeemCodeRequest, AdminUpsertProfileRedeemCodeRequest,
AdminUpsertProfileTaskConfigRequest, AdminUpsertProfileTaskConfigRequest,
AdminUpsertProfileWalletConfigRequest, AdminUpsertProfileWalletConfigRequest,
AdminUpsertPublicWorkInteractionConfigRequest,
AdminUserDetailQuery, AdminUserDetailQuery,
AdminUserDetailResponse, AdminUserDetailResponse,
AdminWalletRestrictionRequest, AdminWalletRestrictionRequest,
AdminWalletRestrictionResponse, AdminWalletRestrictionResponse,
AdminWorkVisibilityListResponse,
ApiErrorEnvelope, ApiErrorEnvelope,
ApiMeta, ApiMeta,
ApiSuccessEnvelope, ApiSuccessEnvelope,
@@ -194,7 +185,7 @@ export function getAdminMe(token: string) {
} }
export function listAdminAccounts(token: string) { export function listAdminAccounts(token: string) {
return request<AdminAccountListResponse>('/admin/api/accounts', {token}); return request<AdminAccountListResponse>('/admin/api/accounts', { token });
} }
export function createAdminAccount( export function createAdminAccount(
@@ -215,7 +206,7 @@ export function updateAdminAccount(
) { ) {
return request<AdminUpdateAccountResponse>( return request<AdminUpdateAccountResponse>(
`/admin/api/accounts/${encodeURIComponent(accountId)}`, `/admin/api/accounts/${encodeURIComponent(accountId)}`,
{method: 'PUT', token, body: payload}, { method: 'PUT', token, body: payload },
); );
} }
@@ -275,74 +266,6 @@ export function listAdminTrackingEventKeys(token: string) {
); );
} }
export function getAdminFeatureGateConfig(token: string) {
return request<AdminFeatureGateConfigResponse>('/admin/api/feature-gates', {
token,
});
}
export function upsertAdminFeatureGateConfig(
token: string,
payload: AdminUpsertFeatureGateConfigRequest,
) {
return request<AdminFeatureGateConfigResponse>('/admin/api/feature-gates', {
method: 'PUT',
token,
body: payload,
});
}
export function getAdminCreationEntryConfig(token: string) {
return request<AdminCreationEntryConfigResponse>(
'/admin/api/creation-entry/config',
{ token },
);
}
export function upsertAdminCreationEntryConfig(
token: string,
payload: AdminUpsertCreationEntryTypeConfigRequest,
) {
return request<AdminCreationEntryConfigResponse>(
'/admin/api/creation-entry/config',
{
method: 'POST',
token,
body: payload,
},
);
}
/** 保存创作入口公告表单序列化后的后端传输字段。 */
export function upsertAdminCreationEntryBanners(
token: string,
payload: AdminUpsertCreationEntryEventBannersRequest,
) {
return request<AdminCreationEntryConfigResponse>(
'/admin/api/creation-entry/config/banners',
{
method: 'POST',
token,
body: payload,
},
);
}
/** 保存公开作品详情页点赞 / 改造能力配置。 */
export function upsertAdminPublicWorkInteractions(
token: string,
payload: AdminUpsertPublicWorkInteractionConfigRequest,
) {
return request<AdminCreationEntryConfigResponse>(
'/admin/api/creation-entry/config/interactions',
{
method: 'POST',
token,
body: payload,
},
);
}
export function getAdminEditorGenerationPricing(token: string) { export function getAdminEditorGenerationPricing(token: string) {
return request<EditorGenerationPricingConfigPayload>( return request<EditorGenerationPricingConfigPayload>(
'/admin/api/editor-generation-pricing', '/admin/api/editor-generation-pricing',
@@ -364,27 +287,6 @@ export function upsertAdminEditorGenerationPricing(
); );
} }
export function listAdminWorkVisibility(token: string) {
return request<AdminWorkVisibilityListResponse>(
'/admin/api/works/visibility',
{ token },
);
}
export function updateAdminWorkVisibility(
token: string,
payload: AdminUpdateWorkVisibilityRequest,
) {
return request<AdminUpdateWorkVisibilityResponse>(
'/admin/api/works/visibility',
{
method: 'POST',
token,
body: payload,
},
);
}
export function getAdminAssetReadUrl( export function getAdminAssetReadUrl(
token: string, token: string,
query: AdminAssetReadUrlQuery, query: AdminAssetReadUrlQuery,
@@ -471,18 +373,19 @@ export async function uploadAdminEditorShowcaseCampaignImage(
): Promise<AdminUploadedEditorShowcaseCampaignImage> { ): Promise<AdminUploadedEditorShowcaseCampaignImage> {
const contentType = resolveAdminImageContentType(file); const contentType = resolveAdminImageContentType(file);
const dimensions = await readAdminImageFileDimensions(file); const dimensions = await readAdminImageFileDimensions(file);
const response = await request<AdminCreateEditorShowcaseCampaignImageUploadTicketResponse>( const response =
'/admin/api/editor-showcase/campaign/image-upload-ticket', await request<AdminCreateEditorShowcaseCampaignImageUploadTicketResponse>(
{ '/admin/api/editor-showcase/campaign/image-upload-ticket',
method: 'POST', {
token, method: 'POST',
body: { token,
fileName: file.name.trim() || 'showcase-campaign.png', body: {
contentType, fileName: file.name.trim() || 'showcase-campaign.png',
contentLength: file.size, contentType,
} satisfies AdminCreateEditorShowcaseCampaignImageUploadTicketRequest, contentLength: file.size,
}, } satisfies AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
); },
);
await postAdminDirectUploadFile(response.upload, file); await postAdminDirectUploadFile(response.upload, file);
const objectKey = response.upload.objectKey.trim().replace(/^\/+/u, ''); const objectKey = response.upload.objectKey.trim().replace(/^\/+/u, '');
return { return {
@@ -630,17 +533,14 @@ export function listAdminRechargeOrders(
) { ) {
return request<AdminRechargeOrderListResponse>( return request<AdminRechargeOrderListResponse>(
`/admin/api/profile/recharge-orders${buildAdminRechargeOrderListQuery(query)}`, `/admin/api/profile/recharge-orders${buildAdminRechargeOrderListQuery(query)}`,
{token}, { token },
); );
} }
export function getAdminUserDetail( export function getAdminUserDetail(token: string, query: AdminUserDetailQuery) {
token: string,
query: AdminUserDetailQuery,
) {
return request<AdminUserDetailResponse>( return request<AdminUserDetailResponse>(
`/admin/api/profile/users/detail${buildAdminUserDetailQuery(query)}`, `/admin/api/profile/users/detail${buildAdminUserDetailQuery(query)}`,
{token}, { token },
); );
} }
@@ -650,7 +550,7 @@ export function previewAdminRechargeRefund(
) { ) {
return request<AdminRechargeRefundPreviewResponse>( return request<AdminRechargeRefundPreviewResponse>(
'/admin/api/profile/recharge-refunds/preview', '/admin/api/profile/recharge-refunds/preview',
{method: 'POST', token, body: payload}, { method: 'POST', token, body: payload },
); );
} }
@@ -660,7 +560,7 @@ export function executeAdminRechargeRefund(
) { ) {
return request<AdminRechargeRefundActionResponse>( return request<AdminRechargeRefundActionResponse>(
'/admin/api/profile/recharge-refunds/execute', '/admin/api/profile/recharge-refunds/execute',
{method: 'POST', token, body: payload}, { method: 'POST', token, body: payload },
); );
} }
@@ -670,7 +570,7 @@ export function registerAdminRechargeRefund(
) { ) {
return request<AdminRechargeRefundActionResponse>( return request<AdminRechargeRefundActionResponse>(
'/admin/api/profile/recharge-refunds/register', '/admin/api/profile/recharge-refunds/register',
{method: 'POST', token, body: payload}, { method: 'POST', token, body: payload },
); );
} }
@@ -680,7 +580,7 @@ export function resolveAdminRechargeRefundManualReview(
) { ) {
return request<AdminRechargeRefundActionResponse>( return request<AdminRechargeRefundActionResponse>(
'/admin/api/profile/recharge-refunds/manual-review/resolve', '/admin/api/profile/recharge-refunds/manual-review/resolve',
{method: 'POST', token, body: payload}, { method: 'POST', token, body: payload },
); );
} }
@@ -690,7 +590,7 @@ export function updateAdminWalletRestriction(
) { ) {
return request<AdminWalletRestrictionResponse>( return request<AdminWalletRestrictionResponse>(
'/admin/api/profile/wallet-restriction', '/admin/api/profile/wallet-restriction',
{method: 'POST', token, body: payload}, { method: 'POST', token, body: payload },
); );
} }
@@ -710,10 +610,7 @@ function buildAssetReadUrlQuery(query: AdminAssetReadUrlQuery) {
if (objectKey) { if (objectKey) {
params.set('objectKey', objectKey); params.set('objectKey', objectKey);
} else if (legacyPublicPath) { } else if (legacyPublicPath) {
params.set( params.set('legacyPublicPath', `/${legacyPublicPath.replace(/^\/+/u, '')}`);
'legacyPublicPath',
`/${legacyPublicPath.replace(/^\/+/u, '')}`,
);
} }
if ( if (
typeof query.expireSeconds === 'number' && typeof query.expireSeconds === 'number' &&
@@ -731,7 +628,10 @@ function resolveAdminImageContentType(file: File) {
if (declaredType.startsWith('image/')) { if (declaredType.startsWith('image/')) {
return declaredType; return declaredType;
} }
const extension = file.name.trim().toLowerCase().match(/\.([a-z0-9]+)$/u)?.[1]; const extension = file.name
.trim()
.toLowerCase()
.match(/\.([a-z0-9]+)$/u)?.[1];
if (extension === 'jpg' || extension === 'jpeg') { if (extension === 'jpg' || extension === 'jpeg') {
return 'image/jpeg'; return 'image/jpeg';
} }
-141
View File
@@ -286,98 +286,6 @@ export interface AdminTrackingEventListQuery {
exportAll?: boolean; exportAll?: boolean;
} }
export interface AdminFeatureGateConfigPayload {
gateKey: string;
enabled: boolean;
rolloutPercent: number;
allowUserIds: string[];
allowUserTags: string[];
denyUserIds: string[];
description: string;
updatedAt: string;
}
export interface AdminFeatureGateConfigResponse {
gates: AdminFeatureGateConfigPayload[];
}
export type AdminUpsertFeatureGateConfigRequest = Omit<
AdminFeatureGateConfigPayload,
'updatedAt'
>;
/** 后台创作入口配置响应,同时包含模板入口和独立公告配置。 */
export interface AdminCreationEntryConfigResponse {
entries: AdminCreationEntryTypeConfigPayload[];
eventBanners: AdminCreationEntryEventBannerPayload[];
publicWorkInteractions: PublicWorkInteractionConfigPayload[];
}
/** 后台创作入口公告位配置项;旧结构化 banner 字段仅保留兼容。 */
export interface AdminCreationEntryEventBannerPayload {
title: string;
description: string;
coverImageSrc: string;
prizePoolMudPoints: number;
startsAtText: string;
endsAtText: string;
renderMode: 'structured' | 'html';
htmlCode?: string | null;
}
/** 后台单个创作模板入口配置,公告不再绑定在某一个入口上。 */
export interface AdminCreationEntryTypeConfigPayload {
id: string;
title: string;
subtitle: string;
badge: string;
imageSrc: string;
visible: boolean;
open: boolean;
sortOrder: number;
categoryId: string;
categoryLabel: string;
categorySortOrder: number;
updatedAtMicros: number;
unifiedCreationSpec?: UnifiedCreationSpecPayload | null;
}
/** 后台保存创作模板入口开关与统一创作契约的请求体。 */
export interface AdminUpsertCreationEntryTypeConfigRequest {
id: string;
title: string;
subtitle: string;
badge: string;
imageSrc: string;
visible: boolean;
open: boolean;
sortOrder: number;
categoryId: string;
categoryLabel: string;
categorySortOrder: number;
unifiedCreationSpec?: UnifiedCreationSpecPayload | null;
}
/** 后台保存创作入口公告表单序列化结果的请求体。 */
export interface AdminUpsertCreationEntryEventBannersRequest {
/** 传输字段沿用后端契约,内容由后台表单生成。 */
eventBannersJson: string;
}
/** 后台公开作品详情页互动能力配置项。 */
export interface PublicWorkInteractionConfigPayload {
sourceType: string;
likeEnabled: boolean;
remixEnabled: boolean;
likeDisabledMessage: string;
remixDisabledMessage: string;
}
/** 后台保存公开作品点赞 / 改造能力配置请求体。 */
export interface AdminUpsertPublicWorkInteractionConfigRequest {
publicWorkInteractions: PublicWorkInteractionConfigPayload[];
}
/** 图片画布生成模型泥点定价配置。 */ /** 图片画布生成模型泥点定价配置。 */
export type EditorGenerationPricingUnitPayload = 'perGeneration' | 'perSecond'; export type EditorGenerationPricingUnitPayload = 'perGeneration' | 'perSecond';
@@ -391,55 +299,6 @@ export interface EditorGenerationPricingConfigPayload {
models: Record<string, EditorGenerationModelPricingPayload>; models: Record<string, EditorGenerationModelPricingPayload>;
} }
/** 后台统一创作工作台契约表单的传输结构。 */
export interface UnifiedCreationSpecPayload {
playId: string;
title: string;
mudPointCost: number;
workspaceStage: string;
generationStage: string;
resultStage: string;
fields: UnifiedCreationFieldPayload[];
}
/** 后台统一创作字段契约,保存前会校验字段类型和必填标记。 */
export interface UnifiedCreationFieldPayload {
id: string;
kind: 'text' | 'select' | 'image' | 'audio';
label: string;
required: boolean;
}
export interface AdminWorkVisibilityEntryPayload {
sourceType: string;
workId: string;
profileId: string;
sourceSessionId?: string | null;
publicWorkCode: string;
ownerUserId: string;
authorDisplayName: string;
title: string;
subtitle: string;
coverImageSrc?: string | null;
visible: boolean;
publishedAtMicros?: number | null;
updatedAtMicros: number;
}
export interface AdminWorkVisibilityListResponse {
entries: AdminWorkVisibilityEntryPayload[];
}
export interface AdminUpdateWorkVisibilityRequest {
sourceType: string;
profileId: string;
visible: boolean;
}
export interface AdminUpdateWorkVisibilityResponse {
entry: AdminWorkVisibilityEntryPayload;
}
export interface AdminEditorAssetListQuery { export interface AdminEditorAssetListQuery {
cursor?: string | null; cursor?: string | null;
ownerUserId?: string | null; ownerUserId?: string | null;
+34 -62
View File
@@ -1,4 +1,4 @@
import {useCallback, useEffect, useMemo, useState} from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { import {
formatAdminApiError, formatAdminApiError,
@@ -17,33 +17,30 @@ import {
getStoredAdminToken, getStoredAdminToken,
setStoredAdminToken, setStoredAdminToken,
} from '../auth/adminAuthStore'; } from '../auth/adminAuthStore';
import {AdminAccountsPage} from '../pages/AdminAccountsPage'; import { AdminAccountsPage } from '../pages/AdminAccountsPage';
import {AdminCreationEntrySwitchPage} from '../pages/AdminCreationEntrySwitchPage'; import { AdminDashboardPage } from '../pages/AdminDashboardPage';
import {AdminDashboardPage} from '../pages/AdminDashboardPage'; import { AdminDatabaseTablesPage } from '../pages/AdminDatabaseTablesPage';
import {AdminDatabaseTablesPage} from '../pages/AdminDatabaseTablesPage'; import { AdminDebugHttpPage } from '../pages/AdminDebugHttpPage';
import {AdminDebugHttpPage} from '../pages/AdminDebugHttpPage'; import { AdminEditorAssetQueryPage } from '../pages/AdminEditorAssetQueryPage';
import {AdminEditorAssetQueryPage} from '../pages/AdminEditorAssetQueryPage'; import { AdminEditorGenerationPricingPage } from '../pages/AdminEditorGenerationPricingPage';
import {AdminEditorGenerationPricingPage} from '../pages/AdminEditorGenerationPricingPage'; import { AdminEditorShowcaseReviewPage } from '../pages/AdminEditorShowcaseReviewPage';
import {AdminEditorShowcaseReviewPage} from '../pages/AdminEditorShowcaseReviewPage'; import { AdminInviteCodePage } from '../pages/AdminInviteCodePage';
import {AdminGrayReleaseConfigPage} from '../pages/AdminGrayReleaseConfigPage'; import { AdminLoginPage } from '../pages/AdminLoginPage';
import {AdminInviteCodePage} from '../pages/AdminInviteCodePage'; import { AdminOverviewPage } from '../pages/AdminOverviewPage';
import {AdminLoginPage} from '../pages/AdminLoginPage'; import { AdminProfileWalletConfigPage } from '../pages/AdminProfileWalletConfigPage';
import {AdminOverviewPage} from '../pages/AdminOverviewPage'; import { AdminRechargeOrderPage } from '../pages/AdminRechargeOrderPage';
import {AdminProfileWalletConfigPage} from '../pages/AdminProfileWalletConfigPage'; import { AdminRechargeProductPage } from '../pages/AdminRechargeProductPage';
import {AdminRechargeOrderPage} from '../pages/AdminRechargeOrderPage'; import { AdminRedeemCodePage } from '../pages/AdminRedeemCodePage';
import {AdminRechargeProductPage} from '../pages/AdminRechargeProductPage'; import { AdminTaskConfigPage } from '../pages/AdminTaskConfigPage';
import {AdminRedeemCodePage} from '../pages/AdminRedeemCodePage'; import { AdminTrackingEventsPage } from '../pages/AdminTrackingEventsPage';
import {AdminTaskConfigPage} from '../pages/AdminTaskConfigPage'; import type { AdminRouteId } from './adminRoutes';
import {AdminTrackingEventsPage} from '../pages/AdminTrackingEventsPage';
import {AdminWorkVisibilityPage} from '../pages/AdminWorkVisibilityPage';
import type {AdminRouteId} from './adminRoutes';
import { import {
getAccessibleAdminRoutes, getAccessibleAdminRoutes,
resolveAccessibleAdminRoute, resolveAccessibleAdminRoute,
resolveAdminRoute, resolveAdminRoute,
routeHash, routeHash,
} from './adminRoutes'; } from './adminRoutes';
import {AdminShell} from './AdminShell'; import { AdminShell } from './AdminShell';
type SessionStatus = 'checking' | 'guest' | 'authenticated'; type SessionStatus = 'checking' | 'guest' | 'authenticated';
@@ -157,17 +154,20 @@ export function AdminApp() {
} }
}, []); }, []);
const handleLogin = useCallback(async (username: string, password: string) => { const handleLogin = useCallback(
const response = await loginAdmin(username, password); async (username: string, password: string) => {
setStoredAdminToken(response.token); const response = await loginAdmin(username, password);
setToken(response.token); setStoredAdminToken(response.token);
setAdmin(response.admin); setToken(response.token);
setTaskConfigResult(null); setAdmin(response.admin);
setProfileWalletConfigResult(null); setTaskConfigResult(null);
setRechargeProductResult(null); setProfileWalletConfigResult(null);
setLoginNotice(''); setRechargeProductResult(null);
setStatus('authenticated'); setLoginNotice('');
}, []); setStatus('authenticated');
},
[],
);
const handleUnauthorized = useCallback( const handleUnauthorized = useCallback(
(message = '登录状态已失效') => { (message = '登录状态已失效') => {
@@ -227,12 +227,6 @@ export function AdminApp() {
onUnauthorized={handleUnauthorized} onUnauthorized={handleUnauthorized}
/> />
) : null} ) : null}
{activeRouteId === 'gray-release' ? (
<AdminGrayReleaseConfigPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'redeem' ? ( {activeRouteId === 'redeem' ? (
<AdminRedeemCodePage <AdminRedeemCodePage
token={token} token={token}
@@ -245,25 +239,6 @@ export function AdminApp() {
onUnauthorized={handleUnauthorized} onUnauthorized={handleUnauthorized}
/> />
) : null} ) : null}
{activeRouteId === 'creation-announcement' ? (
<AdminCreationEntrySwitchPage
mode="announcements"
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'creation-entry' ? (
<AdminCreationEntrySwitchPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'work-visibility' ? (
<AdminWorkVisibilityPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'tasks' ? ( {activeRouteId === 'tasks' ? (
<AdminTaskConfigPage <AdminTaskConfigPage
result={taskConfigResult} result={taskConfigResult}
@@ -313,10 +288,7 @@ export function AdminApp() {
/> />
) : null} ) : null}
{activeRouteId === 'accounts' ? ( {activeRouteId === 'accounts' ? (
<AdminAccountsPage <AdminAccountsPage token={token} onUnauthorized={handleUnauthorized} />
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null} ) : null}
</AdminShell> </AdminShell>
); );
+3 -11
View File
@@ -4,16 +4,12 @@ import {
Bug, Bug,
Coins, Coins,
Database, Database,
Eye,
GitBranch,
Images, Images,
LayoutDashboard, LayoutDashboard,
ListChecks, ListChecks,
LogOut, LogOut,
Megaphone,
ReceiptText, ReceiptText,
ShieldCheck, ShieldCheck,
SlidersHorizontal,
Star, Star,
Table2, Table2,
TicketCheck, TicketCheck,
@@ -21,10 +17,10 @@ import {
Users, Users,
WalletCards, WalletCards,
} from 'lucide-react'; } from 'lucide-react';
import type {ReactNode} from 'react'; import type { ReactNode } from 'react';
import type {AdminSessionPayload} from '../api/adminApiTypes'; import type { AdminSessionPayload } from '../api/adminApiTypes';
import type {AdminRouteDefinition, AdminRouteId} from './adminRoutes'; import type { AdminRouteDefinition, AdminRouteId } from './adminRoutes';
interface AdminShellProps { interface AdminShellProps {
admin: AdminSessionPayload; admin: AdminSessionPayload;
@@ -41,7 +37,6 @@ const routeIcons = {
tables: Database, tables: Database,
debug: Bug, debug: Bug,
tracking: Table2, tracking: Table2,
'gray-release': GitBranch,
redeem: TicketPercent, redeem: TicketPercent,
invite: TicketCheck, invite: TicketCheck,
'profile-wallet': WalletCards, 'profile-wallet': WalletCards,
@@ -51,9 +46,6 @@ const routeIcons = {
'editor-generation-pricing': Coins, 'editor-generation-pricing': Coins,
'editor-showcase': Star, 'editor-showcase': Star,
'editor-assets': Images, 'editor-assets': Images,
'creation-announcement': Megaphone,
'creation-entry': SlidersHorizontal,
'work-visibility': Eye,
accounts: Users, accounts: Users,
} satisfies Record<AdminRouteId, typeof LayoutDashboard>; } satisfies Record<AdminRouteId, typeof LayoutDashboard>;
+7 -24
View File
@@ -1,4 +1,4 @@
import {expect, test} from 'vitest'; import { expect, test } from 'vitest';
import { import {
adminRoutes, adminRoutes,
@@ -19,19 +19,6 @@ test('后台默认进入 Dashboard', () => {
expect(routeHash('dashboard')).toBe('#dashboard'); expect(routeHash('dashboard')).toBe('#dashboard');
}); });
// 中文注释:后台入口公告必须作为独立导航存在,避免公告表单被误藏在入口开关页。
test('后台入口公告路由可通过导航和 hash 访问', () => {
expect(adminRoutes).toContainEqual({
id: 'creation-announcement',
label: '入口公告',
hash: '#creation-announcement',
});
expect(resolveAdminRoute('#creation-announcement')).toBe(
'creation-announcement',
);
expect(routeHash('creation-announcement')).toBe('#creation-announcement');
});
test('后台模型定价路由可通过导航和 hash 访问', () => { test('后台模型定价路由可通过导航和 hash 访问', () => {
expect(adminRoutes).toContainEqual({ expect(adminRoutes).toContainEqual({
id: 'editor-generation-pricing', id: 'editor-generation-pricing',
@@ -46,16 +33,12 @@ test('后台模型定价路由可通过导航和 hash 访问', () => {
); );
}); });
test('后台灰度发布路由可通过导航和 hash 访问', () => { test('后台不再暴露旧创作模板管理路由', () => {
expect(adminRoutes).toContainEqual({ expect(resolveAdminRoute('#creation-entry')).toBe('dashboard');
id: 'gray-release', expect(resolveAdminRoute('#creation-announcement')).toBe('dashboard');
label: '灰度发布', expect(resolveAdminRoute('#gray-release')).toBe('dashboard');
hash: '#gray-release', expect(resolveAdminRoute('#work-visibility')).toBe('dashboard');
});
expect(resolveAdminRoute('#gray-release')).toBe('gray-release');
expect(routeHash('gray-release')).toBe('#gray-release');
}); });
test('后台素材查询路由可通过导航和 hash 访问', () => { test('后台素材查询路由可通过导航和 hash 访问', () => {
expect(adminRoutes).toContainEqual({ expect(adminRoutes).toContainEqual({
id: 'editor-assets', id: 'editor-assets',
@@ -92,7 +75,7 @@ test('owner 可访问全部业务 Tab 和账号管理', () => {
tabPermissions: [], tabPermissions: [],
}); });
expect(routes).toEqual(adminRoutes); expect(routes).toEqual(adminRoutes);
expect(routes.at(-1)).toMatchObject({id: 'accounts', ownerOnly: true}); expect(routes.at(-1)).toMatchObject({ id: 'accounts', ownerOnly: true });
}); });
test('member 只访问已分配 Tab 且无权 hash 回落到第一项', () => { test('member 只访问已分配 Tab 且无权 hash 回落到第一项', () => {
+19 -23
View File
@@ -5,7 +5,6 @@ export type AdminRouteId =
| 'tables' | 'tables'
| 'debug' | 'debug'
| 'tracking' | 'tracking'
| 'gray-release'
| 'redeem' | 'redeem'
| 'invite' | 'invite'
| 'profile-wallet' | 'profile-wallet'
@@ -15,9 +14,6 @@ export type AdminRouteId =
| 'editor-generation-pricing' | 'editor-generation-pricing'
| 'editor-showcase' | 'editor-showcase'
| 'editor-assets' | 'editor-assets'
| 'creation-announcement'
| 'creation-entry'
| 'work-visibility'
| 'accounts'; | 'accounts';
export type AdminTabPermission = Exclude<AdminRouteId, 'accounts'>; export type AdminTabPermission = Exclude<AdminRouteId, 'accounts'>;
@@ -31,25 +27,25 @@ export interface AdminRouteDefinition {
} }
export const adminRoutes: AdminRouteDefinition[] = [ export const adminRoutes: AdminRouteDefinition[] = [
{id: 'dashboard', label: 'Dashboard', hash: '#dashboard'}, { id: 'dashboard', label: 'Dashboard', hash: '#dashboard' },
{id: 'overview', label: '服务总览', hash: '#overview'}, { id: 'overview', label: '服务总览', hash: '#overview' },
{id: 'tables', label: '表查询', hash: '#tables'}, { id: 'tables', label: '表查询', hash: '#tables' },
{id: 'debug', label: 'API 调试', hash: '#debug'}, { id: 'debug', label: 'API 调试', hash: '#debug' },
{id: 'tracking', label: '埋点数据', hash: '#tracking'}, { id: 'tracking', label: '埋点数据', hash: '#tracking' },
{id: 'gray-release', label: '灰度发布', hash: '#gray-release'}, { id: 'redeem', label: '兑换码', hash: '#redeem' },
{id: 'redeem', label: '兑换码', hash: '#redeem'}, { id: 'invite', label: '邀请码', hash: '#invite' },
{id: 'invite', label: '邀请码', hash: '#invite'}, { id: 'profile-wallet', label: '账号配置', hash: '#profile-wallet' },
{id: 'profile-wallet', label: '账号配置', hash: '#profile-wallet'}, { id: 'tasks', label: '任务配置', hash: '#tasks' },
{id: 'tasks', label: '任务配置', hash: '#tasks'}, { id: 'recharge-products', label: '充值商品', hash: '#recharge-products' },
{id: 'recharge-products', label: '充值商品', hash: '#recharge-products'}, { id: 'recharge-orders', label: '充值管理', hash: '#recharge-orders' },
{id: 'recharge-orders', label: '充值管理', hash: '#recharge-orders'}, {
{id: 'editor-generation-pricing', label: '模型定价', hash: '#editor-generation-pricing'}, id: 'editor-generation-pricing',
{id: 'editor-showcase', label: '精选审核', hash: '#editor-showcase'}, label: '模型定价',
{id: 'editor-assets', label: '素材查询', hash: '#editor-assets'}, hash: '#editor-generation-pricing',
{id: 'creation-announcement', label: '入口公告', hash: '#creation-announcement'}, },
{id: 'creation-entry', label: '入口开关', hash: '#creation-entry'}, { id: 'editor-showcase', label: '精选审核', hash: '#editor-showcase' },
{id: 'work-visibility', label: '作品可见性', hash: '#work-visibility'}, { id: 'editor-assets', label: '素材查询', hash: '#editor-assets' },
{id: 'accounts', label: '账号管理', hash: '#accounts', ownerOnly: true}, { id: 'accounts', label: '账号管理', hash: '#accounts', ownerOnly: true },
]; ];
export interface AdminRouteAccess { export interface AdminRouteAccess {
@@ -21,8 +21,8 @@ describe('admin tracking event definitions', () => {
expect(keys).toContain('auth_login_options_view'); expect(keys).toContain('auth_login_options_view');
expect(keys).toContain('task_center_view'); expect(keys).toContain('task_center_view');
expect(keys).toContain('asset_upload_ticket_create'); expect(keys).toContain('asset_upload_ticket_create');
expect(keys).toContain('creative_agent_route_success'); expect(keys).not.toContain('creative_agent_route_success');
expect(keys).toContain('work_play_start'); expect(keys).not.toContain('work_play_start');
}); });
test('任务配置候选只开放适合个人任务的事件', () => { test('任务配置候选只开放适合个人任务的事件', () => {
@@ -49,13 +49,15 @@ describe('admin tracking event definitions', () => {
]); ]);
expect(options.find(({ key }) => key === 'work_play_start')?.title).toBe( expect(options.find(({ key }) => key === 'work_play_start')?.title).toBe(
'作品开始游玩', 'work_play_start',
); );
expect(options.find(({ key }) => key === 'unknown_event')?.title).toBe( expect(options.find(({ key }) => key === 'unknown_event')?.title).toBe(
'未知事件', '未知事件',
); );
expect( expect(
filterAdminTrackingEventKeyOptions(options, '作品').map(({ key }) => key), filterAdminTrackingEventKeyOptions(options, 'work_play').map(
({ key }) => key,
),
).toEqual(['work_play_start']); ).toEqual(['work_play_start']);
}); });
@@ -65,7 +67,7 @@ describe('admin tracking event definitions', () => {
).toEqual(['asset_upload_ticket_create']); ).toEqual(['asset_upload_ticket_create']);
expect( expect(
filterAdminTrackingEventDefinitions('work_play').map(({ key }) => key), filterAdminTrackingEventDefinitions('work_play').map(({ key }) => key),
).toEqual(['work_play_start']); ).toEqual([]);
expect(findAdminTrackingEventDefinition(' daily_login ')?.title).toBe( expect(findAdminTrackingEventDefinition(' daily_login ')?.title).toBe(
'每日登录', '每日登录',
); );
@@ -148,42 +148,6 @@ export const adminTrackingEventDefinitions: AdminTrackingEventDefinition[] = [
scopeKind: 'user', scopeKind: 'user',
remark: '领取个人任务奖励成功后记录。', remark: '领取个人任务奖励成功后记录。',
}, },
{
key: 'save_archive_list_view',
title: '存档列表查看',
scopeKind: 'user',
remark: '读取个人存档列表成功后记录。',
},
{
key: 'save_archive_detail_view',
title: '存档详情查看',
scopeKind: 'user',
remark: '读取个人存档详情成功后记录。',
},
{
key: 'browse_history_view',
title: '浏览历史查看',
scopeKind: 'user',
remark: '读取浏览历史成功后记录。',
},
{
key: 'browse_history_record',
title: '浏览历史写入',
scopeKind: 'user',
remark: '记录浏览历史成功后记录。',
},
{
key: 'browse_history_clear',
title: '浏览历史清空',
scopeKind: 'user',
remark: '清空浏览历史成功后记录。',
},
{
key: 'play_stats_view',
title: '游玩统计查看',
scopeKind: 'user',
remark: '读取个人游玩统计成功后记录。',
},
{ {
key: 'profile_analytics_metric_view', key: 'profile_analytics_metric_view',
title: '个人指标查看', title: '个人指标查看',
@@ -352,62 +316,6 @@ export const adminTrackingEventDefinitions: AdminTrackingEventDefinition[] = [
scopeKind: 'user', scopeKind: 'user',
remark: '更新运行设置成功后记录。', remark: '更新运行设置成功后记录。',
}, },
{
key: 'runtime_snapshot_view',
title: '运行快照查看',
scopeKind: 'user',
remark: '读取运行快照成功后记录。',
},
{
key: 'runtime_snapshot_save',
title: '运行快照保存',
scopeKind: 'user',
remark: '保存运行快照成功后记录。',
},
{
key: 'runtime_snapshot_delete',
title: '运行快照删除',
scopeKind: 'user',
remark: '删除运行快照成功后记录。',
},
{
key: 'puzzle_route_success',
title: '拼图路由成功',
scopeKind: 'user',
remark: '拼图运行或创作接口成功响应后兜底记录;GET 入口可能按 site 统计。',
},
{
key: 'match3d_route_success',
title: '抓大鹅路由成功',
scopeKind: 'user',
remark:
'抓大鹅创作或运行接口成功响应后兜底记录;GET 入口可能按 site 统计。',
},
{
key: 'square_hole_route_success',
title: '方洞路由成功',
scopeKind: 'user',
remark: '方洞创作或运行接口成功响应后兜底记录;GET 入口可能按 site 统计。',
},
{
key: 'custom_world_route_success',
title: '自定义世界路由成功',
scopeKind: 'user',
remark: '自定义世界运行接口成功响应后兜底记录;GET 入口可能按 site 统计。',
},
{
key: 'creative_agent_route_success',
title: '创意 Agent 路由成功',
scopeKind: 'user',
remark: '创意 Agent 接口成功响应后兜底记录;GET 入口可能按 site 统计。',
},
{
key: 'work_play_start',
title: '作品开始游玩',
scopeKind: 'work',
remark:
'拼图、抓大鹅、方洞、自定义世界、大鱼吃小鱼、Visual Novel 正式开始游玩时记录。',
},
]; ];
export const adminProfileTaskTrackingEventDefinitions = export const adminProfileTaskTrackingEventDefinitions =
+8 -1
View File
@@ -15,5 +15,12 @@
"noEmit": true, "noEmit": true,
"types": ["vite/client"] "types": ["vite/client"]
}, },
"include": ["src", "vite.config.ts"] "include": ["src", "vite.config.ts"],
"exclude": [
"src/pages/AdminCreationEntrySwitchPage.tsx",
"src/pages/AdminCreationEntrySwitchPage.test.tsx",
"src/pages/AdminGrayReleaseConfigPage.tsx",
"src/pages/AdminGrayReleaseConfigPage.test.tsx",
"src/pages/AdminWorkVisibilityPage.tsx"
]
} }
+2 -1
View File
@@ -68,7 +68,8 @@ npm run container:up -- spacetimedb
spacetime publish genarrative-loadtest --server http://127.0.0.1:13101 --module-path server-rs/crates/spacetime-module --yes --build-options="--debug" spacetime publish genarrative-loadtest --server http://127.0.0.1:13101 --module-path server-rs/crates/spacetime-module --yes --build-options="--debug"
npm run container:up npm run container:up
npm run container:ps npm run container:ps
curl -sS http://127.0.0.1:18080/api/runtime/puzzle/gallery curl -sS -H 'Authorization: Bearer <access-token>' \
'http://127.0.0.1:18080/api/assets/history?kind=character_visual'
``` ```
查看日志: 查看日志:
+1 -1
View File
@@ -118,7 +118,7 @@ services:
soft: 4096 soft: 4096
hard: 4096 hard: 4096
healthcheck: healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/api/runtime/puzzle/gallery"] test: ["CMD", "wget", "-qO-", "http://127.0.0.1/healthz"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 12 retries: 12
+1 -80
View File
@@ -88,85 +88,6 @@ http {
try_files $uri =404; try_files $uri =404;
} }
location = /api/runtime/puzzle/gallery {
default_type application/json;
limit_conn genarrative_api_conn 320;
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
location = /api/runtime/custom-world-gallery {
default_type application/json;
limit_conn genarrative_api_conn 320;
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
location ~ ^/api/runtime/puzzle/gallery/[^/]+$ {
default_type application/json;
limit_conn genarrative_api_conn 32;
limit_req zone=genarrative_api_rps burst=32 nodelay;
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$ {
default_type application/json;
limit_conn genarrative_api_conn 32;
limit_req zone=genarrative_api_rps burst=32 nodelay;
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
location ~ ^/api(?:/|$) { location ~ ^/api(?:/|$) {
default_type application/json; default_type application/json;
@@ -220,7 +141,7 @@ http {
try_files /index.html =404; try_files /index.html =404;
} }
location ~* "^/(?:bark-battle|big-fish|child-motion-demo|creation|creation/baby-object-match|creation/baby-object-match/generating|creation/baby-object-match/result|creation/bark-battle|creation/bark-battle/generating|creation/bark-battle/result|creation/big-fish|creation/big-fish/generating|creation/big-fish/result|creation/creative-agent|creation/jump-hop|creation/jump-hop/generating|creation/jump-hop/result|creation/match3d|creation/match3d/generating|creation/match3d/result|creation/puzzle|creation/puzzle-clear|creation/puzzle-clear/generating|creation/puzzle-clear/result|creation/puzzle/generating|creation/puzzle/result|creation/rpg|creation/rpg/agent|creation/rpg/generating|creation/rpg/result|creation/square-hole|creation/square-hole/generating|creation/square-hole/result|creation/visual-novel|creation/visual-novel/generating|creation/visual-novel/result|creation/wooden-fish|creation/wooden-fish/generating|creation/wooden-fish/result|editor/canvas|gallery/jump-hop/detail|gallery/puzzle/detail|gallery/visual-novel/detail|match3d|project|puzzle|runtime/baby-love-drawing|runtime/baby-object-match|runtime/bark-battle|runtime/big-fish|runtime/jump-hop|runtime/match3d|runtime/puzzle|runtime/puzzle-clear|runtime/rpg/adventure|runtime/rpg/characters|runtime/square-hole|runtime/visual-novel|runtime/wooden-fish|works/detail|worlds/detail)/?$" { location ~* "^/(?:editor/canvas|project)/?$" {
try_files $uri /index.html =404; try_files $uri /index.html =404;
} }
# END GENARRATIVE MAIN SPA ROUTES # END GENARRATIVE MAIN SPA ROUTES
+7 -5
View File
@@ -11,7 +11,7 @@
## gzip ## gzip
- `deploy/nginx/genarrative.conf``deploy/nginx/genarrative-dev-http.conf` 默认开启 gzip。 - `deploy/nginx/genarrative.conf``deploy/nginx/genarrative-dev-http.conf` 默认开启 gzip。
- 覆盖 `application/json`,用于降低 `/api/runtime/*/gallery` 这类 JSON 列表接口的公网带宽占用。 - 覆盖 `application/json`,用于降低 `/api/assets/history` 这类 JSON 列表接口的公网带宽占用。
- 当前推荐等级为 `gzip_comp_level 5`,兼顾 2C/2G 服务器 CPU 与压缩收益。 - 当前推荐等级为 `gzip_comp_level 5`,兼顾 2C/2G 服务器 CPU 与压缩收益。
- Pingora 影子网关已通过 `GENARRATIVE_PINGORA_GATEWAY_COMPRESSION_ALGORITHMS=gzip``GENARRATIVE_PINGORA_GATEWAY_GZIP_ENABLED=true``GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL=5``GENARRATIVE_PINGORA_GATEWAY_GZIP_MIN_LENGTH_BYTES=1024` 承接 gzip parity;网关会把压缩算法收敛到 gzip allowlist,并按 `gzip_min_length 1024` 避免压缩小响应,同时沿用 Nginx `gzip_types` 边界不压缩图片资源。改动后必须运行 `npm run check:pingora-gateway-smoke` 验证小响应不压缩、图片资源不压缩,以及 `Accept-Encoding: gzip``Accept-Encoding: br, gzip` 的大响应都返回 gzip。 - Pingora 影子网关已通过 `GENARRATIVE_PINGORA_GATEWAY_COMPRESSION_ALGORITHMS=gzip``GENARRATIVE_PINGORA_GATEWAY_GZIP_ENABLED=true``GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL=5``GENARRATIVE_PINGORA_GATEWAY_GZIP_MIN_LENGTH_BYTES=1024` 承接 gzip parity;网关会把压缩算法收敛到 gzip allowlist,并按 `gzip_min_length 1024` 避免压缩小响应,同时沿用 Nginx `gzip_types` 边界不压缩图片资源。改动后必须运行 `npm run check:pingora-gateway-smoke` 验证小响应不压缩、图片资源不压缩,以及 `Accept-Encoding: gzip``Accept-Encoding: br, gzip` 的大响应都返回 gzip。
@@ -65,14 +65,14 @@
## Pingora canary ## Pingora canary
- `deploy/nginx/snippets/genarrative-pingora-canary.conf` 是默认不启用的人工前缀 canary 入口,只在需要验证 Nginx -> Pingora handoff 时临时 include。它使用 `/__genarrative_pingora_canary/` 前缀改写后转发到 `127.0.0.1:18081`,并返回 `X-Genarrative-Nginx-Handoff: pingora-canary` - `deploy/nginx/snippets/genarrative-pingora-canary.conf` 是默认不启用的人工前缀 canary 入口,只在需要验证 Nginx -> Pingora handoff 时临时 include。它使用 `/__genarrative_pingora_canary/` 前缀改写后转发到 `127.0.0.1:18081`,并返回 `X-Genarrative-Nginx-Handoff: pingora-canary`
- `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 是前缀 canary 之后、direct 直连之前的真实路径 canary。它只能在 Nginx `http` 上下文人工 include,默认监听 `127.0.0.1:18083`,覆盖 `/api/creation-entry/config``/v1/identity``/v1/database/<db>/subscribe``/assets/app.js` 和拒绝入口,并写入独立 `/var/log/nginx/genarrative-pingora-realpath-canary.access.log`;不要把它 include 到生产 `443` server 内作为 location 覆盖。 - `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 是前缀 canary 之后、direct 直连之前的真实路径 canary。它只能在 Nginx `http` 上下文人工 include,默认监听 `127.0.0.1:18083`,覆盖 `/api/assets/history``/v1/identity``/v1/database/<db>/subscribe``/assets/app.js` 和拒绝入口,并写入独立 `/var/log/nginx/genarrative-pingora-realpath-canary.access.log`;不要把它 include 到生产 `443` server 内作为 location 覆盖。
- 真实路径 canary 片段使用 `access_log ... genarrative_upstream`,如果以独立文件放入 `/etc/nginx/conf.d/`,文件加载顺序必须晚于定义 `log_format genarrative_upstream` 的主站配置。当前目标机可使用 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`;若站点把 `log_format` 移到全局 Nginx 配置,则需确保它仍在所有 `conf.d` server include 之前加载。启用前必须先跑 `nginx -t`,不要用会早于 `genarrative.conf` 加载的文件名。 - 真实路径 canary 片段使用 `access_log ... genarrative_upstream`,如果以独立文件放入 `/etc/nginx/conf.d/`,文件加载顺序必须晚于定义 `log_format genarrative_upstream` 的主站配置。当前目标机可使用 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`;若站点把 `log_format` 移到全局 Nginx 配置,则需确保它仍在所有 `conf.d` server include 之前加载。启用前必须先跑 `nginx -t`,不要用会早于 `genarrative.conf` 加载的文件名。
- 启用 Nginx canary 前,目标机必须已经有持久 `genarrative-pingora-gateway.service` 运行在 `127.0.0.1:18081`,且带真实 `api-server`、SpacetimeDB、静态目录和 `/var/log/genarrative/pingora-gateway.access.log` 完成本机 shadow 验收;不要从一次性 `/tmp` 网关进程直接切到 Nginx handoff。 - 启用 Nginx canary 前,目标机必须已经有持久 `genarrative-pingora-gateway.service` 运行在 `127.0.0.1:18081`,且带真实 `api-server`、SpacetimeDB、静态目录和 `/var/log/genarrative/pingora-gateway.access.log` 完成本机 shadow 验收;不要从一次性 `/tmp` 网关进程直接切到 Nginx handoff。
- 启用前必须运行 `npm run check:nginx-pingora-canary`;目标 agent 有 Nginx 时运行 `node scripts/check-nginx-pingora-canary.mjs --require-nginx`,同时做两个 snippet 的静态护栏和真实 `nginx -t`。本机或 CI 可运行 `npm run check:pingora-canary-docker`,用 Docker Nginx、真实 Pingora 和 mock 上游复现前缀 canary 与真实路径 canary 两条 handoff 链路。 - 启用前必须运行 `npm run check:nginx-pingora-canary`;目标 agent 有 Nginx 时运行 `node scripts/check-nginx-pingora-canary.mjs --require-nginx`,同时做两个 snippet 的静态护栏和真实 `nginx -t`。本机或 CI 可运行 `npm run check:pingora-canary-docker`,用 Docker Nginx、真实 Pingora 和 mock 上游复现前缀 canary 与真实路径 canary 两条 handoff 链路。
- 前缀 canary reload 后运行 `GENARRATIVE_PINGORA_CANARY_BASE_URL=http://127.0.0.1 GENARRATIVE_PINGORA_CANARY_HOST=<域名> npm run check:pingora-canary-live`,再用 `scripts/check-pingora-canary-access-log-parity.mjs` 对账 `/var/log/nginx/genarrative.access.log``/var/log/genarrative/pingora-gateway.access.log` - 前缀 canary reload 后运行 `GENARRATIVE_PINGORA_CANARY_BASE_URL=http://127.0.0.1 GENARRATIVE_PINGORA_CANARY_HOST=<域名> npm run check:pingora-canary-live`,再用 `scripts/check-pingora-canary-access-log-parity.mjs` 对账 `/var/log/nginx/genarrative.access.log``/var/log/genarrative/pingora-gateway.access.log`
- 真实路径 canary 不再手工编辑 `/etc/nginx/conf.d/`。目标机从 current release 执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-enable.sh --apply --probe-token <token> --host <域名> --base-url http://127.0.0.1:18083`;脚本会渲染到 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`,保持晚于主站 `log_format genarrative_upstream` 加载,执行 `nginx -t`、reload Nginx,并默认运行 realpath live smoke。`nginx -t`、reload 或 live smoke 任一失败都会恢复写入前配置。关闭时执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-disable.sh --apply`,脚本会删除该 `zz-...` 文件,执行 `nginx -t` 和 reload,失败时恢复删除前配置。 - 真实路径 canary 不再手工编辑 `/etc/nginx/conf.d/`。目标机从 current release 执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-enable.sh --apply --probe-token <token> --host <域名> --base-url http://127.0.0.1:18083`;脚本会渲染到 `/etc/nginx/conf.d/zz-genarrative-pingora-realpath-canary.conf`,保持晚于主站 `log_format genarrative_upstream` 加载,执行 `nginx -t`、reload Nginx,并默认运行 realpath live smoke。`nginx -t`、reload 或 live smoke 任一失败都会恢复写入前配置。关闭时执行 `/opt/genarrative/current/scripts/deploy/pingora-realpath-canary-disable.sh --apply`,脚本会删除该 `zz-...` 文件,执行 `nginx -t` 和 reload,失败时恢复删除前配置。
- `npm run check:pingora-release-readiness` 默认包含 `npm run check:pingora-realpath-canary-toggle`,用临时 Nginx 目录和 fake `nginx` / `systemctl` / live smoke 验证 realpath canary 启停脚本默认 dry-run、apply 写入、失败回滚和 disable 恢复逻辑;不再把这条脚本护栏留成单独可选项。 - `npm run check:pingora-release-readiness` 默认包含 `npm run check:pingora-realpath-canary-toggle`,用临时 Nginx 目录和 fake `nginx` / `systemctl` / live smoke 验证 realpath canary 启停脚本默认 dry-run、apply 写入、失败回滚和 disable 恢复逻辑;不再把这条脚本护栏留成单独可选项。
- 真实路径 canary reload 后可再次运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-access-log-parity.mjs --realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_realpath_canary/healthz --path /api/creation-entry/config --path /v1/identity --path /assets/app.js`。真实路径模式除 healthz 探针外要求 Nginx path 与 Pingora path 完全一致。 - 真实路径 canary reload 后可再次运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-live.mjs --realpath --base-url http://127.0.0.1:18083 --host <域名>`,再运行 `node -- /opt/genarrative/current/scripts/check-pingora-canary-access-log-parity.mjs --realpath --nginx-log-file /var/log/nginx/genarrative-pingora-realpath-canary.access.log --pingora-log-file /var/log/genarrative/pingora-gateway.access.log --path /__genarrative_pingora_realpath_canary/healthz --path /api/assets/history --path /v1/identity --path /assets/app.js`。真实路径模式除 healthz 探针外要求 Nginx path 与 Pingora path 完全一致。
- 正式切换前在源码 checkout / CI / 构建环境跑聚合门禁,并按目标机已启用的 canary 入口选择参数:前缀 canary 已启用时使用 `node scripts/check-pingora-release-readiness.mjs --require-docker --pull-docker --require-nginx --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 已启用时追加或单独使用 `--require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`。如果现场只启用了真实路径 canary,不要同时传 `--require-live` - 正式切换前在源码 checkout / CI / 构建环境跑聚合门禁,并按目标机已启用的 canary 入口选择参数:前缀 canary 已启用时使用 `node scripts/check-pingora-release-readiness.mjs --require-docker --pull-docker --require-nginx --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 已启用时追加或单独使用 `--require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`。如果现场只启用了真实路径 canary,不要同时传 `--require-live`
- 目标机 `/opt/genarrative/current` 只能跑随包 runtime-only 门禁,并同样按现场已启用入口选择:前缀 canary 用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;验证 Pingora 直连入口时再追加 `--require-direct ...` 参数。未带 `--require-direct` 的 runtime-only 基础门禁会自动执行直连彩排状态复核,确认公网 `80/443` 仍由 Nginx 接流、Pingora shadow `127.0.0.1:18081`、realpath canary `127.0.0.1:18083` 和 current release 自审均通过;启用后 `--require-direct` 复核不再要求 Nginx 接公网彩排状态。生成正式直连切换 runbook 时,`--direct-redirect-host` 和回退 smoke Host 必须与 `--direct-host` 同 hostname,只允许端口不同。 - 目标机 `/opt/genarrative/current` 只能跑随包 runtime-only 门禁,并同样按现场已启用入口选择:前缀 canary 用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-live --live-base-url http://127.0.0.1 --live-host <域名> --live-nginx-access-log /var/log/nginx/genarrative.access.log --live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;真实路径 canary 用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-realpath-live --realpath-live-base-url http://127.0.0.1:18083 --realpath-live-host <域名> --realpath-live-nginx-access-log /var/log/nginx/genarrative-pingora-realpath-canary.access.log --realpath-live-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`;验证 Pingora 直连入口时再追加 `--require-direct ...` 参数。未带 `--require-direct` 的 runtime-only 基础门禁会自动执行直连彩排状态复核,确认公网 `80/443` 仍由 Nginx 接流、Pingora shadow `127.0.0.1:18081`、realpath canary `127.0.0.1:18083` 和 current release 自审均通过;启用后 `--require-direct` 复核不再要求 Nginx 接公网彩排状态。生成正式直连切换 runbook 时,`--direct-redirect-host` 和回退 smoke Host 必须与 `--direct-host` 同 hostname,只允许端口不同。
@@ -84,11 +84,13 @@ ls -l /etc/nginx/modules-enabled/*brotli*
nginx -T 2>/dev/null | grep -Ei 'brotli|load_module' nginx -T 2>/dev/null | grep -Ei 'brotli|load_module'
curl -sSI -H 'Accept-Encoding: gzip' \ curl -sSI -H 'Accept-Encoding: gzip' \
http://<host>/api/runtime/puzzle/gallery \ -H 'Authorization: Bearer <access-token>' \
'http://<host>/api/assets/history?kind=character_visual' \
| grep -iE 'content-encoding|vary|content-type|content-length' | grep -iE 'content-encoding|vary|content-type|content-length'
curl -sSI -H 'Accept-Encoding: br' \ curl -sSI -H 'Accept-Encoding: br' \
http://<host>/api/runtime/puzzle/gallery \ -H 'Authorization: Bearer <access-token>' \
'http://<host>/api/assets/history?kind=character_visual' \
| grep -iE 'content-encoding|vary|content-type|content-length' | grep -iE 'content-encoding|vary|content-type|content-length'
``` ```
+1 -96
View File
@@ -116,101 +116,6 @@ server {
try_files $uri =404; try_files $uri =404;
} }
location = /api/runtime/puzzle/gallery {
default_type application/json;
limit_conn genarrative_api_conn 320;
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
if ($genarrative_maintenance) {
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
}
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
location = /api/runtime/custom-world-gallery {
default_type application/json;
limit_conn genarrative_api_conn 320;
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
if ($genarrative_maintenance) {
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
}
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
location ~ ^/api/runtime/puzzle/gallery/[^/]+$ {
default_type application/json;
limit_conn genarrative_api_conn 32;
limit_req zone=genarrative_api_rps burst=32 nodelay;
if ($genarrative_maintenance) {
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
}
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$ {
default_type application/json;
limit_conn genarrative_api_conn 32;
limit_req zone=genarrative_api_rps burst=32 nodelay;
if ($genarrative_maintenance) {
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
}
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
# 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。 # 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。
location ~ ^/api(?:/|$) { location ~ ^/api(?:/|$) {
@@ -285,7 +190,7 @@ server {
try_files /index.html =404; try_files /index.html =404;
} }
location ~* "^/(?:bark-battle|big-fish|child-motion-demo|creation|creation/baby-object-match|creation/baby-object-match/generating|creation/baby-object-match/result|creation/bark-battle|creation/bark-battle/generating|creation/bark-battle/result|creation/big-fish|creation/big-fish/generating|creation/big-fish/result|creation/creative-agent|creation/jump-hop|creation/jump-hop/generating|creation/jump-hop/result|creation/match3d|creation/match3d/generating|creation/match3d/result|creation/puzzle|creation/puzzle-clear|creation/puzzle-clear/generating|creation/puzzle-clear/result|creation/puzzle/generating|creation/puzzle/result|creation/rpg|creation/rpg/agent|creation/rpg/generating|creation/rpg/result|creation/square-hole|creation/square-hole/generating|creation/square-hole/result|creation/visual-novel|creation/visual-novel/generating|creation/visual-novel/result|creation/wooden-fish|creation/wooden-fish/generating|creation/wooden-fish/result|editor/canvas|gallery/jump-hop/detail|gallery/puzzle/detail|gallery/visual-novel/detail|match3d|project|puzzle|runtime/baby-love-drawing|runtime/baby-object-match|runtime/bark-battle|runtime/big-fish|runtime/jump-hop|runtime/match3d|runtime/puzzle|runtime/puzzle-clear|runtime/rpg/adventure|runtime/rpg/characters|runtime/square-hole|runtime/visual-novel|runtime/wooden-fish|works/detail|worlds/detail)/?$" { location ~* "^/(?:editor/canvas|project)/?$" {
error_page 503 /maintenance.html; error_page 503 /maintenance.html;
if ($genarrative_maintenance) { if ($genarrative_maintenance) {
+1 -96
View File
@@ -136,101 +136,6 @@ server {
try_files $uri =404; try_files $uri =404;
} }
location = /api/runtime/puzzle/gallery {
default_type application/json;
limit_conn genarrative_api_conn 320;
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
if ($genarrative_maintenance) {
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
}
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
location = /api/runtime/custom-world-gallery {
default_type application/json;
limit_conn genarrative_api_conn 320;
limit_req zone=genarrative_gallery_rps burst=4096 nodelay;
if ($genarrative_maintenance) {
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
}
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
location ~ ^/api/runtime/puzzle/gallery/[^/]+$ {
default_type application/json;
limit_conn genarrative_api_conn 32;
limit_req zone=genarrative_api_rps burst=32 nodelay;
if ($genarrative_maintenance) {
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
}
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$ {
default_type application/json;
limit_conn genarrative_api_conn 32;
limit_req zone=genarrative_api_rps burst=32 nodelay;
if ($genarrative_maintenance) {
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
}
proxy_pass http://genarrative_api;
proxy_http_version 1.1;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
add_header X-Accel-Buffering no always;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-Id $request_id;
}
# 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。 # 临时兼容主站仍在使用的 /api/* HTTP facade;前端完成 SpacetimeDB SDK 迁移后删除。
location ~ ^/api(?:/|$) { location ~ ^/api(?:/|$) {
@@ -305,7 +210,7 @@ server {
try_files /index.html =404; try_files /index.html =404;
} }
location ~* "^/(?:bark-battle|big-fish|child-motion-demo|creation|creation/baby-object-match|creation/baby-object-match/generating|creation/baby-object-match/result|creation/bark-battle|creation/bark-battle/generating|creation/bark-battle/result|creation/big-fish|creation/big-fish/generating|creation/big-fish/result|creation/creative-agent|creation/jump-hop|creation/jump-hop/generating|creation/jump-hop/result|creation/match3d|creation/match3d/generating|creation/match3d/result|creation/puzzle|creation/puzzle-clear|creation/puzzle-clear/generating|creation/puzzle-clear/result|creation/puzzle/generating|creation/puzzle/result|creation/rpg|creation/rpg/agent|creation/rpg/generating|creation/rpg/result|creation/square-hole|creation/square-hole/generating|creation/square-hole/result|creation/visual-novel|creation/visual-novel/generating|creation/visual-novel/result|creation/wooden-fish|creation/wooden-fish/generating|creation/wooden-fish/result|editor/canvas|gallery/jump-hop/detail|gallery/puzzle/detail|gallery/visual-novel/detail|match3d|project|puzzle|runtime/baby-love-drawing|runtime/baby-object-match|runtime/bark-battle|runtime/big-fish|runtime/jump-hop|runtime/match3d|runtime/puzzle|runtime/puzzle-clear|runtime/rpg/adventure|runtime/rpg/characters|runtime/square-hole|runtime/visual-novel|runtime/wooden-fish|works/detail|worlds/detail)/?$" { location ~* "^/(?:editor/canvas|project)/?$" {
error_page 503 /maintenance.html; error_page 503 /maintenance.html;
if ($genarrative_maintenance) { if ($genarrative_maintenance) {
@@ -26,7 +26,7 @@ server {
proxy_pass http://127.0.0.1:18081/__genarrative_pingora/healthz; proxy_pass http://127.0.0.1:18081/__genarrative_pingora/healthz;
} }
location = /api/creation-entry/config { location = /api/assets/history {
default_type application/json; default_type application/json;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_buffering off; proxy_buffering off;
+129 -137
View File
@@ -16,7 +16,9 @@
"root /var/www/html;" "root /var/www/html;"
] ]
}, },
"docs": ["`/.well-known/acme-challenge/*`"] "docs": [
"`/.well-known/acme-challenge/*`"
]
}, },
{ {
"id": "shadow_probe", "id": "shadow_probe",
@@ -24,7 +26,9 @@
"expect": { "expect": {
"kind": "shadow_probe" "kind": "shadow_probe"
}, },
"docs": ["`/__genarrative_pingora/healthz`"] "docs": [
"`/__genarrative_pingora/healthz`"
]
}, },
{ {
"id": "admin_redirect", "id": "admin_redirect",
@@ -34,10 +38,19 @@
"location": "/admin/" "location": "/admin/"
}, },
"nginx": { "nginx": {
"production": ["location = /admin", "return 301 /admin/;"], "production": [
"development": ["location = /admin", "return 301 /admin/;"] "location = /admin",
"return 301 /admin/;"
],
"development": [
"location = /admin",
"return 301 /admin/;"
]
}, },
"docs": ["`/admin`", "301 到 `/admin/`"] "docs": [
"`/admin`",
"301 到 `/admin/`"
]
}, },
{ {
"id": "admin_api_proxy", "id": "admin_api_proxy",
@@ -62,7 +75,9 @@
"limit_req zone=genarrative_admin_rps burst=16 nodelay;" "limit_req zone=genarrative_admin_rps burst=16 nodelay;"
] ]
}, },
"docs": ["`/admin/api/*`"] "docs": [
"`/admin/api/*`"
]
}, },
{ {
"id": "admin_assets", "id": "admin_assets",
@@ -73,10 +88,18 @@
"mode": "exact" "mode": "exact"
}, },
"nginx": { "nginx": {
"production": ["location ^~ /admin/assets/", "try_files $uri =404;"], "production": [
"development": ["location ^~ /admin/assets/", "try_files $uri =404;"] "location ^~ /admin/assets/",
"try_files $uri =404;"
],
"development": [
"location ^~ /admin/assets/",
"try_files $uri =404;"
]
}, },
"docs": ["`/admin/assets/*`"] "docs": [
"`/admin/assets/*`"
]
}, },
{ {
"id": "admin_spa_fallback", "id": "admin_spa_fallback",
@@ -96,7 +119,9 @@
"try_files $uri $uri/ /admin/index.html;" "try_files $uri $uri/ /admin/index.html;"
] ]
}, },
"docs": ["`/admin/*`"] "docs": [
"`/admin/*`"
]
}, },
{ {
"id": "web_assets", "id": "web_assets",
@@ -106,115 +131,23 @@
"root": "web", "root": "web",
"mode": "exact" "mode": "exact"
}, },
"nginx": {
"production": ["location ^~ /assets/", "try_files $uri =404;"],
"development": ["location ^~ /assets/", "try_files $uri =404;"]
},
"docs": ["`/assets/*`"]
},
{
"id": "puzzle_gallery_list",
"samplePath": "/api/runtime/puzzle/gallery",
"expect": {
"kind": "proxy",
"target": "api",
"bodyLimit": null,
"protectionClass": "gallery_list"
},
"nginx": { "nginx": {
"production": [ "production": [
"location = /api/runtime/puzzle/gallery", "location ^~ /assets/",
"limit_conn genarrative_api_conn 320;", "try_files $uri =404;"
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
"add_header X-Accel-Buffering no always;"
], ],
"development": [ "development": [
"location = /api/runtime/puzzle/gallery", "location ^~ /assets/",
"limit_conn genarrative_api_conn 320;", "try_files $uri =404;"
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
"add_header X-Accel-Buffering no always;"
] ]
}, },
"docs": ["`/api/runtime/puzzle/gallery`"] "docs": [
}, "`/assets/*`"
{ ]
"id": "custom_world_gallery_list",
"samplePath": "/api/runtime/custom-world-gallery",
"expect": {
"kind": "proxy",
"target": "api",
"bodyLimit": null,
"protectionClass": "gallery_list"
},
"nginx": {
"production": [
"location = /api/runtime/custom-world-gallery",
"limit_conn genarrative_api_conn 320;",
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
"add_header X-Accel-Buffering no always;"
],
"development": [
"location = /api/runtime/custom-world-gallery",
"limit_conn genarrative_api_conn 320;",
"limit_req zone=genarrative_gallery_rps burst=4096 nodelay;",
"add_header X-Accel-Buffering no always;"
]
},
"docs": ["`/api/runtime/custom-world-gallery`"]
},
{
"id": "puzzle_gallery_detail",
"samplePath": "/api/runtime/puzzle/gallery/work-a",
"expect": {
"kind": "proxy",
"target": "api",
"bodyLimit": null,
"protectionClass": "gallery_detail"
},
"nginx": {
"production": [
"location ~ ^/api/runtime/puzzle/gallery/[^/]+$",
"limit_conn genarrative_api_conn 32;",
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
"add_header X-Accel-Buffering no always;"
],
"development": [
"location ~ ^/api/runtime/puzzle/gallery/[^/]+$",
"limit_conn genarrative_api_conn 32;",
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
"add_header X-Accel-Buffering no always;"
]
},
"docs": ["`/api/runtime/puzzle/gallery/{id}`"]
},
{
"id": "custom_world_gallery_detail",
"samplePath": "/api/runtime/custom-world-gallery/profile-a/owner-b",
"expect": {
"kind": "proxy",
"target": "api",
"bodyLimit": null,
"protectionClass": "gallery_detail"
},
"nginx": {
"production": [
"location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$",
"limit_conn genarrative_api_conn 32;",
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
"add_header X-Accel-Buffering no always;"
],
"development": [
"location ~ ^/api/runtime/custom-world-gallery/[^/]+/[^/]+$",
"limit_conn genarrative_api_conn 32;",
"limit_req zone=genarrative_api_rps burst=32 nodelay;",
"add_header X-Accel-Buffering no always;"
]
},
"docs": ["`/api/runtime/custom-world-gallery/{profile}/{owner}`"]
}, },
{ {
"id": "generic_api_proxy", "id": "generic_api_proxy",
"samplePath": "/api/creation-entry/config", "samplePath": "/api/assets/history",
"expect": { "expect": {
"kind": "proxy", "kind": "proxy",
"target": "api", "target": "api",
@@ -237,7 +170,9 @@
"add_header X-Accel-Buffering no always;" "add_header X-Accel-Buffering no always;"
] ]
}, },
"docs": ["`/api`、`/api/*`"] "docs": [
"`/api`、`/api/*`"
]
}, },
{ {
"id": "spacetime_subscribe", "id": "spacetime_subscribe",
@@ -264,7 +199,9 @@
"proxy_read_timeout 3600s;" "proxy_read_timeout 3600s;"
] ]
}, },
"docs": ["`/v1/database/{db}/subscribe`"] "docs": [
"`/v1/database/{db}/subscribe`"
]
}, },
{ {
"id": "spacetime_identity", "id": "spacetime_identity",
@@ -289,7 +226,9 @@
"proxy_set_header Connection \"Upgrade\";" "proxy_set_header Connection \"Upgrade\";"
] ]
}, },
"docs": ["`/v1/identity*`"] "docs": [
"`/v1/identity*`"
]
}, },
{ {
"id": "v1_forbidden", "id": "v1_forbidden",
@@ -298,10 +237,18 @@
"kind": "not_found" "kind": "not_found"
}, },
"nginx": { "nginx": {
"production": ["location ^~ /v1/", "return 404;"], "production": [
"development": ["location ^~ /v1/", "return 404;"] "location ^~ /v1/",
"return 404;"
],
"development": [
"location ^~ /v1/",
"return 404;"
]
}, },
"docs": ["`/v1/*`"] "docs": [
"`/v1/*`"
]
}, },
{ {
"id": "healthz_forbidden", "id": "healthz_forbidden",
@@ -319,7 +266,9 @@
"return 404;" "return 404;"
] ]
}, },
"docs": ["`/healthz*`"] "docs": [
"`/healthz*`"
]
}, },
{ {
"id": "readyz_forbidden", "id": "readyz_forbidden",
@@ -337,7 +286,9 @@
"return 404;" "return 404;"
] ]
}, },
"docs": ["`/readyz*`"] "docs": [
"`/readyz*`"
]
}, },
{ {
"id": "generated_assets_forbidden", "id": "generated_assets_forbidden",
@@ -355,11 +306,13 @@
"return 404;" "return 404;"
] ]
}, },
"docs": ["`/generated-*`"] "docs": [
"`/generated-*`"
]
}, },
{ {
"id": "web_spa_fallback", "id": "web_spa_fallback",
"samplePath": "/creation/puzzle/result", "samplePath": "/project",
"expect": { "expect": {
"kind": "static", "kind": "static",
"root": "web", "root": "web",
@@ -375,21 +328,33 @@
"try_files $uri /index.html =404;" "try_files $uri /index.html =404;"
] ]
}, },
"docs": ["主站 SPA allowlist", "失败回退 `/index.html`"] "docs": [
"主站 SPA allowlist",
"失败回退 `/index.html`"
]
}, },
{ {
"id": "web_spa_case_trailing_slash", "id": "web_spa_case_trailing_slash",
"samplePath": "/CREATION/PUZZLE/RESULT/", "samplePath": "/PROJECT/",
"expect": { "expect": {
"kind": "static", "kind": "static",
"root": "web", "root": "web",
"mode": "spa_fallback" "mode": "spa_fallback"
}, },
"nginx": { "nginx": {
"production": ["location ~*", "try_files $uri /index.html =404;"], "production": [
"development": ["location ~*", "try_files $uri /index.html =404;"] "location ~*",
"try_files $uri /index.html =404;"
],
"development": [
"location ~*",
"try_files $uri /index.html =404;"
]
}, },
"docs": ["大小写不敏感", "一个尾部斜杠"] "docs": [
"大小写不敏感",
"一个尾部斜杠"
]
}, },
{ {
"id": "web_unknown_path_exact", "id": "web_unknown_path_exact",
@@ -400,10 +365,19 @@
"mode": "exact" "mode": "exact"
}, },
"nginx": { "nginx": {
"production": ["location /", "try_files $uri $uri/ =404;"], "production": [
"development": ["location /", "try_files $uri $uri/ =404;"] "location /",
"try_files $uri $uri/ =404;"
],
"development": [
"location /",
"try_files $uri $uri/ =404;"
]
}, },
"docs": ["其它 Web 路径", "缺失时返回真实 404"] "docs": [
"其它 Web 路径",
"缺失时返回真实 404"
]
}, },
{ {
"id": "creation_unknown_path_exact", "id": "creation_unknown_path_exact",
@@ -414,10 +388,16 @@
"mode": "exact" "mode": "exact"
}, },
"nginx": { "nginx": {
"production": ["try_files $uri $uri/ =404;"], "production": [
"development": ["try_files $uri $uri/ =404;"] "try_files $uri $uri/ =404;"
],
"development": [
"try_files $uri $uri/ =404;"
]
}, },
"docs": ["`/creation/not-exist`"] "docs": [
"`/creation/not-exist`"
]
}, },
{ {
"id": "runtime_unknown_path_exact", "id": "runtime_unknown_path_exact",
@@ -428,10 +408,16 @@
"mode": "exact" "mode": "exact"
}, },
"nginx": { "nginx": {
"production": ["try_files $uri $uri/ =404;"], "production": [
"development": ["try_files $uri $uri/ =404;"] "try_files $uri $uri/ =404;"
],
"development": [
"try_files $uri $uri/ =404;"
]
}, },
"docs": ["`/runtime/not-exist`"] "docs": [
"`/runtime/not-exist`"
]
}, },
{ {
"id": "puzzle_unknown_path_exact", "id": "puzzle_unknown_path_exact",
@@ -442,10 +428,16 @@
"mode": "exact" "mode": "exact"
}, },
"nginx": { "nginx": {
"production": ["try_files $uri $uri/ =404;"], "production": [
"development": ["try_files $uri $uri/ =404;"] "try_files $uri $uri/ =404;"
],
"development": [
"try_files $uri $uri/ =404;"
]
}, },
"docs": ["`/puzzle/not-exist`"] "docs": [
"`/puzzle/not-exist`"
]
} }
] ]
} }
@@ -4213,3 +4213,6 @@
- 决策:旧创作模板、旧创作入口及其专属运行服务进入下线范围,不再为跳一跳、抓大鹅 Match3D、儿童动作 Demo 等旧链路修复兼容问题、补生成脚本或维持专属门禁。 - 决策:旧创作模板、旧创作入口及其专属运行服务进入下线范围,不再为跳一跳、抓大鹅 Match3D、儿童动作 Demo 等旧链路修复兼容问题、补生成脚本或维持专属门禁。
- 边界:共享账号、钱包、资产、图片编辑器、公开作品、通用 HostBridge、API、SpacetimeDB、发布运维和安全能力不属于旧链路,仍需维持正式门禁。历史文档只作为背景材料,不再作为旧入口继续运行的依据。 - 边界:共享账号、钱包、资产、图片编辑器、公开作品、通用 HostBridge、API、SpacetimeDB、发布运维和安全能力不属于旧链路,仍需维持正式门禁。历史文档只作为背景材料,不再作为旧入口继续运行的依据。
- 清理方式:允许直接删除已经失效的旧素材生成命令、入口路由、专属服务和对应测试;删除工程链路时仍需核对是否被当前共享能力引用,不能连带移除仍在使用的公共契约或持久化事实。 - 清理方式:允许直接删除已经失效的旧素材生成命令、入口路由、专属服务和对应测试;删除工程链路时仍需核对是否被当前共享能力引用,不能连带移除仍在使用的公共契约或持久化事实。
- 最终落地:本次退役范围覆盖整个旧创作模板体系,包括 RPG / 自定义世界、拼图、拼消消、大鱼吃小鱼、敲木鱼、方洞挑战、视觉小说、汪汪声浪、寓教于乐、Creative Agent、Match3D、跳一跳和儿童动作 Demo。全部相关历史表继续作为数据壳参与 `spacetime-module` 编译,`migration.rs` 白名单与历史数据不变;旧 reducer/procedure/view、API 路由/handler/worker、前端页面/工作台/运行态、共享业务 DTO 和纯业务 crate 从编译链与依赖图移除,但旧源码和素材保留在仓库中用于历史追溯。
- 兼容读取:只保留历史审计、迁移和资产归属核对所需的最小读取定义;旧 `worldType`、公开作品号、URL、详情页和专属运行态均不再形成用户可访问入口。
- 方案文档:`docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md`
@@ -10,12 +10,7 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台,把 A
## 当前主要能力 ## 当前主要能力
- RPG / 自定义世界创作与运行时 - 图片画布编辑器、项目与资产管理
- 拼图玩法创作、草稿、发布、运行态和排行榜。
- 拼消消玩法创作、素材图集生成、结果页、发布、统一作品详情、正式运行态和基础统计。
- 敲木鱼玩法创作、草稿、发布、运行态、公开详情和分享码。
- 抓大鹅 Match3D 创作、2D 多视角素材生成、发布和运行态。
- 大鱼吃小鱼、方洞挑战、视觉小说、汪汪声浪和儿童向寓教于乐玩法。
- 账号、短信 / 密码 / 微信登录、个人资料、任务、钱包、邀请码、充值、反馈、法律信息和后台管理。 - 账号、短信 / 密码 / 微信登录、个人资料、任务、钱包、邀请码、充值、反馈、法律信息和后台管理。
## 当前入口 ## 当前入口
@@ -50,6 +45,8 @@ server-rs + Axum + SpacetimeDB
明确废弃:旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud`、人工 `spacetime --root-dir` 口径,以及前端承接正式业务真相的路线。 明确废弃:旧 `server-node`、Express、PostgreSQL、Go 服务端、`maincloud`、人工 `spacetime --root-dir` 口径,以及前端承接正式业务真相的路线。
全部旧创作模板及其创作、生成、发布、公开业务详情与专属运行态已于 2026-07-17 退役。相关历史持久化表只以最小 schema 数据壳继续参与 `spacetime-module` 编译,迁移白名单与历史数据保持不变;旧前端、API、worker、procedure/reducer 和纯业务 crate 源码仅供追溯,不属于当前编译或运行能力。
## 当前文档入口 ## 当前文档入口
- `docs/README.md` - `docs/README.md`
@@ -0,0 +1,62 @@
# 旧创作模板业务退役方案
更新时间:`2026-07-17`
## 目标
退役整个旧创作模板体系及其专属运行态,同时保留全部相关历史持久化表、迁移白名单和必要的兼容读取定义。历史数据不删除,持久化表及字段不删除、不改名、不重排、不改变类型。
本次执行口径是“数据壳保留,业务实现退役”:历史表继续以最小 schema 代码随 `spacetime-module` 编译和发布;创作入口、模板选择、生成、发布、公开业务详情、作品广场、排行榜和专属运行态不再进入正式编译链或运行路由。
## 范围
### 退役
- RPG / 自定义世界、拼图、拼消消、大鱼吃小鱼、敲木鱼、方洞挑战、视觉小说、汪汪声浪、寓教于乐、Creative Agent、Match3D、跳一跳和儿童动作 Demo 的前端页面、路由、工作台、结果页、运行态、service、测试及专属素材生成入口。
- `api-server` 中上述业务的 router、handler、service、生成与发布编排、公开详情、专属 runtime 和 worker 启动点。
- `spacetime-client` 的模板业务 facade、mapper、reducer/procedure 调用和业务 DTO 依赖。
- `spacetime-module` 的模板 reducer、procedure、业务 view、初始化逻辑和领域规则调用。
- 所有纯模板 crate 及 RPG 专属运行态 crate 的 workspace/default 目标和在运依赖边。
- 后台及主站中只服务于旧创作入口的配置、灰度、展示、搜索、启动、追踪和运维门禁。
### 保留
- 全部旧模板历史持久化表及其原有字段顺序、字段类型、默认值、索引和可见性。
- `migration.rs` 中相关表的迁移白名单、表名兼容和字段目录。
- 为历史审计、迁移、资产归属核对所必需的最小只读表定义;不得借兼容读取重新暴露旧创作、发布、公开详情或运行接口。
- 编辑器、项目、账号、钱包、资产、HostBridge、运维和安全等平台公共能力。
- 旧页面、测试、素材、handler、service、worker、生成 bindings 和纯业务 crate 的源码目录;它们仅用于历史追溯,不属于任何正式入口或编译目标。
## 编译边界
### 前端
- 主路由、Vite 入口和运行时动态 import 不得引用旧业务目录。
- 正式应用入口使用 `active-main.tsx``ActiveApp.tsx``routing/activeAppRoutes.tsx``routing/activeAppPageRoutes.ts``services/activeAppTitle.ts``platform-entry/PlatformEntryActiveFlowShell.tsx``platformEntryActiveTypes.ts`;原同名非 active 文件保持历史源码原貌并退出 Vite、TypeScript、ESLint 和 Vitest。
- 旧业务源码与素材保留在仓库中;Vite 不得再引用入口或动态 importTypeScript、ESLint、Vitest 必须明确排除旧目录和专属测试。退役源码只用于历史追溯,不允许从在运代码重新导入。
-`main.tsx``App.tsx`、旧路由、旧标题映射、`PlatformEntryFlowShellImpl.tsx` 与旧入口类型保持原样;正式链路由 `active-main.tsx``ActiveApp.tsx``activeApp*``PlatformEntryActiveFlowShell.tsx``platformEntryActiveTypes.ts` 承载,只包含项目、编辑器、账号、设置与钱包公共能力。`retired/legacy-creation-templates/frontend/original/` 另保留逐文件原样快照。
- 旧 URL 统一回落到当前平台公共首页,不再提供旧业务错误页、详情页或兼容壳。
### Rust
- `api-server` 不声明模板模块,不挂模板路由,不保留模板 worker 启动点。
- `spacetime-client` 不保留模板业务 facade 和 mutation 调用;历史表生成绑定只允许服务必要兼容读取。
- `spacetime-module` 对旧模板只编译历史表结构,不导出旧 reducer、procedure、业务 view 或领域规则。
- `spacetime-module``spacetime-client` 的 Cargo `lib.path` 固定指向各自的 `src/active.rs`;原 `src/lib.rs` 及旧业务源码继续原位保留,但不再作为 crate 根参与编译。
- 历史表最小定义集中在 `spacetime-module/src/legacy_schema/``spacetime-module/src/runtime/legacy_schema/`,混合 profile 表的在运数据壳位于 `spacetime-module/src/runtime/active/profile.rs`;这些目录只允许 schema 和必要兼容读取定义。
- 纯模板 crate 和专属运行态 crate 不属于 workspace members、default members 或任何在运 crate 的依赖图;源码目录保持原样。
## 验收
- 旧 URL 不再命中旧页面或后端路由。
- Vite 构建产物和依赖图不包含旧前端业务目录。
- `cargo tree` 中不存在纯模板 crate 或专属运行态 crate。
- `spacetime-module` 编译结果仍包含全部历史表,但不包含任何旧模板 reducer、procedure 和业务 view。
- `npm run check:spacetime-schema`、定向 Rust 检查、前端类型检查、`npm run check:encoding``git diff --check` 通过。
## 历史记录兼容
- 历史数据库行、作品号、`worldType` 和资产对象前缀可以继续被审计或迁移工具识别,但不再形成面向用户的列表、详情、创作或运行入口。
- 旧公开作品号、创作 URL 和统一创作规格不再作为前端可启动契约;未知或旧 URL 统一回落平台公共首页。
- 历史表名、资产对象前缀和后台数据库表目录可继续出现旧域名,它们属于数据审计与资产读取边界,不代表旧业务仍在运行。
- 原本与历史表混在同一文件的 reducer/procedure 实现按模块保存在 `retired/legacy-creation-templates/rust/`,不属于任何 Cargo workspace/module;从全局样式表移出的专属 CSS 保存在同目录的 `frontend/` 下且不被 Vite 导入。
@@ -1,5 +1,7 @@
# server-rs 与 SpacetimeDB 数据契约 # server-rs 与 SpacetimeDB 数据契约
> 2026-07-17 状态更新:旧创作入口、全部模板业务 API/worker/运行态及 SpacetimeDB 业务逻辑已退役。本文逐玩法路由、流程和 DTO 章节仅作为历史设计记录;相关持久化表仍按原结构作为最小 schema 数据壳编译,当前边界以 `docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md` 为准。
更新时间:`2026-07-17` 更新时间:`2026-07-17`
## 后端主线 ## 后端主线
@@ -1,5 +1,7 @@
# 平台入口与玩法链路 # 平台入口与玩法链路
> 2026-07-17 退役覆盖:全部旧创作模板的前端、API、worker、reducer/procedure、纯业务 crate、生成发布链路、公开业务详情和专属运行态已下线,仅保留相关历史表的数据壳与必要兼容读取。本文后续玩法章节只作为历史设计记录,不再描述当前可用能力;当前实现与验收以 `docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md` 为准。
更新时间:`2026-06-10` 更新时间:`2026-06-10`
## 平台创作入口 ## 平台创作入口
@@ -1,5 +1,7 @@
# 当前产品与工程约束 # 当前产品与工程约束
> 2026-07-17 状态更新:旧创作入口、全部模板创作/生成/发布/公开业务详情及专属运行态已退役。本文涉及 RPG、拼图、拼消消、大鱼、木鱼、方洞、视觉小说、汪汪声浪、寓教于乐等玩法的内容仅作为历史设计记录;当前退役边界以 `docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md` 为准。
更新时间:`2026-06-05` 更新时间:`2026-06-05`
## 项目定位 ## 项目定位
+2 -2
View File
@@ -7,7 +7,7 @@
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/> />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>陶泥儿 Genarrative游戏美术AI创作工具与美术Agent工作台</title> <title>陶泥儿 Genarrative美术编辑器与项目工作台</title>
<meta <meta
name="description" name="description"
content="陶泥儿 Genarrative 是面向个人游戏创作者、独立开发者和UGC游戏社群作者的游戏美术AI创作工具,当前重点提供美术Agent与无限画布,支持素材库管理、AI生成、画布编排、图层编辑、角色素材、游戏图标和宣发图制作,帮助创作者更快准备可展示的游戏美术素材。" content="陶泥儿 Genarrative 是面向个人游戏创作者、独立开发者和UGC游戏社群作者的游戏美术AI创作工具,当前重点提供美术Agent与无限画布,支持素材库管理、AI生成、画布编排、图层编辑、角色素材、游戏图标和宣发图制作,帮助创作者更快准备可展示的游戏美术素材。"
@@ -49,6 +49,6 @@
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/active-main.tsx"></script>
</body> </body>
</html> </html>

Some files were not shown because too many files have changed in this diff Show More