chore(git): 更新.gitignore以忽略新的本地文件
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
This commit is contained in:
411
.qoder/repowiki/zh/content/API参考/材质API/材质收藏.md
Normal file
411
.qoder/repowiki/zh/content/API参考/材质API/材质收藏.md
Normal file
@@ -0,0 +1,411 @@
|
||||
# 材质收藏
|
||||
|
||||
<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)
|
||||
|
||||
## 详细组件分析
|
||||
|
||||
### 收藏切换 API(ToggleTextureFavorite)
|
||||
- 接口路径:POST /api/v1/texture/{id}/favorite
|
||||
- 请求参数
|
||||
- 路径参数:id(材质ID)
|
||||
- 认证:Bearer Token(JWT)
|
||||
- 处理流程
|
||||
- 校验材质存在性
|
||||
- 查询当前用户是否已收藏
|
||||
- 若已收藏:删除收藏记录并减少收藏计数
|
||||
- 若未收藏:插入收藏记录并增加收藏计数
|
||||
- 返回布尔值表示新的收藏状态
|
||||
- 幂等性设计
|
||||
- 同一用户对同一材质重复调用收藏/取消收藏,最终状态与最后一次操作一致
|
||||
- 通过唯一约束避免重复收藏记录(见数据库脚本)
|
||||
- 错误处理
|
||||
- 材质不存在:返回错误
|
||||
- 数据库异常:返回错误
|
||||
- 未认证:返回 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)
|
||||
|
||||
### 用户收藏列表 API(GetUserTextureFavorites)
|
||||
- 接口路径:GET /api/v1/texture/favorites
|
||||
- 请求参数
|
||||
- page:页码,默认 1;最小 1
|
||||
- page_size:每页数量,默认 20;最小 1,最大 100
|
||||
- 认证:Bearer Token(JWT)
|
||||
- 处理流程
|
||||
- 校验分页参数边界
|
||||
- 通过子查询获取当前用户收藏的材质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)
|
||||
Reference in New Issue
Block a user