21 KiB
21 KiB
数据模型
**本文引用的文件** - [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)目录
简介
本文件面向 CarrotSkin 项目的数据库层,聚焦于核心实体 User、Texture、Profile 和 SystemConfig 的数据模型设计。内容涵盖字段定义、数据类型、主键/外键关系、索引与约束、业务语义与验证规则,并提供实体关系图(ERD)、示例数据与数据访问模式说明,帮助初学者快速理解,同时为有经验的开发者提供性能优化与迁移策略建议。
项目结构
围绕数据模型的关键目录与文件:
- 模型层:internal/model 下的各实体模型文件
- 仓储层:internal/repository 下的 CRUD 与聚合查询实现
- 数据库层:pkg/database 提供连接、迁移与连接池管理
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
- internal/model/texture.go
- internal/model/profile.go
- internal/model/system_config.go
- internal/model/audit_log.go
- internal/model/token.go
- internal/model/yggdrasil.go
- internal/repository/user_repository.go
- internal/repository/texture_repository.go
- internal/repository/profile_repository.go
- internal/repository/system_config_repository.go
- pkg/database/manager.go
- pkg/database/postgres.go
章节来源
核心组件
本节对四个核心实体进行字段级说明,包括数据类型、约束、索引与业务含义。
-
User(用户)
- 主键:id(自增整数)
- 唯一索引:username、email
- 字段要点:
- username:字符串,唯一,用于登录与标识
- email:字符串,唯一,用于找回密码与通知
- avatar:字符串,头像 URL
- points:整数,积分余额,支持增减与日志追踪
- role:字符串,默认“user”,用于权限控制
- status:小整数,1 正常、0 禁用、-1 删除(软删除)
- properties:JSONB,存储扩展属性
- last_login_at:时间戳,最近登录时间
- created_at/updated_at:时间戳,默认 CURRENT_TIMESTAMP
- 业务规则:
- 软删除通过 status 字段实现
- 登录日志与积分日志分别记录在 user_login_logs 与 user_point_logs
-
Texture(材质)
- 主键:id(自增整数)
- 外键:uploader_id → User.id
- 唯一索引:hash(SHA-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
- internal/model/user.go
- internal/model/texture.go
- internal/model/texture.go
- internal/model/profile.go
- internal/model/system_config.go
架构总览
数据库层通过连接管理器统一初始化与迁移,模型定义通过 GORM 注解映射到 PostgreSQL 表结构。迁移顺序遵循“先被引用表,后引用表”的原则,确保外键约束可用。
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
- pkg/database/postgres.go
- internal/repository/user_repository.go
- internal/repository/texture_repository.go
- internal/repository/profile_repository.go
- internal/repository/system_config_repository.go
详细组件分析
实体关系图(ERD)
以下 ERD 展示了 User、Texture、Profile、SystemConfig 的主键、外键与关键索引。
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
- internal/model/texture.go
- internal/model/profile.go
- internal/model/system_config.go
User 模型与访问模式
- 字段与约束
- 主键:id
- 唯一索引:username、email
- 状态软删除:status=-1 表示删除
- JSONB 扩展属性:properties
- 关联与日志
- UserPointLog:记录积分变动,含 operator_id 关联操作人
- UserLoginLog:记录登录来源与结果
- 仓储能力
- 基础 CRUD、按用户名/邮箱查询、软删除
- 事务内更新积分并写入日志
- 更新头像、邮箱等字段
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 : 返回结果
图表来源
章节来源
Texture 模型与访问模式
- 字段与约束
- 主键:id;唯一索引:hash
- 外键:uploader_id → User.id
- 组合索引:is_public + type + status;download_count/favorite_count 带索引
- 关联与日志
- UserTextureFavorite:用户收藏材质(联合唯一索引 uk_user_texture)
- TextureDownloadLog:下载记录
- 仓储能力
- 创建、按 ID/Hash 查询、分页与搜索(关键词、类型、公开度)
- 软删除(status=-1)
- 表达式更新下载/收藏计数,避免竞态
- 收藏/取消收藏与查询用户收藏列表
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(["返回结果"])
图表来源
章节来源
Profile 模型与访问模式
- 字段与约束
- 主键:uuid;唯一索引:name
- 外键:user_id → User.id;skin_id/cape_id → Texture.id
- is_active 控制当前生效档案
- 仓储能力
- 创建、按 uuid/name 查询、按用户查询全部档案
- 设置激活档案(事务内将用户其他档案置为非激活)
- 更新最后使用时间
- KeyPair 的读写(JSONB 字段)
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 : 返回结果
图表来源
章节来源
SystemConfig 模型与访问模式
- 字段与约束
- 主键:id;唯一索引:key
- 类型枚举:STRING/INTEGER/BOOLEAN/JSON
- is_public 控制前端可见性
- 仓储能力
- 按 key 查询、获取公开配置、获取全部配置、更新值
章节来源
其他相关模型与迁移顺序
- 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
- internal/model/token.go
- internal/model/yggdrasil.go
- pkg/database/manager.go
依赖分析
- 模型到仓储:各模型通过 GORM 注解与仓储层交互,仓储层负责具体查询、更新与事务控制
- 仓储到数据库:统一通过 MustGetDB 获取连接,避免重复初始化
- 迁移顺序:AutoMigrate 明确列出迁移顺序,确保外键约束可用
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
- internal/repository/texture_repository.go
- internal/repository/profile_repository.go
- internal/repository/system_config_repository.go
- pkg/database/manager.go
- pkg/database/postgres.go
性能考量
- 索引策略
- User:username、email 唯一索引;登录/积分日志按 created_at 倒序索引
- Texture:hash 唯一索引;is_public + type + status 组合索引;download_count/favorite_count 带索引
- Profile:name 唯一索引;user_id 索引
- SystemConfig:key 唯一索引;is_public 索引
- AuditLog:多维索引(action、resource_type+resource_id、created_at)
- 并发与一致性
- 使用表达式更新计数字段(download_count、favorite_count),避免竞态
- 事务内更新用户积分并写入日志,保证原子性
- 连接池与日志
- 连接池参数由配置注入,建议结合负载压测调整 MaxOpenConns、MaxIdleConns、ConnMaxLifetime
- 日志级别在 Postgres 驱动中按驱动类型配置
章节来源
- internal/model/user.go
- internal/model/texture.go
- internal/model/texture.go
- internal/model/profile.go
- internal/model/system_config.go
- internal/model/audit_log.go
- pkg/database/postgres.go
- internal/repository/user_repository.go
- internal/repository/texture_repository.go
故障排查指南
- 数据库未初始化
- 现象:调用 MustGetDB 抛错或 AutoMigrate 返回错误
- 排查:确认已调用 Init(cfg, logger),检查配置项与连接可达性
- 迁移失败
- 现象:AutoMigrate 报错
- 排查:检查迁移顺序是否正确(先被引用表,后引用表);确认数据库版本与驱动兼容
- 查询不到记录
- 现象:按 username/email/uuid 查询返回空
- 排查:确认 status 非 -1;检查唯一索引是否冲突;确认大小写与格式
- 并发计数不一致
- 现象:download_count/favorite_count 不准确
- 排查:确认使用表达式更新;避免直接赋值覆盖
- 事务回滚
- 现象:积分更新失败或日志未写入
- 排查:检查事务内错误处理与提交路径
章节来源
- pkg/database/manager.go
- pkg/database/manager.go
- internal/repository/user_repository.go
- internal/repository/texture_repository.go
结论
本数据模型围绕 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 查询/获取公开配置/获取全部配置/更新值
章节来源