记录星册首页 More 按钮恢复的方案说明与 TDD 实施计划,作为本次特性 commit e079a6c 的设计依据。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
205 lines
8.7 KiB
Markdown
205 lines
8.7 KiB
Markdown
# 星册首页“更多”按钮恢复设计
|
||
|
||
> ★ **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 | 不显示 |
|
||
| 1–3 | 1–3 | 不显示 |
|
||
| 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 查询层;这不影响本次恢复既有按钮行为。
|