Files
backend/.qoder/repowiki/zh/content/API参考/材质API/材质收藏.md

411 lines
16 KiB
Markdown
Raw Normal View History

# 材质收藏
<cite>
**本文引用的文件**
- [texture_service.go](file://internal/service/texture_service.go)
- [texture_repository.go](file://internal/repository/texture_repository.go)
- [texture_handler.go](file://internal/handler/texture_handler.go)
- [routes.go](file://internal/handler/routes.go)
- [texture.go](file://internal/model/texture.go)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql)
- [texture_service_test.go](file://internal/service/texture_service_test.go)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件围绕“材质收藏”能力进行系统化文档化,重点覆盖以下方面:
- 收藏/取消收藏的切换逻辑与幂等性设计
- 用户收藏列表的查询机制与分页实现
- API 的请求参数、响应格式与错误处理
- 基于测试用例的逻辑验证与正确性保障
- 数据模型与数据库结构支撑
## 项目结构
与“材质收藏”直接相关的代码分布在如下层次:
- 路由层:定义收藏相关接口路径
- 处理器层:解析请求、调用服务层、封装响应
- 服务层:业务逻辑(收藏切换、收藏列表查询)
- 仓储层:数据库访问(收藏状态判断、收藏增删、收藏计数增减、收藏列表查询)
- 模型层:数据结构(材质、收藏关系、下载日志)
- 数据库脚本:表结构与索引定义
```mermaid
graph TB
Routes["路由层<br/>routes.go"] --> Handler["处理器层<br/>texture_handler.go"]
Handler --> Service["服务层<br/>texture_service.go"]
Service --> Repo["仓储层<br/>texture_repository.go"]
Service --> Model["模型层<br/>texture.go"]
Repo --> DB["数据库脚本<br/>carrotskin_postgres.sql"]
```
图表来源
- [routes.go](file://internal/handler/routes.go#L42-L61)
- [texture_handler.go](file://internal/handler/texture_handler.go#L421-L471)
- [texture_service.go](file://internal/service/texture_service.go#L189-L237)
- [texture_repository.go](file://internal/repository/texture_repository.go#L159-L221)
- [texture.go](file://internal/model/texture.go#L16-L57)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L43-L110)
章节来源
- [routes.go](file://internal/handler/routes.go#L42-L61)
- [texture_handler.go](file://internal/handler/texture_handler.go#L421-L471)
- [texture_service.go](file://internal/service/texture_service.go#L189-L237)
- [texture_repository.go](file://internal/repository/texture_repository.go#L159-L221)
- [texture.go](file://internal/model/texture.go#L16-L57)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L43-L110)
## 核心组件
- 收藏切换接口POST /api/v1/texture/{id}/favorite
- 收藏列表接口GET /api/v1/texture/favorites
- 数据模型:材质、用户-材质收藏关系、下载日志
- 仓储方法:收藏状态判断、收藏增删、收藏计数增减、收藏列表查询
章节来源
- [texture_handler.go](file://internal/handler/texture_handler.go#L421-L471)
- [texture_handler.go](file://internal/handler/texture_handler.go#L537-L599)
- [texture.go](file://internal/model/texture.go#L16-L57)
- [texture_repository.go](file://internal/repository/texture_repository.go#L159-L221)
## 架构总览
收藏能力的端到端流程如下:
- 客户端向收藏接口发起请求
- 路由层匹配到处理器
- 处理器解析参数、调用服务层
- 服务层根据收藏状态决定新增或删除收藏,并同步更新收藏计数
- 仓储层执行数据库操作
- 处理器封装响应返回客户端
```mermaid
sequenceDiagram
participant Client as "客户端"
participant Routes as "路由层"
participant Handler as "处理器"
participant Service as "服务层"
participant Repo as "仓储层"
participant DB as "数据库"
Client->>Routes : "POST /api/v1/texture/{id}/favorite"
Routes->>Handler : "分发到 ToggleFavorite"
Handler->>Service : "ToggleTextureFavorite(userID, textureID)"
Service->>Repo : "IsTextureFavorited(userID, textureID)"
Repo-->>Service : "布尔结果"
alt 已收藏
Service->>Repo : "RemoveTextureFavorite(userID, textureID)"
Service->>Repo : "DecrementTextureFavoriteCount(textureID)"
Repo-->>Service : "OK"
else 未收藏
Service->>Repo : "AddTextureFavorite(userID, textureID)"
Service->>Repo : "IncrementTextureFavoriteCount(textureID)"
Repo-->>Service : "OK"
end
Service-->>Handler : "返回新的收藏状态"
Handler-->>Client : "200 {is_favorited : bool}"
```
图表来源
- [routes.go](file://internal/handler/routes.go#L57-L57)
- [texture_handler.go](file://internal/handler/texture_handler.go#L421-L471)
- [texture_service.go](file://internal/service/texture_service.go#L189-L237)
- [texture_repository.go](file://internal/repository/texture_repository.go#L159-L221)
## 详细组件分析
### 收藏切换 APIToggleTextureFavorite
- 接口路径POST /api/v1/texture/{id}/favorite
- 请求参数
- 路径参数id材质ID
- 认证Bearer TokenJWT
- 处理流程
- 校验材质存在性
- 查询当前用户是否已收藏
- 若已收藏:删除收藏记录并减少收藏计数
- 若未收藏:插入收藏记录并增加收藏计数
- 返回布尔值表示新的收藏状态
- 幂等性设计
- 同一用户对同一材质重复调用收藏/取消收藏,最终状态与最后一次操作一致
- 通过唯一约束避免重复收藏记录(见数据库脚本)
- 错误处理
- 材质不存在:返回错误
- 数据库异常:返回错误
- 未认证:返回 401
```mermaid
flowchart TD
Start(["进入 ToggleFavorite"]) --> Parse["解析路径参数 id"]
Parse --> CheckAuth["校验 JWT 有效性"]
CheckAuth --> CallSvc["调用服务层 ToggleTextureFavorite"]
CallSvc --> Exists{"材质存在?"}
Exists --> |否| Err["返回错误:材质不存在"]
Exists --> |是| Favorited{"是否已收藏?"}
Favorited --> |是| Unfav["删除收藏记录"]
Favorited --> |否| Fav["新增收藏记录"]
Unfav --> Dec["收藏计数 -1"]
Fav --> Inc["收藏计数 +1"]
Dec --> Ret["返回 false"]
Inc --> Ret2["返回 true"]
Err --> End(["结束"])
Ret --> End
Ret2 --> End
```
图表来源
- [texture_handler.go](file://internal/handler/texture_handler.go#L421-L471)
- [texture_service.go](file://internal/service/texture_service.go#L189-L237)
- [texture_repository.go](file://internal/repository/texture_repository.go#L159-L221)
章节来源
- [texture_handler.go](file://internal/handler/texture_handler.go#L421-L471)
- [texture_service.go](file://internal/service/texture_service.go#L189-L237)
- [texture_repository.go](file://internal/repository/texture_repository.go#L159-L221)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L87-L110)
### 用户收藏列表 APIGetUserTextureFavorites
- 接口路径GET /api/v1/texture/favorites
- 请求参数
- page页码默认 1最小 1
- page_size每页数量默认 20最小 1最大 100
- 认证Bearer TokenJWT
- 处理流程
- 校验分页参数边界
- 通过子查询获取当前用户收藏的材质ID集合
- 基于材质状态过滤(仅返回正常状态)
- 分页查询并返回总数
- 响应格式
- 包含分页信息与材质列表(每项包含基础元信息)
```mermaid
sequenceDiagram
participant Client as "客户端"
participant Routes as "路由层"
participant Handler as "处理器"
participant Service as "服务层"
participant Repo as "仓储层"
participant DB as "数据库"
Client->>Routes : "GET /api/v1/texture/favorites?page&page_size"
Routes->>Handler : "分发到 GetUserFavorites"
Handler->>Service : "GetUserTextureFavorites(userID, page, pageSize)"
Service->>Repo : "子查询获取收藏的 texture_id"
Repo-->>Service : "ID 列表"
Service->>Repo : "按状态过滤并分页查询材质"
Repo-->>Service : "材质列表 + 总数"
Service-->>Handler : "返回结果"
Handler-->>Client : "200 {data, total, page, page_size}"
```
图表来源
- [routes.go](file://internal/handler/routes.go#L59-L59)
- [texture_handler.go](file://internal/handler/texture_handler.go#L537-L599)
- [texture_service.go](file://internal/service/texture_service.go#L227-L237)
- [texture_repository.go](file://internal/repository/texture_repository.go#L189-L221)
章节来源
- [texture_handler.go](file://internal/handler/texture_handler.go#L537-L599)
- [texture_service.go](file://internal/service/texture_service.go#L227-L237)
- [texture_repository.go](file://internal/repository/texture_repository.go#L189-L221)
### 数据模型与数据库结构
- 材质模型textures
- 字段uploader_id、name、description、type、url、hash、size、is_public、download_count、favorite_count、is_slim、status、created_at、updated_at
- 索引:按 uploader_id、公开/类型/状态组合索引、收藏数降序索引
- 用户-材质收藏关系user_texture_favorites
- 字段user_id、texture_id、created_at
- 唯一键:(user_id, texture_id),防止重复收藏
- 索引user_id、texture_id、created_at
- 下载日志texture_download_logs
- 字段texture_id、user_id、ip_address、user_agent、created_at
- 用于统计与风控
```mermaid
erDiagram
USER {
bigint id PK
varchar username UK
varchar email UK
}
TEXTURES {
bigint id PK
bigint uploader_id FK
varchar name
text description
enum type
varchar url
varchar hash UK
integer size
boolean is_public
integer download_count
integer favorite_count
boolean is_slim
smallint status
timestamp created_at
timestamp updated_at
}
USER_TEXTURE_FAVORITES {
bigint id PK
bigint user_id FK
bigint texture_id FK
timestamp created_at
}
TEXTURE_DOWNLOAD_LOGS {
bigint id PK
bigint texture_id FK
bigint user_id FK
inet ip_address
text user_agent
timestamp created_at
}
USER ||--o{ TEXTURES : "上传"
USER ||--o{ USER_TEXTURE_FAVORITES : "收藏"
TEXTURES ||--o{ USER_TEXTURE_FAVORITES : "被收藏"
USER ||--o{ TEXTURE_DOWNLOAD_LOGS : "下载"
TEXTURES ||--o{ TEXTURE_DOWNLOAD_LOGS : "被下载"
```
图表来源
- [texture.go](file://internal/model/texture.go#L16-L57)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L43-L110)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L272-L292)
章节来源
- [texture.go](file://internal/model/texture.go#L16-L57)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L43-L110)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L272-L292)
### 幂等性与重复收藏防护
- 幂等性
- 对同一用户/材质重复调用收藏/取消收藏,最终状态与最后一次操作一致
- 重复收藏防护
- user_texture_favorites 表的唯一键 (user_id, texture_id) 防止重复插入
- 服务层通过“是否已收藏”的查询结果决定新增或删除,避免多余写入
章节来源
- [texture_repository.go](file://internal/repository/texture_repository.go#L172-L187)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L87-L110)
### 分页查询示例(收藏列表)
- 请求
- 方法GET
- 路径:/api/v1/texture/favorites
- 查询参数:
- page页码默认 1最小 1
- page_size每页数量默认 20最小 1最大 100
- 响应
- data材质数组每项包含基础元信息
- total总数
- page、page_size当前页与每页条数
- 错误处理
- 未认证401
- 服务器内部错误500
章节来源
- [texture_handler.go](file://internal/handler/texture_handler.go#L537-L599)
- [texture_service.go](file://internal/service/texture_service.go#L227-L237)
- [texture_repository.go](file://internal/repository/texture_repository.go#L189-L221)
### 测试用例验证要点
- 收藏切换逻辑
- 已收藏 -> 取消收藏:返回 false
- 未收藏 -> 添加收藏:返回 true
- 收藏列表分页
- page 小于 1自动修正为 1
- page_size 超过 100自动修正为 20
- 其他相关测试
- 材质类型验证、默认值、状态验证、分页边界等
章节来源
- [texture_service_test.go](file://internal/service/texture_service_test.go#L347-L374)
- [texture_service_test.go](file://internal/service/texture_service_test.go#L376-L428)
## 依赖分析
- 路由到处理器
- /api/v1/texture/{id}/favorite -> ToggleFavorite
- /api/v1/texture/favorites -> GetUserFavorites
- 处理器到服务层
- ToggleFavorite -> ToggleTextureFavorite
- GetUserFavorites -> GetUserTextureFavorites
- 服务层到仓储层
- ToggleTextureFavorite -> IsTextureFavorited、AddTextureFavorite、RemoveTextureFavorite、IncrementTextureFavoriteCount、DecrementTextureFavoriteCount
- GetUserTextureFavorites -> GetUserTextureFavorites子查询 + 分页)
- 仓储层到数据库
- 使用 GORM 执行查询与更新,依赖唯一键约束保证幂等
```mermaid
graph LR
R["routes.go"] --> H["texture_handler.go"]
H --> S["texture_service.go"]
S --> RE["texture_repository.go"]
RE --> D["carrotskin_postgres.sql"]
```
图表来源
- [routes.go](file://internal/handler/routes.go#L42-L61)
- [texture_handler.go](file://internal/handler/texture_handler.go#L421-L471)
- [texture_handler.go](file://internal/handler/texture_handler.go#L537-L599)
- [texture_service.go](file://internal/service/texture_service.go#L189-L237)
- [texture_repository.go](file://internal/repository/texture_repository.go#L159-L221)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L43-L110)
章节来源
- [routes.go](file://internal/handler/routes.go#L42-L61)
- [texture_handler.go](file://internal/handler/texture_handler.go#L421-L471)
- [texture_handler.go](file://internal/handler/texture_handler.go#L537-L599)
- [texture_service.go](file://internal/service/texture_service.go#L189-L237)
- [texture_repository.go](file://internal/repository/texture_repository.go#L159-L221)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L43-L110)
## 性能考虑
- 索引优化
- textures 表的 favorite_count 降序索引有利于排序与统计
- user_texture_favorites 的 user_id、texture_id 索引提升收藏查询与去重效率
- 写入优化
- 收藏计数采用原子更新(+1/-1避免额外查询
- 分页限制
- 服务层对 page_size 设定上限,防止大页导致的数据库压力
章节来源
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L63-L68)
- [carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L99-L103)
- [texture_repository.go](file://internal/repository/texture_repository.go#L139-L151)
- [texture_service.go](file://internal/service/texture_service.go#L227-L237)
## 故障排查指南
- 常见错误
- 400无效的材质ID、请求参数错误
- 401未认证
- 404材质不存在
- 500服务器内部错误
- 排查步骤
- 确认 JWT 是否正确传递
- 校验路径参数 id 是否为有效整数
- 检查数据库连接与迁移是否完成
- 查看处理器日志定位具体错误点
章节来源
- [texture_handler.go](file://internal/handler/texture_handler.go#L421-L471)
- [texture_handler.go](file://internal/handler/texture_handler.go#L537-L599)
## 结论
- 收藏功能以“取反操作”为核心,通过唯一键约束与服务层条件判断实现幂等
- 收藏列表查询采用子查询 + 分页,兼顾准确性与性能
- 测试用例覆盖了关键分支,确保逻辑正确性
- 数据库层面的索引与约束为高并发场景提供了基础保障
## 附录
- API 列表
- POST /api/v1/texture/{id}/favorite切换收藏状态
- GET /api/v1/texture/favorites获取用户收藏列表分页
- 关键实现位置
- 收藏切换:[texture_service.go](file://internal/service/texture_service.go#L189-L237)
- 收藏列表:[texture_service.go](file://internal/service/texture_service.go#L227-L237)、[texture_repository.go](file://internal/repository/texture_repository.go#L189-L221)
- 路由绑定:[routes.go](file://internal/handler/routes.go#L57-L59)
- 处理器实现:[texture_handler.go](file://internal/handler/texture_handler.go#L421-L471)、[texture_handler.go](file://internal/handler/texture_handler.go#L537-L599)
- 数据模型与表结构:[texture.go](file://internal/model/texture.go#L16-L57)、[carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L43-L110)