Files
backend/.qoder/repowiki/zh/content/API参考/材质API/材质收藏.md
lan a4b6c5011e
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
chore(git): 更新.gitignore以忽略新的本地文件
2025-11-30 08:33:17 +08:00

411 lines
16 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.

# 材质收藏
<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)