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

16 KiB
Raw Blame History

材质收藏

**本文引用的文件** - [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)

目录

  1. 简介
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖分析
  7. 性能考虑
  8. 故障排查指南
  9. 结论
  10. 附录

简介

本文件围绕“材质收藏”能力进行系统化文档化,重点覆盖以下方面:

  • 收藏/取消收藏的切换逻辑与幂等性设计
  • 用户收藏列表的查询机制与分页实现
  • API 的请求参数、响应格式与错误处理
  • 基于测试用例的逻辑验证与正确性保障
  • 数据模型与数据库结构支撑

项目结构

与“材质收藏”直接相关的代码分布在如下层次:

  • 路由层:定义收藏相关接口路径
  • 处理器层:解析请求、调用服务层、封装响应
  • 服务层:业务逻辑(收藏切换、收藏列表查询)
  • 仓储层:数据库访问(收藏状态判断、收藏增删、收藏计数增减、收藏列表查询)
  • 模型层:数据结构(材质、收藏关系、下载日志)
  • 数据库脚本:表结构与索引定义
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"]

图表来源

章节来源

核心组件

  • 收藏切换接口POST /api/v1/texture/{id}/favorite
  • 收藏列表接口GET /api/v1/texture/favorites
  • 数据模型:材质、用户-材质收藏关系、下载日志
  • 仓储方法:收藏状态判断、收藏增删、收藏计数增减、收藏列表查询

章节来源

架构总览

收藏能力的端到端流程如下:

  • 客户端向收藏接口发起请求
  • 路由层匹配到处理器
  • 处理器解析参数、调用服务层
  • 服务层根据收藏状态决定新增或删除收藏,并同步更新收藏计数
  • 仓储层执行数据库操作
  • 处理器封装响应返回客户端
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}"

图表来源

详细组件分析

收藏切换 APIToggleTextureFavorite

  • 接口路径POST /api/v1/texture/{id}/favorite
  • 请求参数
    • 路径参数id材质ID
    • 认证Bearer TokenJWT
  • 处理流程
    • 校验材质存在性
    • 查询当前用户是否已收藏
    • 若已收藏:删除收藏记录并减少收藏计数
    • 若未收藏:插入收藏记录并增加收藏计数
    • 返回布尔值表示新的收藏状态
  • 幂等性设计
    • 同一用户对同一材质重复调用收藏/取消收藏,最终状态与最后一次操作一致
    • 通过唯一约束避免重复收藏记录(见数据库脚本)
  • 错误处理
    • 材质不存在:返回错误
    • 数据库异常:返回错误
    • 未认证:返回 401
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

图表来源

章节来源

用户收藏列表 APIGetUserTextureFavorites

  • 接口路径GET /api/v1/texture/favorites
  • 请求参数
    • page页码默认 1最小 1
    • page_size每页数量默认 20最小 1最大 100
    • 认证Bearer TokenJWT
  • 处理流程
    • 校验分页参数边界
    • 通过子查询获取当前用户收藏的材质ID集合
    • 基于材质状态过滤(仅返回正常状态)
    • 分页查询并返回总数
  • 响应格式
    • 包含分页信息与材质列表(每项包含基础元信息)
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}"

图表来源

章节来源

数据模型与数据库结构

  • 材质模型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
    • 用于统计与风控
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 : "被下载"

图表来源

章节来源

幂等性与重复收藏防护

  • 幂等性
    • 对同一用户/材质重复调用收藏/取消收藏,最终状态与最后一次操作一致
  • 重复收藏防护
    • user_texture_favorites 表的唯一键 (user_id, texture_id) 防止重复插入
    • 服务层通过“是否已收藏”的查询结果决定新增或删除,避免多余写入

章节来源

分页查询示例(收藏列表)

  • 请求
    • 方法GET
    • 路径:/api/v1/texture/favorites
    • 查询参数:
      • page页码默认 1最小 1
      • page_size每页数量默认 20最小 1最大 100
  • 响应
    • data材质数组每项包含基础元信息
    • total总数
    • page、page_size当前页与每页条数
  • 错误处理
    • 未认证401
    • 服务器内部错误500

章节来源

测试用例验证要点

  • 收藏切换逻辑
    • 已收藏 -> 取消收藏:返回 false
    • 未收藏 -> 添加收藏:返回 true
  • 收藏列表分页
    • page 小于 1自动修正为 1
    • page_size 超过 100自动修正为 20
  • 其他相关测试
    • 材质类型验证、默认值、状态验证、分页边界等

章节来源

依赖分析

  • 路由到处理器
    • /api/v1/texture/{id}/favorite -> ToggleFavorite
    • /api/v1/texture/favorites -> GetUserFavorites
  • 处理器到服务层
    • ToggleFavorite -> ToggleTextureFavorite
    • GetUserFavorites -> GetUserTextureFavorites
  • 服务层到仓储层
    • ToggleTextureFavorite -> IsTextureFavorited、AddTextureFavorite、RemoveTextureFavorite、IncrementTextureFavoriteCount、DecrementTextureFavoriteCount
    • GetUserTextureFavorites -> GetUserTextureFavorites子查询 + 分页)
  • 仓储层到数据库
    • 使用 GORM 执行查询与更新,依赖唯一键约束保证幂等
graph LR
R["routes.go"] --> H["texture_handler.go"]
H --> S["texture_service.go"]
S --> RE["texture_repository.go"]
RE --> D["carrotskin_postgres.sql"]

图表来源

章节来源

性能考虑

  • 索引优化
    • textures 表的 favorite_count 降序索引有利于排序与统计
    • user_texture_favorites 的 user_id、texture_id 索引提升收藏查询与去重效率
  • 写入优化
    • 收藏计数采用原子更新(+1/-1避免额外查询
  • 分页限制
    • 服务层对 page_size 设定上限,防止大页导致的数据库压力

章节来源

故障排查指南

  • 常见错误
    • 400无效的材质ID、请求参数错误
    • 401未认证
    • 404材质不存在
    • 500服务器内部错误
  • 排查步骤
    • 确认 JWT 是否正确传递
    • 校验路径参数 id 是否为有效整数
    • 检查数据库连接与迁移是否完成
    • 查看处理器日志定位具体错误点

章节来源

结论

  • 收藏功能以“取反操作”为核心,通过唯一键约束与服务层条件判断实现幂等
  • 收藏列表查询采用子查询 + 分页,兼顾准确性与性能
  • 测试用例覆盖了关键分支,确保逻辑正确性
  • 数据库层面的索引与约束为高并发场景提供了基础保障

附录