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

546 lines
21 KiB
Markdown
Raw Normal View History

# 数据模型
<cite>
**本文引用的文件**
- [internal/model/user.go](file://internal/model/user.go)
- [internal/model/texture.go](file://internal/model/texture.go)
- [internal/model/profile.go](file://internal/model/profile.go)
- [internal/model/system_config.go](file://internal/model/system_config.go)
- [internal/model/audit_log.go](file://internal/model/audit_log.go)
- [internal/model/token.go](file://internal/model/token.go)
- [internal/model/yggdrasil.go](file://internal/model/yggdrasil.go)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go)
- [pkg/database/manager.go](file://pkg/database/manager.go)
- [pkg/database/postgres.go](file://pkg/database/postgres.go)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向 CarrotSkin 项目的数据库层,聚焦于核心实体 User、Texture、Profile 和 SystemConfig 的数据模型设计。内容涵盖字段定义、数据类型、主键/外键关系、索引与约束、业务语义与验证规则并提供实体关系图ERD、示例数据与数据访问模式说明帮助初学者快速理解同时为有经验的开发者提供性能优化与迁移策略建议。
## 项目结构
围绕数据模型的关键目录与文件:
- 模型层internal/model 下的各实体模型文件
- 仓储层internal/repository 下的 CRUD 与聚合查询实现
- 数据库层pkg/database 提供连接、迁移与连接池管理
```mermaid
graph TB
subgraph "模型层"
MUser["User<br/>internal/model/user.go"]
MTexture["Texture<br/>internal/model/texture.go"]
MProfile["Profile<br/>internal/model/profile.go"]
MSystemConfig["SystemConfig<br/>internal/model/system_config.go"]
MAudit["AuditLog<br/>internal/model/audit_log.go"]
MToken["Token<br/>internal/model/token.go"]
MYgg["Yggdrasil<br/>internal/model/yggdrasil.go"]
end
subgraph "仓储层"
RUser["UserRepository<br/>internal/repository/user_repository.go"]
RTexture["TextureRepository<br/>internal/repository/texture_repository.go"]
RProfile["ProfileRepository<br/>internal/repository/profile_repository.go"]
RSystem["SystemConfigRepository<br/>internal/repository/system_config_repository.go"]
end
subgraph "数据库层"
DManager["Database Manager<br/>pkg/database/manager.go"]
DPostgres["Postgres Driver<br/>pkg/database/postgres.go"]
end
MUser --> RUser
MTexture --> RTexture
MProfile --> RProfile
MSystemConfig --> RSystem
RUser --> DManager
RTexture --> DManager
RProfile --> DManager
RSystem --> DManager
DManager --> DPostgres
```
图表来源
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
- [internal/model/profile.go](file://internal/model/profile.go#L1-L64)
- [internal/model/system_config.go](file://internal/model/system_config.go#L1-L42)
- [internal/model/audit_log.go](file://internal/model/audit_log.go#L1-L46)
- [internal/model/token.go](file://internal/model/token.go#L1-L15)
- [internal/model/yggdrasil.go](file://internal/model/yggdrasil.go#L1-L48)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)
- [pkg/database/manager.go](file://pkg/database/manager.go#L1-L114)
- [pkg/database/postgres.go](file://pkg/database/postgres.go#L1-L74)
章节来源
- [pkg/database/manager.go](file://pkg/database/manager.go#L52-L99)
- [pkg/database/postgres.go](file://pkg/database/postgres.go#L13-L60)
## 核心组件
本节对四个核心实体进行字段级说明,包括数据类型、约束、索引与业务含义。
- User用户
- 主键id自增整数
- 唯一索引username、email
- 字段要点:
- username字符串唯一用于登录与标识
- email字符串唯一用于找回密码与通知
- avatar字符串头像 URL
- points整数积分余额支持增减与日志追踪
- role字符串默认“user”用于权限控制
- status小整数1 正常、0 禁用、-1 删除(软删除)
- propertiesJSONB存储扩展属性
- last_login_at时间戳最近登录时间
- created_at/updated_at时间戳默认 CURRENT_TIMESTAMP
- 业务规则:
- 软删除通过 status 字段实现
- 登录日志与积分日志分别记录在 user_login_logs 与 user_point_logs
- Texture材质
- 主键id自增整数
- 外键uploader_id → User.id
- 唯一索引hashSHA-256
- 字段要点:
- uploader_id整数上传者
- name/description名称与描述
- type枚举SKIN 或 CAPE
- url字符串材质资源地址
- hash字符串64唯一用于去重
- size整数字节数
- is_public布尔是否公开
- download_count/favorite_count整数统计指标带索引
- is_slim布尔是否 Alex模型
- status小整数1 正常、0 审核中、-1 删除(软删除)
- created_at/updated_at时间戳
- 业务规则:
- is_public + type + status 组合索引用于检索
- 下载与收藏计数采用表达式更新,避免并发竞争导致的丢失更新
- Profile档案
- 主键uuid字符串36 位)
- 外键user_id → User.id
- 唯一索引name角色名
- 字段要点:
- uuid档案 UUID
- user_id整数所属用户
- name字符串Minecraft 角色名,唯一
- skin_id/cape_id整数关联 Texture.id
- rsa_private_key文本RSA 私钥(不对外返回)
- is_active布尔是否为当前激活档案
- last_used_at时间戳最近使用时间
- created_at/updated_at时间戳
- 业务规则:
- 激活档案切换时,同一用户下其他档案会被置为非激活
- 支持预加载 Skin/Cape 关联实体
- SystemConfig系统配置
- 主键id自增整数
- 唯一索引key
- 字段要点:
- key字符串配置键唯一
- value文本配置值
- description字符串描述
- type枚举STRING/INTEGER/BOOLEAN/JSON
- is_public布尔是否允许前端读取
- created_at/updated_at时间戳
- 业务规则:
- is_public 为 true 的配置可作为公开响应的一部分返回
章节来源
- [internal/model/user.go](file://internal/model/user.go#L7-L21)
- [internal/model/user.go](file://internal/model/user.go#L28-L71)
- [internal/model/texture.go](file://internal/model/texture.go#L15-L40)
- [internal/model/texture.go](file://internal/model/texture.go#L42-L77)
- [internal/model/profile.go](file://internal/model/profile.go#L7-L29)
- [internal/model/system_config.go](file://internal/model/system_config.go#L17-L32)
## 架构总览
数据库层通过连接管理器统一初始化与迁移,模型定义通过 GORM 注解映射到 PostgreSQL 表结构。迁移顺序遵循“先被引用表,后引用表”的原则,确保外键约束可用。
```mermaid
sequenceDiagram
participant App as "应用启动"
participant DBMgr as "Database Manager"
participant PG as "Postgres Driver"
participant ORM as "GORM"
participant Repo as "Repositories"
App->>DBMgr : Init(cfg, logger)
DBMgr->>PG : New(cfg)
PG-->>DBMgr : *gorm.DB
DBMgr->>ORM : AutoMigrate(models...)
ORM-->>DBMgr : 迁移完成
App->>Repo : 使用仓储层进行CRUD
Repo->>ORM : 执行查询/更新
ORM-->>Repo : 结果
```
图表来源
- [pkg/database/manager.go](file://pkg/database/manager.go#L22-L99)
- [pkg/database/postgres.go](file://pkg/database/postgres.go#L13-L60)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)
## 详细组件分析
### 实体关系图ERD
以下 ERD 展示了 User、Texture、Profile、SystemConfig 的主键、外键与关键索引。
```mermaid
erDiagram
USER {
bigint id PK
varchar username UK
varchar email UK
varchar avatar
integer points
varchar role
smallint status
jsonb properties
timestamp last_login_at
timestamp created_at
timestamp updated_at
}
TEXTURE {
bigint id PK
bigint uploader_id FK
varchar name
text description
varchar 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
}
PROFILE {
varchar uuid PK
bigint user_id FK
varchar name UK
bigint skin_id
bigint cape_id
text rsa_private_key
boolean is_active
timestamp last_used_at
timestamp created_at
timestamp updated_at
}
SYSTEM_CONFIG {
bigint id PK
varchar key UK
text value
varchar description
varchar type
boolean is_public
timestamp created_at
timestamp updated_at
}
USER ||--o{ TEXTURE : "uploader_id -> id"
USER ||--o{ PROFILE : "user_id -> id"
TEXTURE ||--o{ PROFILE : "skin_id/cape_id -> id"
```
图表来源
- [internal/model/user.go](file://internal/model/user.go#L7-L21)
- [internal/model/texture.go](file://internal/model/texture.go#L15-L40)
- [internal/model/profile.go](file://internal/model/profile.go#L7-L29)
- [internal/model/system_config.go](file://internal/model/system_config.go#L17-L32)
### User 模型与访问模式
- 字段与约束
- 主键id
- 唯一索引username、email
- 状态软删除status=-1 表示删除
- JSONB 扩展属性properties
- 关联与日志
- UserPointLog记录积分变动含 operator_id 关联操作人
- UserLoginLog记录登录来源与结果
- 仓储能力
- 基础 CRUD、按用户名/邮箱查询、软删除
- 事务内更新积分并写入日志
- 更新头像、邮箱等字段
```mermaid
sequenceDiagram
participant Svc as "服务层"
participant Repo as "UserRepository"
participant DB as "GORM"
participant Log as "UserPointLog"
Svc->>Repo : UpdateUserPoints(userID, amount, type, reason)
Repo->>DB : 事务开始
Repo->>DB : 查询用户当前积分
Repo->>DB : 校验余额防止负值
Repo->>DB : 更新用户积分
Repo->>DB : 创建积分日志(Log)
DB-->>Repo : 提交事务
Repo-->>Svc : 返回结果
```
图表来源
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L89-L124)
- [internal/model/user.go](file://internal/model/user.go#L28-L71)
章节来源
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [internal/model/user.go](file://internal/model/user.go#L7-L71)
### Texture 模型与访问模式
- 字段与约束
- 主键id唯一索引hash
- 外键uploader_id → User.id
- 组合索引is_public + type + statusdownload_count/favorite_count 带索引
- 关联与日志
- UserTextureFavorite用户收藏材质联合唯一索引 uk_user_texture
- TextureDownloadLog下载记录
- 仓储能力
- 创建、按 ID/Hash 查询、分页与搜索(关键词、类型、公开度)
- 软删除status=-1
- 表达式更新下载/收藏计数,避免竞态
- 收藏/取消收藏与查询用户收藏列表
```mermaid
flowchart TD
Start(["搜索入口"]) --> BuildQuery["构建基础查询<br/>status=1"]
BuildQuery --> Public{"仅公开?"}
Public --> |是| ApplyPublic["追加 is_public=true"]
Public --> |否| SkipPublic["跳过公开过滤"]
ApplyPublic --> TypeFilter{"指定类型?"}
SkipPublic --> TypeFilter
TypeFilter --> |是| ApplyType["追加 type=?"]
TypeFilter --> |否| KeywordFilter
ApplyType --> KeywordFilter{"有关键词?"}
KeywordFilter --> |是| ApplyKeyword["name/description LIKE %keyword%"]
KeywordFilter --> |否| Paginate
ApplyKeyword --> Paginate["COUNT + ORDER BY created_at DESC + 分页"]
Paginate --> End(["返回结果"])
```
图表来源
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
- [internal/model/texture.go](file://internal/model/texture.go#L15-L40)
章节来源
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
### Profile 模型与访问模式
- 字段与约束
- 主键uuid唯一索引name
- 外键user_id → User.idskin_id/cape_id → Texture.id
- is_active 控制当前生效档案
- 仓储能力
- 创建、按 uuid/name 查询、按用户查询全部档案
- 设置激活档案(事务内将用户其他档案置为非激活)
- 更新最后使用时间
- KeyPair 的读写JSONB 字段)
```mermaid
sequenceDiagram
participant Svc as "服务层"
participant Repo as "ProfileRepository"
participant DB as "GORM"
Svc->>Repo : SetActiveProfile(uuid, userID)
Repo->>DB : 事务开始
Repo->>DB : 将用户所有档案 is_active=false
Repo->>DB : 将指定档案 is_active=true
DB-->>Repo : 提交事务
Repo-->>Svc : 返回结果
```
图表来源
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L89-L109)
- [internal/model/profile.go](file://internal/model/profile.go#L7-L29)
章节来源
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [internal/model/profile.go](file://internal/model/profile.go#L1-L64)
### SystemConfig 模型与访问模式
- 字段与约束
- 主键id唯一索引key
- 类型枚举STRING/INTEGER/BOOLEAN/JSON
- is_public 控制前端可见性
- 仓储能力
- 按 key 查询、获取公开配置、获取全部配置、更新值
章节来源
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)
- [internal/model/system_config.go](file://internal/model/system_config.go#L1-L42)
### 其他相关模型与迁移顺序
- AuditLog审计日志记录用户行为与资源变更含 JSONB 字段与多维索引
- Token认证令牌模型表名为 token用于 Yggdrasil 等流程
- Yggdrasil与 User 一对一关联User 创建后自动同步生成随机密码记录
迁移顺序AutoMigrate
- 先创建被引用表User、UserPointLog、UserLoginLog
- 再创建引用表Profile、Texture、UserTextureFavorite、TextureDownloadLog、Token、Yggdrasil
- 最后创建 SystemConfig、AuditLog、CasbinRule
章节来源
- [internal/model/audit_log.go](file://internal/model/audit_log.go#L1-L46)
- [internal/model/token.go](file://internal/model/token.go#L1-L15)
- [internal/model/yggdrasil.go](file://internal/model/yggdrasil.go#L1-L48)
- [pkg/database/manager.go](file://pkg/database/manager.go#L52-L99)
## 依赖分析
- 模型到仓储:各模型通过 GORM 注解与仓储层交互,仓储层负责具体查询、更新与事务控制
- 仓储到数据库:统一通过 MustGetDB 获取连接,避免重复初始化
- 迁移顺序AutoMigrate 明确列出迁移顺序,确保外键约束可用
```mermaid
graph LR
MUser["User Model"] --> RUser["UserRepository"]
MTexture["Texture Model"] --> RTexture["TextureRepository"]
MProfile["Profile Model"] --> RProfile["ProfileRepository"]
MSystem["SystemConfig Model"] --> RSystem["SystemConfigRepository"]
RUser --> DMgr["Database Manager"]
RTexture --> DMgr
RProfile --> DMgr
RSystem --> DMgr
DMgr --> DPostgres["Postgres Driver"]
```
图表来源
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)
- [pkg/database/manager.go](file://pkg/database/manager.go#L35-L99)
- [pkg/database/postgres.go](file://pkg/database/postgres.go#L13-L60)
## 性能考量
- 索引策略
- Userusername、email 唯一索引;登录/积分日志按 created_at 倒序索引
- Texturehash 唯一索引is_public + type + status 组合索引download_count/favorite_count 带索引
- Profilename 唯一索引user_id 索引
- SystemConfigkey 唯一索引is_public 索引
- AuditLog多维索引action、resource_type+resource_id、created_at
- 并发与一致性
- 使用表达式更新计数字段download_count、favorite_count避免竞态
- 事务内更新用户积分并写入日志,保证原子性
- 连接池与日志
- 连接池参数由配置注入,建议结合负载压测调整 MaxOpenConns、MaxIdleConns、ConnMaxLifetime
- 日志级别在 Postgres 驱动中按驱动类型配置
章节来源
- [internal/model/user.go](file://internal/model/user.go#L28-L71)
- [internal/model/texture.go](file://internal/model/texture.go#L15-L40)
- [internal/model/texture.go](file://internal/model/texture.go#L42-L77)
- [internal/model/profile.go](file://internal/model/profile.go#L7-L29)
- [internal/model/system_config.go](file://internal/model/system_config.go#L17-L32)
- [internal/model/audit_log.go](file://internal/model/audit_log.go#L7-L27)
- [pkg/database/postgres.go](file://pkg/database/postgres.go#L13-L60)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L89-L124)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
## 故障排查指南
- 数据库未初始化
- 现象:调用 MustGetDB 抛错或 AutoMigrate 返回错误
- 排查:确认已调用 Init(cfg, logger),检查配置项与连接可达性
- 迁移失败
- 现象AutoMigrate 报错
- 排查:检查迁移顺序是否正确(先被引用表,后引用表);确认数据库版本与驱动兼容
- 查询不到记录
- 现象:按 username/email/uuid 查询返回空
- 排查:确认 status 非 -1检查唯一索引是否冲突确认大小写与格式
- 并发计数不一致
- 现象download_count/favorite_count 不准确
- 排查:确认使用表达式更新;避免直接赋值覆盖
- 事务回滚
- 现象:积分更新失败或日志未写入
- 排查:检查事务内错误处理与提交路径
章节来源
- [pkg/database/manager.go](file://pkg/database/manager.go#L22-L33)
- [pkg/database/manager.go](file://pkg/database/manager.go#L52-L99)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L89-L124)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
## 结论
本数据模型围绕 User、Texture、Profile、SystemConfig 四大核心实体,采用 PostgreSQL + GORM 的组合实现,具备完善的索引与约束策略、清晰的迁移顺序与事务保障。通过仓储层抽象,实现了稳定的 CRUD 与复杂查询能力。建议在生产环境中结合监控与压测持续优化连接池与索引策略。
## 附录
### 示例数据(概念性)
- User
- id: 1
- username: "alice"
- email: "alice@example.com"
- points: 100
- role: "user"
- status: 1
- properties: "{}"
- last_login_at: 当前时间
- created_at/updated_at: 当前时间
- Texture
- id: 101
- uploader_id: 1
- name: "Steve Classic"
- type: "SKIN"
- url: "/uploads/skin_101.png"
- hash: "sha256..."
- size: 102400
- is_public: true
- download_count: 120
- favorite_count: 45
- is_slim: false
- status: 1
- created_at/updated_at: 当前时间
- Profile
- uuid: "f47ac10b-62d6-4c6f-8b3c-1234567890ab"
- user_id: 1
- name: "Steve"
- skin_id: 101
- cape_id: null
- rsa_private_key: "..."
- is_active: true
- last_used_at: 当前时间
- created_at/updated_at: 当前时间
- SystemConfig
- id: 1
- key: "site_name"
- value: "CarrotSkin"
- description: "站点名称"
- type: "STRING"
- is_public: true
- created_at/updated_at: 当前时间
### 数据访问模式清单
- User
- 创建/查询/更新/软删除
- 事务内更新积分并写入日志
- Texture
- 创建/查询/按 Hash 去重/分页搜索/软删除
- 表达式更新下载/收藏计数
- 收藏/取消收藏与查询收藏列表
- Profile
- 创建/查询/按用户查询/设置激活档案/更新最后使用时间
- KeyPair 读写JSONB
- SystemConfig
- 按 key 查询/获取公开配置/获取全部配置/更新值
章节来源
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)