408 lines
19 KiB
Markdown
408 lines
19 KiB
Markdown
# 材质模型
|
||
|
||
<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:文件大小(字节)
|
||
- IsSlim:Slim 模型标识(Alex 细臂为 true,Steve 粗臂为 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.id;favorites 与 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 的并发安全更新,实现了高性能、可扩展且易维护的材质管理能力。状态机与公开性控制使内容治理更加灵活,收藏与下载日志完善了用户行为追踪与运营分析基础。建议在后续迭代中持续关注索引命中率与查询计划,配合缓存与异步任务进一步优化热点查询与批量操作。
|
||
|
||
## 附录
|
||
- 字段与索引对照
|
||
- textures:uploader_id、is_public+type+status、download_count、favorite_count、hash 唯一索引
|
||
- user_texture_favorites:user_id、texture_id、created_at、uk_user_texture
|
||
- texture_download_logs:texture_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) |