Files
backend/.qoder/repowiki/zh/content/数据模型/材质模型.md

408 lines
19 KiB
Markdown
Raw Normal View History

# 材质模型
<cite>
**本文引用的文件**
- [internal/model/texture.go](file://internal/model/texture.go)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go)
- [internal/service/texture_service.go](file://internal/service/texture_service.go)
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql)
- [internal/types/common.go](file://internal/types/common.go)
- [internal/model/user.go](file://internal/model/user.go)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件围绕材质模型进行系统化技术文档整理,覆盖以下主题:
- Texture 结构体及关联类型TextureType、UserTextureFavorite、TextureDownloadLog的字段语义与实现细节
- 材质类型SKIN/CAPE、哈希值Hash、URL 存储、尺寸与 Slim 模型标识的技术实现
- 材质状态机正常、审核中、已删除与公开性控制IsPublic的业务逻辑
- 与用户Uploader、收藏系统、下载日志的关联关系与索引策略
- 材质元数据管理、下载计数器并发更新优化、收藏功能去重机制的实践指导
- 基于 GORM 的复杂查询模式示例与最佳实践
## 项目结构
材质模型相关代码分布于模型层、仓储层与服务层,并通过数据库初始化脚本定义表结构与索引。
```mermaid
graph TB
subgraph "模型层"
M1["internal/model/texture.go<br/>定义 Texture/TextureType/UserTextureFavorite/TextureDownloadLog"]
M2["internal/model/user.go<br/>定义 User 及其关联"]
end
subgraph "仓储层"
R1["internal/repository/texture_repository.go<br/>材质 CRUD、搜索、计数器更新、收藏与下载日志"]
end
subgraph "服务层"
S1["internal/service/texture_service.go<br/>材质创建/更新/删除/搜索、下载记录、收藏切换、上传限制检查"]
end
subgraph "数据库"
D1["scripts/carrotskin_postgres.sql<br/>表结构、索引、约束、触发器"]
end
M1 --> R1
M2 --> R1
S1 --> R1
R1 --> D1
```
图表来源
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L1-L344)
章节来源
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L1-L344)
## 核心组件
- Texture材质主实体包含上传者、名称、描述、类型、URL、哈希、尺寸、公开性、下载/收藏计数、Slim 标识、状态与时间戳,并与 User 建立外键关联
- TextureType材质类型枚举SKIN/CAPE
- UserTextureFavorite用户对材质的收藏关系具备联合唯一索引以保证去重
- TextureDownloadLog材质下载记录包含用户、IP、UA、时间等信息并与 Texture/User 建立外键关联
章节来源
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
- [internal/types/common.go](file://internal/types/common.go#L127-L152)
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
## 架构总览
材质模型在服务层完成业务编排,在仓储层封装数据库访问,在模型层定义数据结构与关系。数据库层面通过外键、索引与触发器保障一致性与性能。
```mermaid
classDiagram
class Texture {
+int64 ID
+int64 UploaderID
+string Name
+string Description
+TextureType Type
+string URL
+string Hash
+int Size
+bool IsPublic
+int DownloadCount
+int FavoriteCount
+bool IsSlim
+int16 Status
+time CreatedAt
+time UpdatedAt
}
class User {
+int64 ID
+string Username
+string Email
+string Avatar
+int Points
+string Role
+int16 Status
+string Properties
+time LastLoginAt
+time CreatedAt
+time UpdatedAt
}
class UserTextureFavorite {
+int64 ID
+int64 UserID
+int64 TextureID
+time CreatedAt
}
class TextureDownloadLog {
+int64 ID
+int64 TextureID
+*int64 UserID
+string IPAddress
+string UserAgent
+time CreatedAt
}
Texture --> User : "Uploader"
UserTextureFavorite --> User : "User"
UserTextureFavorite --> Texture : "Texture"
TextureDownloadLog --> Texture : "Texture"
TextureDownloadLog --> User : "User"
```
图表来源
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
## 详细组件分析
### Texture 字段与业务语义
- 类型与哈希
- Type材质类型取值为 SKIN 或 CAPE
- Hash材质文件的 SHA-256 哈希,作为全局唯一标识,用于去重与校验
- 存储与元数据
- URL材质在对象存储中的访问地址
- Size文件大小字节
- IsSlimSlim 模型标识Alex 细臂为 trueSteve 粗臂为 false
- 公开性与状态
- IsPublic是否公开到皮肤广场
- Status状态机1 正常、0 审核中、-1 已删除)
- 计数器与排序
- DownloadCount、FavoriteCount下载与收藏计数分别建立降序索引以便高效排序与统计
- 时间戳与索引
- CreatedAt/UpdatedAt自动维护
- UploaderID、IsPublic+Type+Status、DownloadCount/FavoriteCount 等索引支撑查询与排序
章节来源
- [internal/model/texture.go](file://internal/model/texture.go#L16-L35)
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L39-L85)
### TextureType 与 Slim 模型
- TextureType 枚举在模型与类型定义中保持一致,确保序列化与校验的一致性
- Slim 模型标识用于区分 Alex 与 Steve 模型,便于客户端渲染与兼容
章节来源
- [internal/model/texture.go](file://internal/model/texture.go#L7-L13)
- [internal/types/common.go](file://internal/types/common.go#L127-L152)
### 状态机与公开性控制
- 状态机
- 1正常可用
- 0审核中可视为待审状态
- -1已删除软删除
- 公开性控制
- IsPublic 控制是否对外可见
- 查询侧影响
- 搜索接口默认仅返回状态为正常的材质
- 用户上传列表过滤掉已删除材质
章节来源
- [internal/model/texture.go](file://internal/model/texture.go#L28-L31)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L93-L103)
### 与用户的关联关系
- 上传者关联
- Texture 与 User 通过 UploaderID 外键关联,查询时可预加载上传者信息
- 角色档案关联
- Profiles 表通过 skin_id/cape_id 引用 textures形成角色与材质的绑定关系
章节来源
- [internal/model/texture.go](file://internal/model/texture.go#L33-L35)
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L111-L127)
### 收藏系统UserTextureFavorite
- 关系模型
- 用户与材质的多对多中间表,字段包含 user_id、texture_id、created_at
- 联合唯一索引 uk_user_texture保证同一用户对同一材质只能收藏一次
- 业务流程
- 切换收藏:先检查是否已收藏,再执行新增或删除,并同步更新材质的收藏计数
- 收藏列表子查询获取用户收藏的材质ID再按创建时间倒序分页查询
```mermaid
sequenceDiagram
participant Client as "客户端"
participant Service as "TextureService"
participant Repo as "TextureRepository"
participant DB as "数据库"
Client->>Service : "ToggleTextureFavorite(userID, textureID)"
Service->>Repo : "FindTextureByID(textureID)"
Repo->>DB : "SELECT * FROM textures WHERE id=?"
DB-->>Repo : "Texture"
Repo-->>Service : "Texture"
Service->>Repo : "IsTextureFavorited(userID, textureID)"
Repo->>DB : "SELECT COUNT(*) FROM user_texture_favorites WHERE user_id=? AND texture_id=?"
DB-->>Repo : "count"
Repo-->>Service : "bool"
alt 已收藏
Service->>Repo : "RemoveTextureFavorite(userID, textureID)"
Repo->>DB : "DELETE FROM user_texture_favorites WHERE user_id=? AND texture_id=?"
Service->>Repo : "DecrementTextureFavoriteCount(textureID)"
Repo->>DB : "UPDATE textures SET favorite_count=favorite_count-1 WHERE id=?"
else 未收藏
Service->>Repo : "AddTextureFavorite(userID, textureID)"
Repo->>DB : "INSERT INTO user_texture_favorites(user_id, texture_id)"
Service->>Repo : "IncrementTextureFavoriteCount(textureID)"
Repo->>DB : "UPDATE textures SET favorite_count=favorite_count+1 WHERE id=?"
end
Service-->>Client : "返回收藏状态"
```
图表来源
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L189-L225)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L159-L201)
章节来源
- [internal/model/texture.go](file://internal/model/texture.go#L42-L57)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L159-L201)
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L189-L225)
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L87-L110)
### 下载日志TextureDownloadLog
- 记录每次下载的材质、用户、IP、UA、时间等信息
- 下载计数器采用 GORM 表达式更新,避免并发写冲突导致的计数不准
- 提供按时间倒序索引,便于统计与审计
```mermaid
sequenceDiagram
participant Client as "客户端"
participant Service as "TextureService"
participant Repo as "TextureRepository"
participant DB as "数据库"
Client->>Service : "RecordTextureDownload(textureID, userID, ip, ua)"
Service->>Repo : "FindTextureByID(textureID)"
Repo->>DB : "SELECT * FROM textures WHERE id=?"
DB-->>Repo : "Texture"
Service->>Repo : "IncrementTextureDownloadCount(textureID)"
Repo->>DB : "UPDATE textures SET download_count=download_count+1 WHERE id=?"
Service->>Repo : "CreateTextureDownloadLog(log)"
Repo->>DB : "INSERT INTO texture_download_logs(texture_id, user_id, ip_address, user_agent)"
Service-->>Client : "成功"
```
图表来源
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L162-L187)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L157)
章节来源
- [internal/model/texture.go](file://internal/model/texture.go#L59-L76)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L157)
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L162-L187)
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L271-L291)
### GORM 使用示例与复杂查询模式
- 创建材质
- 参数校验与去重:先检查用户存在与哈希唯一性,再创建材质
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L12-L64)
- 搜索材质
- 过滤条件:状态=正常、公开筛选、类型筛选、关键词模糊匹配
- 分页与总数:先 Count 再分页查询,支持预加载上传者
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
- 更新材质
- 权限校验:仅上传者可更新
- 动态字段更新:根据传入字段选择性更新
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L105-L141)
- 删除材质(软删除)
- 仅将状态置为 -1保留历史数据与日志
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L126-L131)
- 收藏切换
- 原子性:先判断是否已收藏,再执行新增/删除并更新计数
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L189-L225)
- 收藏列表
- 子查询先查出用户收藏的材质ID集合再查询材质并分页
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L190-L221)
- 上传限制检查
- 统计用户已上传材质数量,与系统配置的最大值比较
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L239-L252)
章节来源
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L12-L64)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L105-L141)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L126-L131)
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L189-L225)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L190-L221)
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L239-L252)
## 依赖分析
- 模型层依赖
- Texture 依赖 TextureType、User
- UserTextureFavorite 依赖 User、Texture
- TextureDownloadLog 依赖 User、Texture
- 仓储层依赖
- 通过数据库连接池访问 textures、user_texture_favorites、texture_download_logs
- 使用 GORM 的表达式更新计数器,避免并发竞争
- 服务层依赖
- 负责业务规则编排:权限校验、去重、状态机、计数器更新、日志记录
- 数据库层
- 外键约束textures.uploader_id -> user.idfavorites 与 logs 对 textures/user 的引用
- 索引textures 上的多列索引与计数器降序索引favorites/logs 上的单列索引
```mermaid
graph LR
Service["TextureService"] --> Repo["TextureRepository"]
Repo --> Model["Model: Texture/User/Favorite/DownloadLog"]
Repo --> DB["PostgreSQL 表: textures/favorites/logs"]
Model --> DB
```
图表来源
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L39-L110)
章节来源
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L39-L110)
## 性能考虑
- 并发计数器更新
- 下载计数与收藏计数均使用 GORM 表达式更新,避免读取-计算-写回的竞态,降低锁竞争
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
- 索引设计
- textures 表的多列索引is_public, type, status与计数器降序索引download_count/favorite_count支撑高频查询与排序
- favorites/logs 表的关键列建立索引,提升收藏去重与日志检索效率
- 参考路径:[scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L63-L85)
- 分页与总数
- 搜索与收藏列表先 Count 再分页查询,避免一次性加载大量数据
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
- 预加载
- 查询时预加载上传者信息,减少 N+1 查询风险
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L18-L21)
章节来源
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L63-L85)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
## 故障排查指南
- 材质不存在或已被删除
- 查询时若返回空或状态为已删除,服务层会抛出明确错误
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L66-L79)
- 权限不足
- 更新/删除材质需校验上传者身份,否则返回权限错误
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L105-L120)
- 哈希重复
- 创建材质前检查哈希唯一性,重复则拒绝创建
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L23-L31)
- 收藏去重失败
- 联合唯一索引 uk_user_texture 保证同一用户对同一材质仅一条收藏记录
- 若出现重复插入,需检查业务层是否正确先查询后插入
- 参考路径:[internal/model/texture.go](file://internal/model/texture.go#L42-L57)[scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L87-L110)
- 下载计数不准确
- 确认使用表达式更新而非普通更新,避免并发写入导致计数偏差
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
章节来源
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L66-L79)
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L105-L120)
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L23-L31)
- [internal/model/texture.go](file://internal/model/texture.go#L42-L57)
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L87-L110)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
## 结论
材质模型通过清晰的数据结构、严格的外键与索引设计、以及基于 GORM 的并发安全更新,实现了高性能、可扩展且易维护的材质管理能力。状态机与公开性控制使内容治理更加灵活,收藏与下载日志完善了用户行为追踪与运营分析基础。建议在后续迭代中持续关注索引命中率与查询计划,配合缓存与异步任务进一步优化热点查询与批量操作。
## 附录
- 字段与索引对照
- texturesuploader_id、is_public+type+status、download_count、favorite_count、hash 唯一索引
- user_texture_favoritesuser_id、texture_id、created_at、uk_user_texture
- texture_download_logstexture_id、user_id、ip_address、created_at
- 关键查询模式
- 搜索与分页:先 Count 再分页,支持关键词、类型、公开性筛选
- 收藏列表:子查询 + 分页 + 预加载
- 并发计数:表达式更新,避免竞态
章节来源
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L63-L110)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L190-L221)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)