补充创作者主页与关注粉丝工程文档
明确主页入口、公开关系列表及用户主页跳转行为 补充关注关系表、接口契约、权限和前端状态设计 拆分后端查询与页面交互里程碑及验收标准 记录游戏列表沿用现有上限、不增加分页改造的边界 同步文档索引与团队决策记录
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# 创作者主页与关注粉丝工程设计
|
||||
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| Version | 0.1 |
|
||||
| Status | proposed(文档已形成,业务未实现、未部署) |
|
||||
| Date | 2026-10-05 |
|
||||
| Parent Spec | [创作者主页与关注粉丝合同](../【玩法创作】平台入口与玩法链路-2026-05-15.md#创作者主页与关注粉丝合同) |
|
||||
|
||||
本文明确主站创作者主页、关注关系和列表操作的工程边界,供后续按里程碑实施。产品行为以父规范为准;本次只提交文档,不改业务代码或数据库。
|
||||
|
||||
## 当前实现与修改边界
|
||||
|
||||
| 层 | 现有入口与拟修改范围 |
|
||||
| --- | --- |
|
||||
| 主站导航 | `src/components/platform-entry/PlatformEntryActiveFlowShell.tsx`、`platformEntryActiveTypes.ts`、`src/routing/activeAppPageRoutes.ts`;增加创作者主页与关系列表 stage,保持既有创作/项目/游戏/我的入口 |
|
||||
| 详情与公共 UI | `src/components/game-distribution/GameDetailPage.tsx`、`packages/shared/src/components/GameDetailDisplay/`;扩展作者区域插槽,公共关注按钮和用户行放共享组件 |
|
||||
| 新页面与访问层 | 在 `src/components/creator/`、`src/services/creatorClient.ts` 增加主页、关系列表和 HTTP 访问;复用现有认证、响应解包、错误处理和游戏卡表现 |
|
||||
| DTO | `server-rs/crates/shared-contracts/src/creator.rs`、`packages/shared/src/contracts/creator.ts`(拟新增);分别在现有导出入口注册 |
|
||||
| 领域 | `module-auth` 内新增纯关系规则子模块;不新增账号系统,不把 HTTP、数据库和 UI 状态放入领域规则 |
|
||||
| 持久化 | `spacetime-module` 新增关系模块,复用 `auth/tables.rs` 中的用户身份;同步模块注册、`migration.rs`、表目录和生成绑定 |
|
||||
| Facade | `spacetime-client` 新增 creator typed facade 与 mapper,复用现有连接和受信服务身份 |
|
||||
| HTTP | `api-server/src/modules/creator.rs` 与 `modules`/`app.rs` 注册;认证由现有 access token 校验链提供 |
|
||||
| 作者游戏 | 扩展现有 game-distribution 列表 query、共享内部输入及 facade/procedure,增加可选 `authorId` 精确过滤 |
|
||||
| 深链部署 | 按新增稳定路由同步 nginx SPA 路由及 Pingora 路由对照,覆盖直接访问、刷新与未知路径回退 |
|
||||
|
||||
以上新增文件是拟定落点,实施时可按相邻模块组织拆分,但不得建立第二套身份、作品或数据访问系统。不涉及 AGC 桌面客户端、外部 OpenAPI、后台管理页或游戏运行态。
|
||||
|
||||
源码已确认:公开游戏列表和“我的游戏”一次最多取 48 项,公开列表 `nextCursor` 固定为 null,前端 `listGames` 只返回数组;游戏广场的设备过滤发生在这份数组上。用户明确本次不额外改造这些现状。创作者主页只增加服务端作者过滤,沿用最多 48 项和原排序,不增加游戏分页、加载更多或新的筛选控件。
|
||||
|
||||
## 关系模型和授权
|
||||
|
||||
拟新增私有表 `user_follow`:
|
||||
|
||||
| 字段 | 类型与约束 |
|
||||
| --- | --- |
|
||||
| `relationship_id` | String 主键;编码为关注方 ID 的 UTF-8 字节长度、冒号、关注方 ID、被关注方 ID,保证有向用户对无歧义唯一 |
|
||||
| `follower_user_id` | String,关注发起方;建立对应索引 |
|
||||
| `followee_user_id` | String,被关注方;建立对应索引 |
|
||||
| `created_at` | Timestamp,只在关系首次建立时取事务时间;重复关注不改时间 |
|
||||
|
||||
同一用户对最多一行,双方不得相同。删除后重新关注视为新的建立时间。不另存关系计数、昵称、头像或互关布尔值;读时按对应方向索引计算,并以 `user_account` 当前存在性过滤对端。当前账号表和公开查询没有独立的封禁/公开状态,不能凭空增加状态字段。
|
||||
|
||||
写入沿用项目受信 API 服务身份调用 procedure 的模式:HTTP 从已验证 access token 派生操作者 ID,procedure 先校验调用服务身份,再在事务内检查操作者、目标和方向。普通客户端不能直接传入任意操作者绕过认证。新表不进入认证全量快照替换流程,登录或刷新账号资料不能清空关系。
|
||||
|
||||
关注只插入 `actor → target`;取消只删除同一方向;移除粉丝只删除 `follower → actor`。事务回执返回 actor 相对目标的最新关系,不把移除粉丝误报为 actor 已取消关注。用户不再存在时不暴露其资料,计数和列表同步排除;本次不增加账号注销或清理任务。
|
||||
|
||||
## HTTP 与 DTO
|
||||
|
||||
接口路径、方法和错误状态见父规范,统一复用现有成功/错误响应信封。以下形状是信封内的数据字段,Rust 使用 camelCase 序列化,TS 同名。
|
||||
|
||||
```ts
|
||||
type CreatorUser = {
|
||||
id: string;
|
||||
publicUserCode: string;
|
||||
displayName: string;
|
||||
avatarUrl: string | null;
|
||||
};
|
||||
type CreatorRelationship = {
|
||||
isSelf: boolean;
|
||||
isFollowing: boolean;
|
||||
isFollowedBy: boolean;
|
||||
};
|
||||
type CreatorProfile = {
|
||||
user: CreatorUser;
|
||||
followingCount: number;
|
||||
followerCount: number;
|
||||
};
|
||||
type CreatorConnection = {
|
||||
user: CreatorUser;
|
||||
followedAt: string; // 当前列表所表示方向的建立时间,RFC 3339
|
||||
relationship: CreatorRelationship | null; // 游客为 null
|
||||
};
|
||||
type CreatorConnections = {
|
||||
items: CreatorConnection[];
|
||||
nextCursor: string | null;
|
||||
total: number;
|
||||
};
|
||||
type CreatorRelationshipResponse = {
|
||||
userId: string; // 相对当前访问者的目标;移除粉丝时为 followerId
|
||||
relationship: CreatorRelationship;
|
||||
};
|
||||
```
|
||||
|
||||
`CreatorUser` 从现有公开资料投影所需字段,不返回手机号、登录名、钱包或认证数据。主页返回 `CreatorProfile`;关系列表返回 `CreatorConnections`;关系 GET 与三种写操作返回 `CreatorRelationshipResponse`。写请求无需 body,身份只来自认证,路径 ID 按统一规则 trim、校验并安全编码。
|
||||
|
||||
关系写操作成功返回 200,目标存在时重复关注/删除仍成功;对自己操作返回 400,未认证为 401,非受信或越权调用为 403,不存在的用户为 404。有效目标但不存在关系的删除与“目标不存在”明确区分。前端不自动重试结果未知的写请求,先回读关系。
|
||||
|
||||
主页摘要一次事务内完成用户和双向计数读取;关系列表一次事务内读取 `items/total` 和当前访问者相对各行的关系。不同 HTTP 请求不承诺共享同一事务快照,跨页变化通过写后失效与回读收敛,不能要求两次独立请求永远返回相同计数。
|
||||
|
||||
公开主页不携带访问者状态;`relationship` 端点必须认证。列表无 Authorization 时按游客读取,有 Authorization 时校验后批量返回关系;无效凭据返回 401,前端经现有认证处理后可回到游客读取。关系端点和带认证列表响应使用 `Cache-Control: private, no-store`,不得进入跨用户缓存。
|
||||
|
||||
## 关系列表分页与作者游戏读取
|
||||
|
||||
关注/粉丝列表的查询参数为 `limit`、`cursor`;默认 20,上限 50,非整数或超范围返回 400。按 `(created_at DESC, relationship_id DESC)` 排序,游标包含版本、列表主人、following/followers 类型与末项排序键;使用有长度上限的 base64url JSON 编码并严格解析,时间戳用十进制字符串避免 JS 精度损失。游标只是分页定位,不作为授权证明。
|
||||
|
||||
先排除不存在的对端,再计算 total 和分页;读取 limit+1 判断是否还有下一页。同时间多行不漏读,游标主人或类型不匹配返回 400。并发增删不保证多页构成冻结快照,按 userId 去重,刷新从首批开始;不引入跨请求数据库快照或游标持久化表。
|
||||
|
||||
作者游戏只扩展现有 `GET /api/game-distribution/games` 的 `authorId`:未传保持原行为,显式空值返回 400;先按稳定 owner ID 和既有公开条件过滤,再排序、截取最多 48 项。现有条件包含 published、未删除及有效公开版本。前端 `GameListQuery` 增加可选 authorId,仍返回现有游戏数组;不修改游戏表结构或引入新的游戏列表系统。
|
||||
|
||||
## 前端状态与组件责任
|
||||
|
||||
- 主页 `/creators` 解析当前账号,`/creators?id=...` 读取指定公开用户;关系列表用 `/creators/connections?id=...&tab=following|followers`,缺主人或非法 tab 展示明确无效链接状态。显式他人 ID 不因登录切换改成自己的主页。
|
||||
- 关系状态按访问者 ID、目标 ID 隔离。业务 hook/client 负责请求、写入单飞、失败回读和失效;共享关注按钮、用户行只接收数据、pending 和回调,不直接持有认证或网络副作用。
|
||||
- 自己/他人的关注列表、粉丝列表四种组合均支持头像/昵称进入任意用户主页;用独立链接和按钮避免嵌套交互。列表用户为自己时隐藏关注按钮,主页仍可访问。
|
||||
- 取消关注后的行保留集合只属于当前列表生命周期,与服务端成员和 total 分离;成功重新关注后清除暂留标记。刷新、离开列表或账号变化清除暂留集合;正常失效回读时不可把可重新关注的行立即抹掉。
|
||||
- 移除粉丝成功从粉丝列表去行,回关/取消回关不去行。确认弹窗复用公共组件;两个方向的按钮在同一目标写入期间一起禁用,写后刷新摘要和相关列表。
|
||||
- 进入页面、返回和重新聚焦时回读;旧请求通过请求序号/取消机制隔离。认证变化清除私有关系缓存和旧页请求,不让上个账号响应覆盖新账号。
|
||||
- 新主页路由接入页面标题、导航高亮、返回兜底和滚动位置恢复。移动点击区域至少 44px;加载、错误、空态分别呈现,不因关系失败阻断公开游戏阅读。
|
||||
|
||||
## 兼容、验证和交付
|
||||
|
||||
新关系表从空数据开始;同步迁移登记、后端表目录和 Rust 生成绑定。新增 procedure 与 facade 同步发布,不手改生成文件;游戏查询新增的 authorId 是可选能力,旧 HTTP 请求继续有效。后端部署先于新前端,回滚前端不删除关系数据;回滚后端必须保留新表的兼容 schema,禁止删库回退。
|
||||
|
||||
实施顺序和验收边界分别见[关系与公开查询里程碑](../project-memory/plans/【里程碑】创作者关系与公开查询-2026-10-05.md)、[主页与关系交互里程碑](../project-memory/plans/【里程碑】创作者主页与关系交互-2026-10-05.md)。每阶段评审后再创建单里程碑实施计划;前阶段验收通过才进入下一阶段。
|
||||
|
||||
实施时按范围执行以下命令;本次文档提交只执行最后三项:
|
||||
|
||||
```bash
|
||||
cargo test --locked --manifest-path server-rs/Cargo.toml -p module-auth creator
|
||||
cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server creator
|
||||
cargo test --locked --manifest-path server-rs/Cargo.toml -p api-server game_distribution
|
||||
cargo test --locked --manifest-path server-rs/Cargo.toml -p spacetime-module creator
|
||||
npm run spacetime:generate
|
||||
npm run check:server-rs-ddd
|
||||
npm run test -- src/components/creator src/routing/activeAppPageRoutes.test.ts src/components/platform-entry/PlatformEntryActiveFlowShell.test.tsx src/components/game-distribution/GameDistributionPages.test.tsx
|
||||
npm run typecheck
|
||||
npm run check:nginx-spa-routes
|
||||
npm run check:pingora-route-parity
|
||||
npm run check:encoding
|
||||
npm run check:doc-index
|
||||
git diff --check
|
||||
```
|
||||
|
||||
`creator` 测试组为实施时新增的定向分组,届时核对实际匹配数量,零匹配不算通过;补充共享 DTO/UI 和 facade 的对应测试。host 单测不替代 SpacetimeDB 事务验证,运行时通过 `npm run dev:api-server` 启动并验证实际开发地址 `/healthz`,使用隔离开发库的 A/B/C 三账号完成权限、重复写入、双向关系与未知结果回读。
|
||||
|
||||
浏览器覆盖桌面与 375px 移动,四类列表跳转、返回位置、自己的空游戏主页、互关后移除粉丝和登录切换。游戏边界夹具应包含超过 48 项及其他作者作品,证明作者过滤在截取前,广场原有上限保持不变;关系列表用超过 50 项和同时间关系验证分页。所有执行结果回填里程碑证据,本次不宣称实现或运行时验收完成。
|
||||
Reference in New Issue
Block a user