# 数据模型 **本文引用的文件** - [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) ## 目录 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
internal/model/user.go"] MTexture["Texture
internal/model/texture.go"] MProfile["Profile
internal/model/profile.go"] MSystemConfig["SystemConfig
internal/model/system_config.go"] MAudit["AuditLog
internal/model/audit_log.go"] MToken["Token
internal/model/token.go"] MYgg["Yggdrasil
internal/model/yggdrasil.go"] end subgraph "仓储层" RUser["UserRepository
internal/repository/user_repository.go"] RTexture["TextureRepository
internal/repository/texture_repository.go"] RProfile["ProfileRepository
internal/repository/profile_repository.go"] RSystem["SystemConfigRepository
internal/repository/system_config_repository.go"] end subgraph "数据库层" DManager["Database Manager
pkg/database/manager.go"] DPostgres["Postgres Driver
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 删除(软删除) - 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](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 + status;download_count/favorite_count 带索引 - 关联与日志 - UserTextureFavorite:用户收藏材质(联合唯一索引 uk_user_texture) - TextureDownloadLog:下载记录 - 仓储能力 - 创建、按 ID/Hash 查询、分页与搜索(关键词、类型、公开度) - 软删除(status=-1) - 表达式更新下载/收藏计数,避免竞态 - 收藏/取消收藏与查询用户收藏列表 ```mermaid flowchart TD Start(["搜索入口"]) --> BuildQuery["构建基础查询
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.id;skin_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) ## 性能考量 - 索引策略 - 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](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)