topfans/docs/superpowers/specs/2026-07-24-starbook-home-has-more-design.md
zerosaturation 383b5e042e docs(starbook): add has_more design spec and implementation plan
记录星册首页 More 按钮恢复的方案说明与 TDD 实施计划,作为本次特性 commit e079a6c 的设计依据。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 19:20:32 +08:00

205 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 星册首页“更多”按钮恢复设计
> ★ **MVP 优先**:本次仅恢复旧版星册首页“每组预览 3 张 + 超出显示更多”的既有行为,不引入新分页框架、不修改前端布局。
## 方案概述(必读)
### 要解决的问题
**业务问题**
- 星册首页的原创、典藏和活动分组均不再显示“更多”按钮。
- 用户无法从首页进入对应分类的完整藏品列表。
**技术问题**
- `assetService` 迁移后,分组构建器返回全部藏品,并把 `AssetGroup.HasMore`、`GradeSection.HasMore` 固定为 `false`
- protobuf 的 `false` 字段可能因 `omitempty` 不出现在 JSON 中,前端 `v-if="group.has_more"``v-if="gradeItem.has_more"` 均不成立。
- 当前分组构建器同时被首页 `GetMyAssets` 和查看更多 `GetAssetsByType` 复用,不能直接统一截断,否则查看更多页面也只能拿到前三张。
### 整体实现路径
1. 为共享分组构建器增加预览上限参数,约 0.5 小时。
2. 首页传入固定上限 3查看更多传入 0不限约 0.5 小时。
3. 补充首页截断和查看更多完整返回测试,约 1 小时。
4. 执行 assetService、gateway 相关测试和全服务预编译,约 0.5 小时。
预计总工作量:约 2.5 小时。
### 关键决策
- **恢复旧版 `HomePageSize = 3` 行为**:用户已确认每个分组或等级最多预览 3 张。
- **统一按点赞数选取首页前三张**:原创、典藏、活动均使用 `models.Asset.LikeCount` 按点赞数降序;点赞数相同时按 `asset_id` 升序,沿用项目排行榜的稳定排序口径。
- **后端计算 `has_more`**:保证 API 契约真实,避免前端下载全部数据后再截断。
- **参数化复用现有构建器**:首页限制 3查看更多限制 0避免复制三套分组逻辑。
- **不修改前端模板**:现有 `v-if` 在后端返回正确字段后即可工作。
### 核心架构图TL;DR
```text
GET /api/v1/starbook/home
GetMyAssets ── previewLimit=3 ──► group builders
├─ items[:3]
├─ total_count=真实数量
└─ has_more=(真实数量 > 3)
GET /api/v1/starbook/items
GetAssetsByType ── previewLimit=0 ──► 同一组 builders
├─ items=全部匹配项
└─ 不因首页规则截断
```
## 文档说明
- **适用范围**:星册首页分组预览和“更多”入口。
- **不包含**:前端布局重做、数据库查询级分页重构、新增缓存。
- **前置版本**`starbookService` 已删除,相关能力已迁移到 `assetService`
- **历史依据**:旧版 `starbookService` 使用 `HomePageSize = 3`,原创按等级截断并计算 `has_more`
- **目标读者**:后端开发、前端联调和测试人员。
## 1. 当前行为与根因
`StarbookContent.vue` 已正确消费以下字段:
- 原创:`gradeItem.has_more`
- 典藏/活动:`group.has_more`
问题位于 `assetService/service/asset_service.go`:三个分组构建器返回所有项目,并把 `HasMore` 固定为 `false`。因此前端不需要增加兜底逻辑,修复应落在 API 数据生产端。
## 2. 分组构建器接口
三个构建器增加 `previewLimit int` 参数:
```go
buildRegularGroupForAssets(..., previewLimit int) *pb.AssetGroup
buildCollectionGroupForAssets(..., previewLimit int) *pb.AssetGroup
buildActivityGroupForAssets(..., previewLimit int) *pb.AssetGroup
```
参数语义:
- `previewLimit > 0`:先按 `models.Asset.LikeCount DESC, Asset.ID ASC` 排序,再最多保留指定数量;`has_more` 表示是否有项目被本次预览截断。
- `previewLimit <= 0`:不截断,用于查看更多接口;分组和等级的 `has_more` 必须为 `false`,避免把“无限制模式”错误解释成仍有未返回项目。
定义首页常量:
```go
const homePreviewLimit = 3
```
不增加配置文件或环境变量;该值是已确认的固定产品规则。
## 3. 首页数据规则
### 3.1 原创藏品
原创按 `grade` 独立计算:
1. 只统计能关联到真实 `Asset` 的有效注册记录。
2. 每个等级使用关联到的 `models.Asset.LikeCount` 按点赞数降序排序;点赞数相同时按 `Asset.ID` 升序,不能使用可能漂移的 `AssetRegistry.LikeCount`
3. 每个等级最多返回 3 张。
4. `GradeSection.TotalCount` 为该等级有效项目总数。
5. `GradeSection.HasMore = previewLimit > 0 && TotalCount > previewLimit`
6. `AssetGroup.HasMore` 为任一等级 `HasMore=true`
7. 保持现有 grade 降序输出。
### 3.2 典收藏品
1. 构建有效项目列表。
2. 使用关联到的 `models.Asset.LikeCount` 按点赞数降序排序;点赞数相同时按 `Asset.ID` 升序,不能使用 `AssetRegistry.LikeCount`
3. 首页最多返回前三张。
4. `TotalCount` 为有效项目总数。
5. `HasMore = previewLimit > 0 && TotalCount > previewLimit`
### 3.3 活动藏品
规则与典藏一致:使用 `models.Asset.LikeCount` 按点赞数降序并以 `Asset.ID ASC` 稳定并列顺序,有效项目总数超过 3 时截断并返回 `HasMore=true`
## 4. 查看更多数据规则
`GetAssetsByType` 调用构建器时传 `previewLimit=0`
- 不执行首页前三张截断。
- 保留当前 type/category/grade 过滤逻辑。
- 返回结果沿用首页的统一排序:`models.Asset.LikeCount DESC, Asset.ID ASC`。
- 返回所有匹配分组项目,确保首页点击“更多”后能看到完整数据。
- 本次不扩展数据库级分页;现有接口的分页字段行为不在此次修复范围内。
## 5. 前端行为
`frontend/pages/components/StarbookContent.vue` 不需要修改:
- 原创继续使用 `v-if="gradeItem.has_more"`
- 典藏和活动继续使用 `v-if="group.has_more"`
- `processGroupsWithValidUrls` 的深拷贝会保留值为 `true``has_more` 字段。
验收行为:
| 实际数量 | 首页显示数量 | “更多”按钮 |
|---:|---:|---|
| 0 | 0 | 不显示 |
| 13 | 13 | 不显示 |
| 4+ | 3 | 显示 |
## 6. 测试设计
### 6.1 Service 单元测试
至少覆盖:
1. 原创某等级 3 张:返回 3 张,等级和外层 `has_more=false`
2. 原创某等级 4 张:首页按 `Asset.LikeCount DESC, Asset.ID ASC` 返回前三张,`total_count=4`,等级和外层 `has_more=true`
3. 典藏 4 张:首页按 `Asset.LikeCount DESC, Asset.ID ASC` 返回前三张,`total_count=4``has_more=true`。
4. 活动 4 张:首页按 `Asset.LikeCount DESC, Asset.ID ASC` 返回前三张,`total_count=4``has_more=true`。
5. 点赞来源口径:故意让 `AssetRegistry.LikeCount``Asset.LikeCount` 相反,断言排序严格采用 `Asset.LikeCount`
6. 查看更多模式 4 张:按同一规则返回全部 4 张,分组/等级 `has_more=false`,不受首页上限影响。
7. 边界值 `previewLimit=3`:数量为 0、1、3、4 时分别验证返回数量和 `has_more`,防止 off-by-one。
8. 注册记录找不到对应资产时:无效记录不应制造错误的 `total_count``has_more`
9. 多个原创等级中仅一个等级超过 3 张:仅该等级 `has_more=true`,外层 `AssetGroup.HasMore=true`
### 6.2 回归验证
- 运行 assetService 相关 service/provider 测试。
- 运行 Starbook gateway controller 测试。
- 编译 gateway 和 assetService。
-`dev.sh` 服务列表预编译全部后端服务。
- 检查 `GetMyAssets``GetAssetsByType` 的调用方,确认二者分别传入 3 和 0。
## 7. 错误处理与兼容性
- 不改变 protobuf 字段或 REST 响应结构。
- 不改变身份校验和 type/category/grade 过滤规则。
- 空分组维持现有行为。
- `has_more=false` 仍可能被 JSON 省略,前端将其视为假值,符合预期;只有需要按钮时必须返回 `true`
## 8. 实施文件
预计修改:
- `backend/services/assetService/service/asset_service.go`
- 对应的 assetService 测试文件(优先复用现有测试文件)
预计不修改:
- `frontend/pages/components/StarbookContent.vue`
- `backend/proto/asset.proto`
- 生成的 protobuf 文件
- 数据库与 migration
## 9. 非目标与后续项
本次明确不做:
- 数据库查询层的真实分页与 limit/offset 下推。
- 首页排序规则重构。
- collection/activity 的分类结构重构。
- 前端“更多”按钮样式调整。
如果单个用户藏品数量显著超过当前 `GetByOwner(..., 1000, 0)` 上限,应另立任务把首页预览下推到 repository 查询层;这不影响本次恢复既有按钮行为。