From 63444d047f9b9c43b0737ff0501991be1490986d Mon Sep 17 00:00:00 2001 From: kdletters Date: Sun, 7 Jun 2026 15:08:32 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E6=8B=BC=E6=B6=88=E6=B6=88?= =?UTF-8?q?=E4=BD=9C=E5=93=81=E5=8F=AF=E8=A7=81=E6=80=A7=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 接入后台作品可见性列表中的拼消消源类型 支持后台切换拼消消已发布作品 visible 状态 补充后台前端玩法标签与公开 ReadModel 文档说明 --- .../src/pages/AdminWorkVisibilityPage.tsx | 1 + ...架构】统一公开作品ReadModel设计-2026-05-26.md | 2 +- .../src/runtime/admin_work_visibility.rs | 62 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/apps/admin-web/src/pages/AdminWorkVisibilityPage.tsx b/apps/admin-web/src/pages/AdminWorkVisibilityPage.tsx index 4e02a845..aea06873 100644 --- a/apps/admin-web/src/pages/AdminWorkVisibilityPage.tsx +++ b/apps/admin-web/src/pages/AdminWorkVisibilityPage.tsx @@ -16,6 +16,7 @@ interface AdminWorkVisibilityPageProps { const sourceLabels: Record = { puzzle: '拼图', + 'puzzle-clear': '拼消消', 'custom-world': '自定义世界', 'jump-hop': '跳一跳', 'wooden-fish': '敲木鱼', diff --git a/docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md b/docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md index bd116756..ac1c723d 100644 --- a/docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md +++ b/docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md @@ -50,7 +50,7 @@ - `GET /admin/api/works/visibility` - `POST /admin/api/works/visibility` -后台操作 key 使用统一的 `sourceType + profileId` 组合。`profileId` 在大多数玩法中对应作品 profile;特殊玩法维持既有源表身份:`big-fish` 对应 `session_id`,`bark-battle` 对应 `work_id`。`custom-world` 更新源表时必须同步 `custom_world_gallery_entry.visible`,避免兼容 gallery 缓存与统一公开 read model 出现可见性漂移。 +后台操作 key 使用统一的 `sourceType + profileId` 组合。当前后端统一可见性管理覆盖 `puzzle`、`puzzle-clear`、`custom-world`、`jump-hop`、`wooden-fish`、`match3d`、`square-hole`、`visual-novel`、`big-fish` 和 `bark-battle`;`edutainment` 当前没有后端统一作品源表,暂不接入该后台能力。`profileId` 在大多数玩法中对应作品 profile;特殊玩法维持既有源表身份:`big-fish` 对应 `session_id`,`bark-battle` 对应 `work_id`。`custom-world` 更新源表时必须同步 `custom_world_gallery_entry.visible`,避免兼容 gallery 缓存与统一公开 read model 出现可见性漂移。 该后台能力只修改源表 / source view 过滤事实,不把 `visible` 暴露到公开列表或公开详情契约。隐藏作品后,统一 `public_work_gallery_entry` 与 `public_work_detail_entry` 不再返回该作品;恢复显示后重新进入公开 read model。 diff --git a/server-rs/crates/spacetime-module/src/runtime/admin_work_visibility.rs b/server-rs/crates/spacetime-module/src/runtime/admin_work_visibility.rs index 13ec22a5..9dd603a6 100644 --- a/server-rs/crates/spacetime-module/src/runtime/admin_work_visibility.rs +++ b/server-rs/crates/spacetime-module/src/runtime/admin_work_visibility.rs @@ -4,6 +4,7 @@ use module_custom_world::CustomWorldPublicationStatus; use module_puzzle::PuzzlePublicationStatus; const SOURCE_TYPE_PUZZLE: &str = "puzzle"; +const SOURCE_TYPE_PUZZLE_CLEAR: &str = "puzzle-clear"; const SOURCE_TYPE_CUSTOM_WORLD: &str = "custom-world"; const SOURCE_TYPE_JUMP_HOP: &str = "jump-hop"; const SOURCE_TYPE_WOODEN_FISH: &str = "wooden-fish"; @@ -63,6 +64,7 @@ fn list_work_visibility_tx( let mut entries = Vec::new(); entries.extend(list_puzzle_work_visibility(ctx)); + entries.extend(list_puzzle_clear_work_visibility(ctx)); entries.extend(list_custom_world_work_visibility(ctx)); entries.extend(list_jump_hop_work_visibility(ctx)); entries.extend(list_wooden_fish_work_visibility(ctx)); @@ -85,6 +87,9 @@ fn update_work_visibility_tx( match source_type.as_str() { SOURCE_TYPE_PUZZLE => update_puzzle_work_visibility(ctx, &profile_id, input.visible), + SOURCE_TYPE_PUZZLE_CLEAR => { + update_puzzle_clear_work_visibility(ctx, &profile_id, input.visible) + } SOURCE_TYPE_CUSTOM_WORLD => { update_custom_world_work_visibility(ctx, &profile_id, input.visible) } @@ -167,6 +172,63 @@ fn puzzle_work_visibility_snapshot(row: &PuzzleWorkProfileRow) -> AdminWorkVisib } } +fn list_puzzle_clear_work_visibility(ctx: &ReducerContext) -> Vec { + ctx.db + .puzzle_clear_work_profile() + .by_puzzle_clear_work_publication_status() + .filter(PUZZLE_CLEAR_PUBLICATION_PUBLISHED) + .map(|row| puzzle_clear_work_visibility_snapshot(&row)) + .collect() +} + +fn update_puzzle_clear_work_visibility( + ctx: &ReducerContext, + profile_id: &str, + visible: bool, +) -> Result { + let row = ctx + .db + .puzzle_clear_work_profile() + .profile_id() + .find(&profile_id.to_string()) + .ok_or_else(|| "拼消消作品不存在".to_string())?; + if row.publication_status != PUZZLE_CLEAR_PUBLICATION_PUBLISHED { + return Err("只能修改已发布拼消消作品可见性".to_string()); + } + let next = PuzzleClearWorkProfileRow { visible, ..row }; + let snapshot = puzzle_clear_work_visibility_snapshot(&next); + let profile_id = next.profile_id.clone(); + ctx.db + .puzzle_clear_work_profile() + .profile_id() + .delete(&profile_id); + ctx.db.puzzle_clear_work_profile().insert(next); + Ok(snapshot) +} + +fn puzzle_clear_work_visibility_snapshot( + row: &PuzzleClearWorkProfileRow, +) -> AdminWorkVisibilitySnapshot { + let sort_time = timestamp_sort_micros(row.published_at, row.updated_at); + AdminWorkVisibilitySnapshot { + source_type: SOURCE_TYPE_PUZZLE_CLEAR.to_string(), + work_id: row.work_id.clone(), + profile_id: row.profile_id.clone(), + source_session_id: Some(row.source_session_id.clone()).filter(|value| !value.is_empty()), + public_work_code: build_prefixed_public_work_code("PC", &row.profile_id), + owner_user_id: row.owner_user_id.clone(), + author_display_name: row.author_display_name.clone(), + title: choose_non_empty(&[row.work_title.as_str(), row.theme_prompt.as_str(), "拼消消"]), + subtitle: "拼消消".to_string(), + cover_image_src: Some(row.cover_image_src.clone()).filter(|value| !value.is_empty()), + visible: row.visible, + published_at_micros: row + .published_at + .map(|value| value.to_micros_since_unix_epoch()), + updated_at_micros: sort_time, + } +} + fn list_custom_world_work_visibility(ctx: &ReducerContext) -> Vec { ctx.db .custom_world_profile()