chore(git): 更新.gitignore以忽略新的本地文件
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled

This commit is contained in:
lan
2025-11-30 08:33:17 +08:00
parent 4b4980820f
commit a4b6c5011e
58 changed files with 19353 additions and 0 deletions

View File

@@ -0,0 +1,546 @@
# 数据模型
<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)

View File

@@ -0,0 +1,408 @@
# 材质模型
<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)

View File

@@ -0,0 +1,473 @@
# 档案模型
<cite>
**本文引用的文件**
- [internal/model/profile.go](file://internal/model/profile.go)
- [internal/service/profile_service.go](file://internal/service/profile_service.go)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go)
- [internal/model/texture.go](file://internal/model/texture.go)
- [internal/model/user.go](file://internal/model/user.go)
- [internal/model/yggdrasil.go](file://internal/model/yggdrasil.go)
- [internal/service/yggdrasil_service.go](file://internal/service/yggdrasil_service.go)
- [internal/types/common.go](file://internal/types/common.go)
- [internal/service/profile_service_test.go](file://internal/service/profile_service_test.go)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件围绕Minecraft档案模型进行系统化技术文档整理重点覆盖以下主题
- Profile结构体的核心字段UUID、Name、SkinID、CapeID以及RSA密钥对的安全存储机制
- 档案激活状态IsActive与最后使用时间LastUsedAt的业务意义
- 与用户User、皮肤Texture的外键关联关系及其在Yggdrasil协议中的作用
- ProfileResponse响应结构的设计原理包括Textures数据的嵌套格式与元数据metadata中模型类型slim/classic的表示方式
- UUID命名规范、角色名唯一性约束以及密钥轮换策略的技术说明并结合实际API响应示例进行说明
## 项目结构
本项目采用分层架构,档案模型位于内部模型层,服务层负责业务流程编排,仓储层负责数据持久化,类型定义用于请求/响应契约与校验。
```mermaid
graph TB
subgraph "模型层"
M_Profile["Profile<br/>profiles 表"]
M_Texture["Texture<br/>textures 表"]
M_User["User<br/>user 表"]
M_Ygg["Yggdrasil<br/>Yggdrasil 表"]
end
subgraph "服务层"
S_Profile["ProfileService<br/>档案业务逻辑"]
S_Ygg["YggdrasilService<br/>Yggdrasil协议集成"]
end
subgraph "仓储层"
R_Profile["ProfileRepository<br/>档案数据访问"]
end
M_Profile --> M_User
M_Profile --> M_Texture
S_Profile --> R_Profile
S_Ygg --> R_Profile
S_Ygg --> M_Profile
S_Ygg --> M_Ygg
```
图表来源
- [internal/model/profile.go](file://internal/model/profile.go#L1-L64)
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
- [internal/model/yggdrasil.go](file://internal/model/yggdrasil.go#L1-L49)
- [internal/service/profile_service.go](file://internal/service/profile_service.go#L1-L253)
- [internal/service/yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L202)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
章节来源
- [internal/model/profile.go](file://internal/model/profile.go#L1-L64)
- [internal/service/profile_service.go](file://internal/service/profile_service.go#L1-L253)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
- [internal/model/yggdrasil.go](file://internal/model/yggdrasil.go#L1-L49)
- [internal/service/yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L202)
## 核心组件
- ProfileMinecraft档案实体包含UUID、角色名、皮肤/披风ID、RSA私钥、激活状态、最后使用时间等字段并与User、Texture建立关联。
- ProfileResponse对外响应结构包含UUID、角色名、Textures含皮肤/披风URL与metadata模型类型、IsActive、LastUsedAt、CreatedAt。
- KeyPair密钥对结构包含私钥、公钥、过期时间、刷新时间用于安全存储与轮换。
- Texture材质实体支持皮肤/披风类型、URL、哈希、是否公开、下载/收藏计数、是否slim等属性。
- Yggdrasil与用户绑定的Yggdrasil密码实体用于协议认证与会话管理。
章节来源
- [internal/model/profile.go](file://internal/model/profile.go#L1-L64)
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
- [internal/model/yggdrasil.go](file://internal/model/yggdrasil.go#L1-L49)
## 架构总览
档案模型贯穿“模型-服务-仓储-外部协议”的完整链路。服务层负责创建/更新/删除档案、设置活跃档案、更新最后使用时间、生成RSA密钥对仓储层负责数据库读写与事务控制模型层定义表结构与关联类型层定义请求/响应契约Yggdrasil服务层负责与外部协议交互如会话数据存储与校验
```mermaid
sequenceDiagram
participant Client as "客户端"
participant Handler as "处理器"
participant Service as "ProfileService"
participant Repo as "ProfileRepository"
participant DB as "数据库"
participant Ygg as "YggdrasilService"
Client->>Handler : "创建档案/更新档案/设置活跃档案"
Handler->>Service : "调用业务方法"
Service->>Repo : "查询/更新/事务"
Repo->>DB : "执行SQL/GORM操作"
DB-->>Repo : "返回结果"
Repo-->>Service : "返回实体/影响行数"
Service-->>Handler : "返回业务结果"
Handler-->>Client : "返回ProfileResponse/错误"
Note over Service,DB : "设置活跃档案时,服务层会调用仓储层更新最后使用时间"
Service->>Ygg : "JoinServer/HasJoinedServer与Yggdrasil协议交互"
```
图表来源
- [internal/service/profile_service.go](file://internal/service/profile_service.go#L1-L253)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [internal/service/yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L202)
## 详细组件分析
### Profile结构体与外键关联
- 字段说明
- UUID档案唯一标识主键长度为36标准UUID格式
- Name角色名最大16字符全局唯一索引
- SkinID/CapeID指向Texture表的外键允许为空
- RSAPrivateKeyRSA私钥PEM格式不返回给前端
- IsActive是否为当前活跃档案默认true带索引
- LastUsedAt最后使用时间用于统计与排序
- CreatedAt/UpdatedAt记录创建与更新时间戳
- 关联关系
- Profile.UserID -> User.ID
- Profile.SkinID -> Texture.ID
- Profile.CapeID -> Texture.ID
```mermaid
classDiagram
class Profile {
+string UUID
+int64 UserID
+string Name
+int64* SkinID
+int64* CapeID
+string RSAPrivateKey
+bool IsActive
+time.Time* LastUsedAt
+time.Time CreatedAt
+time.Time UpdatedAt
}
class User {
+int64 ID
+string Username
+string Email
+string Role
+int16 Status
+time.Time* LastLoginAt
+time.Time CreatedAt
+time.Time UpdatedAt
}
class Texture {
+int64 ID
+int64 UploaderID
+string Name
+string URL
+string Hash
+bool IsPublic
+int DownloadCount
+int FavoriteCount
+bool IsSlim
+int16 Status
+time.Time CreatedAt
+time.Time UpdatedAt
}
Profile --> User : "外键 UserID"
Profile --> Texture : "外键 SkinID"
Profile --> Texture : "外键 CapeID"
```
图表来源
- [internal/model/profile.go](file://internal/model/profile.go#L1-L64)
- [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/user.go](file://internal/model/user.go#L1-L71)
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
### ProfileResponse响应结构设计
- 结构组成
- uuid/name/is_active/last_used_at/created_at基础档案信息
- textures包含皮肤与披风两个子项
- SKIN/CAPE每个项包含url与metadata
- metadata.model取值为"slim"或"classic",用于指示模型类型
- 设计原则
- 以Yggdrasil协议兼容为目标textures字段直接映射皮肤/披风资源与元数据
- 通过枚举化的model字段明确区分Alex细臂与Steve粗臂模型
```mermaid
classDiagram
class ProfileResponse {
+string uuid
+string name
+ProfileTexturesData textures
+bool is_active
+time.Time* last_used_at
+time.Time created_at
}
class ProfileTexturesData {
+ProfileTexture* SKIN
+ProfileTexture* CAPE
}
class ProfileTexture {
+string url
+ProfileTextureMetadata* metadata
}
class ProfileTextureMetadata {
+string model
}
ProfileResponse --> ProfileTexturesData
ProfileTexturesData --> ProfileTexture
ProfileTexture --> ProfileTextureMetadata
```
图表来源
- [internal/model/profile.go](file://internal/model/profile.go#L31-L64)
章节来源
- [internal/model/profile.go](file://internal/model/profile.go#L31-L64)
### RSA密钥对的安全存储机制
- 生成与存储
- 服务层在创建档案时生成RSA-2048私钥PEM格式并保存至Profile.RSAPrivateKey字段
- 私钥不返回给前端,避免泄露风险
- 读取与轮换
- 仓储层提供GetProfileKeyPair/UpdateProfileKeyPair接口支持从数据库读取与更新密钥对
- KeyPair结构体包含私钥、公钥、过期时间、刷新时间便于后续密钥轮换策略落地
- 安全建议
- 建议在密钥过期前主动轮换,更新数据库中的密钥对并同步到缓存/内存
- 对敏感字段进行最小暴露,仅在必要时解密或传输
```mermaid
flowchart TD
Start(["开始"]) --> Gen["生成RSA-2048私钥PEM"]
Gen --> Save["保存至Profile.RSAPrivateKey"]
Save --> Use["对外响应不返回私钥"]
Use --> Rotate{"是否需要轮换?"}
Rotate --> |否| End(["结束"])
Rotate --> |是| Update["更新数据库中的密钥对"]
Update --> End
```
图表来源
- [internal/service/profile_service.go](file://internal/service/profile_service.go#L204-L220)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L139-L199)
- [internal/model/profile.go](file://internal/model/profile.go#L1-L64)
章节来源
- [internal/service/profile_service.go](file://internal/service/profile_service.go#L204-L220)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L139-L199)
- [internal/model/profile.go](file://internal/model/profile.go#L1-L64)
### 档案激活状态与最后使用时间的业务意义
- IsActive
- 用于标记当前用户所选中的活跃档案,同一用户下仅有一个档案处于活跃状态
- 设置活跃档案时,服务层会将该用户其他档案置为非活跃
- LastUsedAt
- 每当设置活跃档案时,服务层会更新该字段为当前时间
- 用于统计与排序,帮助用户快速识别最近使用的档案
```mermaid
sequenceDiagram
participant Client as "客户端"
participant Service as "ProfileService"
participant Repo as "ProfileRepository"
participant DB as "数据库"
Client->>Service : "设置活跃档案"
Service->>Repo : "将用户其他档案置为非活跃"
Repo->>DB : "UPDATE profiles SET is_active=false WHERE user_id=?"
Service->>Repo : "将目标档案置为活跃"
Repo->>DB : "UPDATE profiles SET is_active=true WHERE uuid=? AND user_id=?"
Service->>Repo : "更新最后使用时间"
Repo->>DB : "UPDATE profiles SET last_used_at=CURRENT_TIMESTAMP WHERE uuid=?"
DB-->>Repo : "返回影响行数"
Repo-->>Service : "返回成功"
Service-->>Client : "返回成功"
```
图表来源
- [internal/service/profile_service.go](file://internal/service/profile_service.go#L161-L188)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L89-L117)
章节来源
- [internal/service/profile_service.go](file://internal/service/profile_service.go#L161-L188)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L89-L117)
### 与用户User与皮肤Texture的外键关联关系
- Profile.UserID -> User.ID
- 一对多:一个用户可拥有多个档案
- Profile.SkinID/CapeID -> Texture.ID
- 多对一:一个档案可关联到一张皮肤与一张披风(均可为空)
```mermaid
erDiagram
USER {
int64 id PK
string username UK
string email UK
string role
int16 status
timestamp last_login_at
timestamp created_at
timestamp updated_at
}
TEXTURE {
int64 id PK
int64 uploader_id FK
string name
string url
string hash UK
bool is_public
int download_count
int favorite_count
bool is_slim
int16 status
timestamp created_at
timestamp updated_at
}
PROFILE {
string uuid PK
int64 user_id FK
string name UK
int64* skin_id FK
int64* cape_id FK
text rsa_private_key
bool is_active
timestamp last_used_at
timestamp created_at
timestamp updated_at
}
USER ||--o{ PROFILE : "拥有"
TEXTURE ||--o{ PROFILE : "被使用"
```
图表来源
- [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/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)
### 在Yggdrasil协议中的作用
- Profile与Yggdrasil的关系
- ProfileResponse中的textures字段用于向客户端提供皮肤/披风资源与模型元数据满足Yggdrasil协议对纹理与模型类型的要求
- Yggdrasil实体与User存在一对一关联用于协议认证与会话管理
- 会话与验证
- 服务层提供JoinServer/HasJoinedServer方法将会话数据写入Redis并进行用户名/IP校验确保玩家加入服务器的合法性
- 会话数据包含accessToken、userName、selectedProfile、ip等关键字段
```mermaid
sequenceDiagram
participant Client as "客户端"
participant YggSvc as "YggdrasilService"
participant TokenRepo as "TokenRepository"
participant ProfRepo as "ProfileRepository"
participant Redis as "Redis"
Client->>YggSvc : "JoinServer(serverId, accessToken, selectedProfile, ip)"
YggSvc->>TokenRepo : "根据accessToken查询Token"
TokenRepo-->>YggSvc : "返回Token"
YggSvc->>ProfRepo : "根据ProfileId查询Profile"
ProfRepo-->>YggSvc : "返回Profile"
YggSvc->>Redis : "写入会话数据Join_前缀+serverId"
Redis-->>YggSvc : "成功"
YggSvc-->>Client : "返回成功"
```
图表来源
- [internal/service/yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L81-L163)
- [internal/model/yggdrasil.go](file://internal/model/yggdrasil.go#L1-L49)
章节来源
- [internal/service/yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L202)
- [internal/model/yggdrasil.go](file://internal/model/yggdrasil.go#L1-L49)
### UUID命名规范、角色名唯一性约束与密钥轮换策略
- UUID命名规范
- Profile.UUID为主键采用标准36字符格式包含连字符
- 服务层在创建档案时使用标准库生成新UUID
- 角色名唯一性约束
- Profile.Name具有唯一索引服务层在创建/更新时均进行冲突检测
- 请求/响应契约中对名称长度有严格限制1-16字符
- 密钥轮换策略
- KeyPair结构体提供过期时间与刷新时间字段便于实现周期性轮换
- 建议在过期前主动生成新密钥对并更新数据库,同时同步到缓存/内存,确保服务可用性
章节来源
- [internal/model/profile.go](file://internal/model/profile.go#L1-L64)
- [internal/types/common.go](file://internal/types/common.go#L181-L206)
- [internal/service/profile_service.go](file://internal/service/profile_service.go#L18-L69)
- [internal/service/profile_service_test.go](file://internal/service/profile_service_test.go#L348-L406)
## 依赖分析
- 组件耦合
- ProfileService高度依赖ProfileRepository与数据库/GORM
- ProfileRepository对数据库连接与事务有直接依赖
- YggdrasilService依赖Redis与TokenRepository间接依赖ProfileRepository
- 关联关系
- Profile与User/Texture通过GORM外键注解建立关联
- ProfileResponse与Profile/Texture的嵌套结构映射清晰便于序列化/反序列化
```mermaid
graph LR
ProfileService --> ProfileRepository
ProfileRepository --> Database["GORM/PostgreSQL"]
YggdrasilService --> Redis["Redis"]
YggdrasilService --> ProfileRepository
Profile --> User
Profile --> Texture
```
图表来源
- [internal/service/profile_service.go](file://internal/service/profile_service.go#L1-L253)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [internal/service/yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L202)
章节来源
- [internal/service/profile_service.go](file://internal/service/profile_service.go#L1-L253)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [internal/service/yggdrasil_service.go](file://internal/service/yggdrasil_service.go#L1-L202)
## 性能考虑
- 索引优化
- Profile.Name具备唯一索引减少重复角色名查询成本
- Profile.UserID与IsActive具备索引提升活跃档案切换与查询效率
- 预加载策略
- 仓储层在查询档案时预加载Skin/Cape关联避免N+1查询
- 事务与一致性
- 设置活跃档案采用事务,确保原子性与一致性
- 缓存与会话
- Yggdrasil会话数据写入Redis降低频繁查询数据库的压力
## 故障排查指南
- 常见错误与定位
- 角色名冲突:创建/更新时若返回“角色名已被使用”检查Profile.Name唯一性约束与服务层校验逻辑
- 权限不足操作他人档案会返回“无权操作此档案”检查服务层对Profile.UserID与传入userID的比对
- 档案不存在:查询/更新/删除时若返回“档案不存在”检查UUID格式与仓储层查询条件
- 密钥读取失败GetProfileKeyPair返回“未找到”或错误检查数据库字段映射与查询条件
- 调试建议
- 在服务层与仓储层增加日志输出,定位具体环节(查询、更新、事务)
- 使用单元测试验证请求/响应契约与边界条件名称长度、UUID格式、密钥PEM格式
章节来源
- [internal/service/profile_service.go](file://internal/service/profile_service.go#L71-L159)
- [internal/repository/profile_repository.go](file://internal/repository/profile_repository.go#L139-L199)
- [internal/service/profile_service_test.go](file://internal/service/profile_service_test.go#L348-L406)
## 结论
本档案模型围绕Profile为核心结合ProfileResponse的纹理与元数据设计满足Minecraft Yggdrasil协议对皮肤/披风与模型类型的要求。通过严格的唯一性约束、索引优化与事务保障确保了数据一致性与性能。RSA密钥对的安全存储与KeyPair结构为后续密钥轮换提供了基础。与User/Texture的外键关联清晰地刻画了用户与材质的使用关系配合Yggdrasil服务层的会话管理形成完整的档案生命周期闭环。
## 附录
- API响应示例概念性说明
- ProfileResponse示例包含uuid、name、textures含SKIN/CAPE、is_active、last_used_at、created_at等字段
- textures.metadata.model取值为"slim"或"classic",用于指示模型类型
- 请求/响应契约要点
- UpdateProfileRequest对name长度与skin_id/cape_id进行约束
- CreateProfileRequest对角色名长度与类型进行约束
章节来源
- [internal/model/profile.go](file://internal/model/profile.go#L31-L64)
- [internal/types/common.go](file://internal/types/common.go#L181-L206)

View File

@@ -0,0 +1,451 @@
# 用户模型
<cite>
**本文引用的文件**
- [internal/model/user.go](file://internal/model/user.go)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go)
- [internal/service/user_service.go](file://internal/service/user_service.go)
- [pkg/auth/password.go](file://pkg/auth/password.go)
- [pkg/database/postgres.go](file://pkg/database/postgres.go)
- [internal/model/profile.go](file://internal/model/profile.go)
- [internal/model/system_config.go](file://internal/model/system_config.go)
- [internal/service/serialize_service.go](file://internal/service/serialize_service.go)
- [internal/service/user_service_test.go](file://internal/service/user_service_test.go)
- [internal/repository/user_repository_test.go](file://internal/repository/user_repository_test.go)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件聚焦于用户模型 User 的字段定义、数据类型、GORM 标签与业务含义,以及与 UserPointLog、UserLoginLog 的关联关系。同时提供用户状态流转图、积分系统使用模式、属性扩展Properties JSONB的实践建议并给出数据验证规则、唯一性约束与索引优化建议最后结合代码路径说明如何安全地处理密码存储与敏感信息脱敏。
## 项目结构
围绕用户模型的关键文件分布如下:
- 模型层:用户、积分日志、登录日志、档案等
- 仓储层:用户 CRUD、积分更新、登录日志创建等
- 服务层:注册、登录、修改密码、更换邮箱等业务流程
- 安全层:密码哈希与校验
- 数据库层PostgreSQL 连接与配置
```mermaid
graph TB
subgraph "模型层"
M_User["User<br/>internal/model/user.go"]
M_PointLog["UserPointLog<br/>internal/model/user.go"]
M_LoginLog["UserLoginLog<br/>internal/model/user.go"]
M_Profile["Profile<br/>internal/model/profile.go"]
end
subgraph "仓储层"
R_UserRepo["UserRepository<br/>internal/repository/user_repository.go"]
end
subgraph "服务层"
S_UserSvc["UserService<br/>internal/service/user_service.go"]
S_Serialize["SerializeService<br/>internal/service/serialize_service.go"]
end
subgraph "安全层"
A_Password["Password Hash/Check<br/>pkg/auth/password.go"]
end
subgraph "数据库层"
D_Postgres["PostgreSQL连接<br/>pkg/database/postgres.go"]
end
S_UserSvc --> R_UserRepo
R_UserRepo --> D_Postgres
S_UserSvc --> A_Password
S_Serialize --> M_User
M_User --> M_PointLog
M_User --> M_LoginLog
M_Profile --> M_User
```
图表来源
- [internal/model/user.go](file://internal/model/user.go#L1-L70)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
- [pkg/database/postgres.go](file://pkg/database/postgres.go#L1-L74)
- [internal/model/profile.go](file://internal/model/profile.go#L1-L64)
- [internal/service/serialize_service.go](file://internal/service/serialize_service.go#L86-L97)
章节来源
- [internal/model/user.go](file://internal/model/user.go#L1-L70)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
- [pkg/database/postgres.go](file://pkg/database/postgres.go#L1-L74)
- [internal/model/profile.go](file://internal/model/profile.go#L1-L64)
- [internal/service/serialize_service.go](file://internal/service/serialize_service.go#L86-L97)
## 核心组件
本节对 User 结构体及其关键字段进行逐项解析涵盖字段类型、GORM 标签、默认值、业务语义与约束。
- 字段与类型
- ID整型自增主键用于唯一标识用户
- Username字符串长度限制与唯一索引约束
- Password字符串存储哈希后的密码JSON 中不返回
- Email字符串长度限制与唯一索引约束
- Avatar字符串头像 URL默认空串
- Points整型积分余额默认 0
- Role字符串角色默认 "user"
- Status短整型状态枚举默认 1正常0禁用-1删除
- Properties字符串映射 PostgreSQL 的 JSONB 类型,用于扩展属性
- LastLoginAt时间戳最近登录时间
- CreatedAt/UpdatedAt时间戳默认 CURRENT_TIMESTAMP
- 约束与索引
- Username、Email 唯一索引
- UserPointLog、UserLoginLog 的 UserID、IP 地址、is_success 等字段建立索引
- Properties 以 JSONB 存储,便于灵活扩展
- 业务含义
- 注册时默认 Role 为 "user"Status 为 1正常Points 为 0
- 登录成功后更新 LastLoginAt
- 删除采用软删除,将 Status 设为 -1
章节来源
- [internal/model/user.go](file://internal/model/user.go#L1-L26)
- [internal/model/user.go](file://internal/model/user.go#L28-L70)
- [internal/service/user_service.go](file://internal/service/user_service.go#L12-L67)
- [internal/service/user_service.go](file://internal/service/user_service.go#L111-L121)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L71-L75)
## 架构总览
用户模型在系统中的交互流程如下:
```mermaid
sequenceDiagram
participant Client as "客户端"
participant Handler as "处理器/控制器"
participant Service as "UserService"
participant Repo as "UserRepository"
participant DB as "数据库(PostgreSQL)"
participant Auth as "Password(哈希/校验)"
Client->>Handler : "注册/登录请求"
Handler->>Service : "调用业务方法"
Service->>Auth : "注册时哈希密码"
Auth-->>Service : "返回哈希值"
Service->>Repo : "创建用户/保存用户"
Repo->>DB : "执行SQL"
DB-->>Repo : "返回结果"
Repo-->>Service : "返回用户对象"
Service-->>Handler : "返回用户与令牌"
Handler-->>Client : "响应(不含敏感字段)"
```
图表来源
- [internal/service/user_service.go](file://internal/service/user_service.go#L12-L67)
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L11-L15)
- [pkg/database/postgres.go](file://pkg/database/postgres.go#L1-L74)
## 详细组件分析
### User 结构体与字段详解
- ID
- 类型:整型自增主键
- 用途:全局唯一标识
- GORM 标签:主键、自增
- Username
- 类型:字符串,长度限制,唯一索引
- 用途:登录凭据之一
- 约束:唯一性
- Password
- 类型:字符串,存储 bcrypt 哈希
- 用途:登录凭据
- 安全JSON 中不返回
- Email
- 类型:字符串,长度限制,唯一索引
- 用途:找回密码、通知等
- 约束:唯一性
- Avatar
- 类型字符串URL
- 默认:空串
- Points
- 类型:整型
- 默认0
- 用途:积分系统余额
- Role
- 类型:字符串
- 默认:"user"
- Status
- 类型:短整型
- 默认1正常
- 取值1 正常0 禁用;-1 删除(软删)
- Properties
- 类型:字符串,映射 PostgreSQL 的 JSONB
- 用途:扩展属性,如权限、配置等
- LastLoginAt
- 类型:时间戳
- 用途:记录最近登录时间
- CreatedAt/UpdatedAt
- 类型:时间戳,默认 CURRENT_TIMESTAMP
章节来源
- [internal/model/user.go](file://internal/model/user.go#L1-L26)
### User 与 UserPointLog 的关联
- 外键关系
- UserPointLog.UserID 引用 User.ID
- UserPointLog.OperatorID 可选,指向操作者用户
- 级联行为
- 代码中未显式声明级联删除/更新,遵循 GORM 默认行为
- 由于 User 采用软删除Status=-1建议在业务层避免直接删除用户防止破坏积分日志的完整性
- 日志字段
- ChangeType变更类型如 EARN、SPEND、ADMIN_ADJUST
- Amount/BalanceBefore/BalanceAfter金额与前后余额
- Reason/ReferenceType/ReferenceID原因与关联对象类型/ID
- OperatorID可选记录管理员操作者
```mermaid
classDiagram
class User {
+int64 ID
+string Username
+string Email
+string Avatar
+int Points
+string Role
+int16 Status
+string Properties
+time.Time LastLoginAt
+time.Time CreatedAt
+time.Time UpdatedAt
}
class UserPointLog {
+int64 ID
+int64 UserID
+string ChangeType
+int Amount
+int BalanceBefore
+int BalanceAfter
+string Reason
+string ReferenceType
+*int64 ReferenceID
+*int64 OperatorID
+time.Time CreatedAt
}
class UserLoginLog {
+int64 ID
+int64 UserID
+string IPAddress
+string UserAgent
+string LoginMethod
+bool IsSuccess
+string FailureReason
+time.Time CreatedAt
}
User "1" <-- "many" UserPointLog : "外键UserID"
User "1" <-- "many" UserLoginLog : "外键UserID"
User "1" <-- "many" UserPointLog : "外键OperatorID(可选)"
```
图表来源
- [internal/model/user.go](file://internal/model/user.go#L28-L70)
章节来源
- [internal/model/user.go](file://internal/model/user.go#L28-L70)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L89-L124)
### User 与 UserLoginLog 的关联
- 外键关系
- UserLoginLog.UserID 引用 User.ID
- 日志字段
- IPAddressinet 类型,记录登录 IP
- UserAgent文本
- LoginMethod默认 PASSWORD
- IsSuccess/FailureReason登录成功与否及原因
- 索引
- UserID、IPAddress、IsSuccess 建有索引CreatedAt 建有复合索引并按降序排序
章节来源
- [internal/model/user.go](file://internal/model/user.go#L52-L70)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L77-L87)
### 用户状态流转图
- 正常1可登录、可消费积分
- 禁用0禁止登录不影响积分
- 删除(-1软删除查询时默认过滤该状态
```mermaid
stateDiagram-v2
[*] --> 正常
正常 --> 禁用 : "管理员操作"
正常 --> 删除 : "软删除"
禁用 --> 正常 : "恢复"
禁用 --> 删除 : "软删除"
删除 --> 正常 : "不可恢复"
删除 --> 禁用 : "不可恢复"
```
图表来源
- [internal/model/user.go](file://internal/model/user.go#L14-L16)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L71-L75)
### 积分系统Points使用模式
- 初始化:注册时 Points 默认 0
- 更新:通过 UpdateUserPoints 在事务中完成,先读取当前余额,计算新余额,再写入并创建日志
- 校验:若新余额小于 0则回滚并返回错误
- 日志:记录变更类型、金额、前后余额、原因、关联对象与操作者
```mermaid
flowchart TD
Start(["开始"]) --> ReadUser["读取用户当前积分"]
ReadUser --> Calc["计算新余额 = 当前 + 变更金额"]
Calc --> Check{"新余额 >= 0 ?"}
Check --> |否| Rollback["回滚事务并返回错误"]
Check --> |是| Update["更新用户积分"]
Update --> Log["创建积分日志"]
Log --> End(["结束"])
Rollback --> End
```
图表来源
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L89-L124)
章节来源
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L89-L124)
- [internal/service/user_service.go](file://internal/service/user_service.go#L12-L67)
### 属性扩展Properties JSONB使用模式
- 存储Properties 为字符串,映射 PostgreSQL 的 JSONB 类型
- 用途:存放扩展字段,如权限、配置等
- 序列化:在序列化时可直接返回 Properties但需注意敏感信息脱敏
- 示例路径:序列化用户时将 Properties 原样返回,供上层处理
章节来源
- [internal/model/user.go](file://internal/model/user.go#L16-L17)
- [internal/service/serialize_service.go](file://internal/service/serialize_service.go#L86-L97)
### 数据验证规则、唯一性约束与索引优化建议
- 验证规则
- 用户名非空,长度范围(建议在服务层补充)
- 邮箱非空且包含 @ 符号(建议在服务层补充)
- 密码非空(建议在服务层补充)
- 唯一性约束
- Username、Email 唯一索引
- 索引优化建议
- UserUsername、Email 唯一索引(已具备)
- UserPointLogUserID、CreatedAt(降序)、ReferenceType/ReferenceID可选
- UserLoginLogUserID、IPAddress、IsSuccess、CreatedAt(降序)
- 状态查询
- 查询用户时默认排除 Status=-1软删除
章节来源
- [internal/model/user.go](file://internal/model/user.go#L9-L21)
- [internal/model/user.go](file://internal/model/user.go#L31-L41)
- [internal/model/user.go](file://internal/model/user.go#L55-L61)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L18-L29)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L31-L43)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L45-L57)
- [internal/service/user_service_test.go](file://internal/service/user_service_test.go#L109-L164)
- [internal/repository/user_repository_test.go](file://internal/repository/user_repository_test.go#L1-L69)
### 密码存储与敏感信息脱敏
- 密码存储
- 注册时使用 bcrypt 哈希存储
- 登录时使用 bcrypt 校验
- User.Password 在 JSON 响应中不返回
- 敏感信息脱敏
- Password 字段在 JSON 中标记为不输出
- Properties 作为 JSONB 返回,需在上层进行脱敏处理(例如移除敏感键或替换为占位符)
- 头像 URL、邮箱等字段在返回前可根据需要进行脱敏策略
章节来源
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
- [internal/service/user_service.go](file://internal/service/user_service.go#L12-L67)
- [internal/service/user_service.go](file://internal/service/user_service.go#L141-L164)
- [internal/model/user.go](file://internal/model/user.go#L10-L12)
- [internal/service/serialize_service.go](file://internal/service/serialize_service.go#L86-L97)
## 依赖分析
- 组件耦合
- UserService 依赖 UserRepository、JWT 服务与 Password 工具
- UserRepository 依赖数据库连接
- User 模型与 UserPointLog、UserLoginLog 通过外键关联
- 外键与级联
- 未显式声明级联,遵循 GORM 默认行为
- 建议在业务层避免直接物理删除用户,以保持积分与登录日志的完整性
- 外部依赖
- PostgreSQL 驱动与连接池配置
- bcrypt 密码哈希
```mermaid
graph LR
S_UserSvc["UserService"] --> R_UserRepo["UserRepository"]
S_UserSvc --> A_Password["Password"]
R_UserRepo --> D_Postgres["PostgreSQL"]
M_User["User"] --> M_PointLog["UserPointLog"]
M_User --> M_LoginLog["UserLoginLog"]
```
图表来源
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
- [pkg/database/postgres.go](file://pkg/database/postgres.go#L1-L74)
- [internal/model/user.go](file://internal/model/user.go#L1-L70)
章节来源
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
- [pkg/database/postgres.go](file://pkg/database/postgres.go#L1-L74)
- [internal/model/user.go](file://internal/model/user.go#L1-L70)
## 性能考虑
- 索引设计
- UserUsername、Email 唯一索引
- UserPointLogUserID、CreatedAt(降序)、ReferenceType/ReferenceID可选
- UserLoginLogUserID、IPAddress、IsSuccess、CreatedAt(降序)
- 查询过滤
- 查询用户时默认排除软删除Status!=-1减少扫描
- 事务与并发
- 积分更新使用事务,保证一致性
- 缓存与限流
- 登录失败与成功日志可配合缓存与限流策略,降低暴力破解风险
[本节为通用指导,无需列出具体文件来源]
## 故障排查指南
- 登录失败
- 检查用户是否存在与状态是否为 1
- 校验密码哈希是否正确
- 记录失败日志以便审计
- 积分不足
- 确认余额计算逻辑与事务一致性
- 检查日志是否正确记录
- 软删除影响
- 查询用户时默认排除 Status=-1
- 若出现“用户不存在”,确认是否被软删除
章节来源
- [internal/service/user_service.go](file://internal/service/user_service.go#L70-L121)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L71-L75)
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L89-L124)
- [internal/repository/user_repository_test.go](file://internal/repository/user_repository_test.go#L1-L69)
## 结论
User 模型通过明确的字段定义、GORM 标签与业务约束,支撑了注册、登录、积分与日志等核心功能。通过软删除与事务化的积分更新,确保了数据一致性与可审计性。建议在服务层补充输入验证与脱敏策略,并根据业务增长持续优化索引与查询路径。
[本节为总结性内容,无需列出具体文件来源]
## 附录
- 相关模型与配置
- Profile 模型与 User 的关联
- SystemConfig 模型(系统配置)
- 序列化与脱敏
- SerializeUser 返回 Properties需在上层进行脱敏处理
章节来源
- [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/service/serialize_service.go](file://internal/service/serialize_service.go#L86-L97)

View File

@@ -0,0 +1,369 @@
# 系统配置模型
<cite>
**本文引用的文件**
- [internal/model/system_config.go](file://internal/model/system_config.go)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go)
- [internal/repository/system_config_repository_test.go](file://internal/repository/system_config_repository_test.go)
- [internal/types/common.go](file://internal/types/common.go)
- [pkg/config/config.go](file://pkg/config/config.go)
- [pkg/config/manager.go](file://pkg/config/manager.go)
- [scripts/check-env.sh](file://scripts/check-env.sh)
</cite>
## 目录
1. [引言](#引言)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 引言
本文件围绕系统配置模型进行系统性说明,重点聚焦于 SystemConfig 结构体的设计与应用,涵盖:
- 键值配置存储机制与配置类型ConfigTypeSTRING/INTEGER/BOOLEAN/JSON的枚举定义与值序列化策略
- IsPublic 标志位如何控制前端可访问的配置项(站点名称、注册开关、维护模式等)
- SystemConfigPublicResponse 响应结构的字段映射逻辑与敏感配置与公开配置的分离原则
- 关于配置项版本管理、变更审计与缓存策略的建议
- 如何安全地读取与更新系统参数,避免配置注入风险
## 项目结构
系统配置模型位于内部模型层与仓库层,配合通用类型与配置加载模块共同构成完整的配置体系。下图展示了与系统配置模型直接相关的文件与职责划分。
```mermaid
graph TB
subgraph "模型层"
M1["internal/model/system_config.go<br/>定义 SystemConfig 与 SystemConfigPublicResponse"]
end
subgraph "仓库层"
R1["internal/repository/system_config_repository.go<br/>提供配置查询与更新接口"]
R2["internal/repository/system_config_repository_test.go<br/>测试查询条件、公开配置逻辑与更新逻辑"]
end
subgraph "类型与响应"
T1["internal/types/common.go<br/>定义 SystemConfigResponse 等通用响应类型"]
end
subgraph "配置加载"
C1["pkg/config/config.go<br/>应用运行时配置加载与环境变量覆盖"]
C2["pkg/config/manager.go<br/>全局配置实例管理"]
S1["scripts/check-env.sh<br/>环境变量检查脚本"]
end
M1 --> R1
R1 --> C1
T1 -.-> M1
C1 --> C2
S1 --> C1
```
图表来源
- [internal/model/system_config.go](file://internal/model/system_config.go#L1-L42)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)
- [internal/repository/system_config_repository_test.go](file://internal/repository/system_config_repository_test.go#L1-L145)
- [internal/types/common.go](file://internal/types/common.go#L208-L215)
- [pkg/config/config.go](file://pkg/config/config.go#L108-L133)
- [pkg/config/manager.go](file://pkg/config/manager.go#L1-L63)
- [scripts/check-env.sh](file://scripts/check-env.sh#L1-L61)
章节来源
- [internal/model/system_config.go](file://internal/model/system_config.go#L1-L42)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)
- [internal/types/common.go](file://internal/types/common.go#L208-L215)
- [pkg/config/config.go](file://pkg/config/config.go#L108-L133)
- [pkg/config/manager.go](file://pkg/config/manager.go#L1-L63)
- [scripts/check-env.sh](file://scripts/check-env.sh#L1-L61)
## 核心组件
- SystemConfig系统配置的持久化模型包含键、值、类型、描述、是否公开、创建与更新时间等字段通过 GORM 映射到 system_config 表。
- ConfigType配置类型枚举支持 STRING、INTEGER、BOOLEAN、JSON 四种类型。
- IsPublic布尔标志位决定该配置是否可被前端获取。
- SystemConfigPublicResponse面向前端的公开配置响应结构包含站点名称、站点描述、注册开关、维护模式、公告等字段。
- SystemConfigRepository提供按键查询、获取公开配置、获取全部配置、更新配置与更新配置值等操作。
- SystemConfigResponse通用系统配置响应类型与公开响应不同包含更多业务参数
章节来源
- [internal/model/system_config.go](file://internal/model/system_config.go#L7-L41)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L11-L57)
- [internal/types/common.go](file://internal/types/common.go#L208-L215)
## 架构总览
系统配置模型遵循“模型-仓库-服务-控制器”的分层设计。配置读写通过仓库层封装数据库访问,对外暴露简洁的接口;公开配置通过 IsPublic 进行过滤,确保敏感信息不泄露给前端。
```mermaid
graph TB
Client["前端/管理端"] --> API["控制器/服务层"]
API --> Repo["SystemConfigRepository"]
Repo --> Model["SystemConfig 模型"]
Repo --> DB["数据库 system_config 表"]
API --> PublicResp["SystemConfigPublicResponse"]
API --> CommonResp["SystemConfigResponse"]
```
图表来源
- [internal/model/system_config.go](file://internal/model/system_config.go#L18-L41)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L11-L57)
- [internal/types/common.go](file://internal/types/common.go#L208-L215)
## 详细组件分析
### SystemConfig 结构体与配置类型
- 字段说明
- Key配置键唯一索引用于标识具体配置项。
- Value配置值以字符串形式存储配合 Type 决定解析方式。
- Type配置类型枚举 STRING/INTEGER/BOOLEAN/JSON。
- IsPublic是否公开true 时可在公开接口中返回给前端。
- Description、CreatedAt、UpdatedAt元信息与时间戳。
- 表名映射TableName 返回 system_config用于 GORM 自动迁移与查询。
- 配置类型与序列化策略
- STRING直接以字符串存储适合文本类配置。
- INTEGER字符串存储需要在读取后转换为整数。
- BOOLEAN字符串存储需要在读取后转换为布尔值。
- JSON字符串存储需要在读取后进行 JSON 解析,支持复杂结构。
```mermaid
classDiagram
class SystemConfig {
+int64 id
+string key
+string value
+string description
+ConfigType type
+bool is_public
+time created_at
+time updated_at
+TableName() string
}
class ConfigType {
<<enumeration>>
"STRING"
"INTEGER"
"BOOLEAN"
"JSON"
}
SystemConfig --> ConfigType : "使用"
```
图表来源
- [internal/model/system_config.go](file://internal/model/system_config.go#L7-L32)
章节来源
- [internal/model/system_config.go](file://internal/model/system_config.go#L7-L32)
### 公开配置响应 SystemConfigPublicResponse 的字段映射
- 字段含义
- site_name站点名称
- site_description站点描述
- registration_enabled注册开关
- maintenance_mode维护模式
- announcement公告
- 映射原则
- 仅来源于 IsPublic 为 true 的配置项,避免敏感配置泄露。
- 字段名与 JSON 序列化标签一一对应,便于前端消费。
- 分离原则
- 敏感配置(如密钥、内部开关)应设置 IsPublic=false不在公开响应中出现。
- 公开配置(如站点名称、注册开关、维护模式、公告)应设置 IsPublic=true。
```mermaid
classDiagram
class SystemConfigPublicResponse {
+string site_name
+string site_description
+bool registration_enabled
+bool maintenance_mode
+string announcement
}
```
图表来源
- [internal/model/system_config.go](file://internal/model/system_config.go#L34-L41)
章节来源
- [internal/model/system_config.go](file://internal/model/system_config.go#L34-L41)
### 仓库层接口与流程
- GetSystemConfigByKey(key):根据键查询配置,若记录不存在返回空而非错误。
- GetPublicSystemConfigs():仅返回 IsPublic=true 的配置集合。
- GetAllSystemConfigs():返回全部配置(供管理员使用)。
- UpdateSystemConfig(config):保存配置(含类型与值)。
- UpdateSystemConfigValue(key, value):仅更新值字段。
```mermaid
sequenceDiagram
participant Client as "调用方"
participant Repo as "SystemConfigRepository"
participant DB as "数据库"
Client->>Repo : GetPublicSystemConfigs()
Repo->>DB : 查询 where is_public = true
DB-->>Repo : 返回公开配置列表
Repo-->>Client : 返回公开配置
Client->>Repo : UpdateSystemConfigValue(key, value)
Repo->>DB : Model(...).Where("key = ?").Update("value", value)
DB-->>Repo : 更新成功
Repo-->>Client : 返回 nil
```
图表来源
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L25-L57)
章节来源
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L11-L57)
### 配置读取与序列化策略
- 读取流程
- 使用 GetSystemConfigByKey(key) 获取配置。
- 根据 Type 对 Value 进行类型化解析:
- STRING直接使用字符串。
- INTEGER解析为整数。
- BOOLEAN解析为布尔值。
- JSON解析为结构体或 map。
- 序列化策略
- 公开响应使用 SystemConfigPublicResponse仅包含 IsPublic=true 的字段映射。
- 非公开配置不参与公开响应序列化。
```mermaid
flowchart TD
Start(["开始"]) --> Load["GetSystemConfigByKey(key)"]
Load --> Found{"找到记录?"}
Found --> |否| ReturnNil["返回空配置"]
Found --> |是| ParseType["根据 Type 解析 Value"]
ParseType --> STRING["STRING直接使用字符串"]
ParseType --> INTEGER["INTEGER解析为整数"]
ParseType --> BOOLEAN["BOOLEAN解析为布尔值"]
ParseType --> JSON["JSON解析为结构体/字典"]
STRING --> End(["结束"])
INTEGER --> End
BOOLEAN --> End
JSON --> End
ReturnNil --> End
```
图表来源
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L11-L23)
- [internal/model/system_config.go](file://internal/model/system_config.go#L7-L15)
章节来源
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L11-L23)
- [internal/model/system_config.go](file://internal/model/system_config.go#L7-L15)
### 公开配置逻辑与测试验证
- 公开配置查询逻辑
- GetPublicSystemConfigs() 仅返回 IsPublic=true 的配置,确保敏感配置不被泄露。
- 测试覆盖点
- 查询条件验证:键非空才视为有效。
- 公开配置逻辑:仅包含 IsPublic=true 的配置。
- 更新值逻辑:键非空即允许更新,空值亦可接受。
- 错误处理:记录不存在时返回空而非错误。
```mermaid
flowchart TD
QStart(["查询入口"]) --> BuildQuery["构建查询where is_public = true"]
BuildQuery --> Exec["执行查询"]
Exec --> Result{"查询结果"}
Result --> |存在| Filter["过滤 IsPublic=true"]
Result --> |不存在| NotFound["返回空集合"]
Filter --> Return["返回公开配置集合"]
NotFound --> Return
```
图表来源
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L25-L33)
- [internal/repository/system_config_repository_test.go](file://internal/repository/system_config_repository_test.go#L51-L78)
章节来源
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L25-L33)
- [internal/repository/system_config_repository_test.go](file://internal/repository/system_config_repository_test.go#L1-L145)
### 安全与注入防护
- 输入校验
- 键非空校验:更新与查询均要求键非空,防止空键导致的异常行为。
- 值可为空:允许空值,但需结合业务语义判断有效性。
- 类型约束
- 通过 Type 字段强制约束 Value 的解析方式,避免任意字符串被错误解读。
- 敏感信息隔离
- 通过 IsPublic=false 隐藏敏感配置,仅在管理员通道可见。
- 环境变量安全
- 应用配置加载来自环境变量,建议使用强口令与密钥,脚本会提示 JWT 密钥长度不足的风险。
章节来源
- [internal/repository/system_config_repository_test.go](file://internal/repository/system_config_repository_test.go#L80-L116)
- [scripts/check-env.sh](file://scripts/check-env.sh#L52-L61)
- [pkg/config/config.go](file://pkg/config/config.go#L238-L305)
## 依赖关系分析
- 模型依赖
- SystemConfig 依赖 GORM 标签进行数据库映射。
- SystemConfigPublicResponse 依赖 JSON 标签进行序列化。
- 仓库依赖
- 依赖数据库连接MustGetDB通过 where 条件实现键查询与公开筛选。
- 类型依赖
- SystemConfigResponse 与 SystemConfigPublicResponse 分别服务于不同场景的响应结构。
- 配置加载依赖
- 应用配置加载与环境变量覆盖,确保运行时参数可控且可审计。
```mermaid
graph LR
Model["SystemConfig 模型"] --> Repo["SystemConfigRepository"]
Repo --> DB["数据库"]
PublicResp["SystemConfigPublicResponse"] --> API["控制器/服务层"]
CommonResp["SystemConfigResponse"] --> API
Cfg["pkg/config/config.go"] --> API
CfgMgr["pkg/config/manager.go"] --> Cfg
```
图表来源
- [internal/model/system_config.go](file://internal/model/system_config.go#L18-L41)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L11-L57)
- [internal/types/common.go](file://internal/types/common.go#L208-L215)
- [pkg/config/config.go](file://pkg/config/config.go#L108-L133)
- [pkg/config/manager.go](file://pkg/config/manager.go#L1-L63)
章节来源
- [internal/model/system_config.go](file://internal/model/system_config.go#L18-L41)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L11-L57)
- [internal/types/common.go](file://internal/types/common.go#L208-L215)
- [pkg/config/config.go](file://pkg/config/config.go#L108-L133)
- [pkg/config/manager.go](file://pkg/config/manager.go#L1-L63)
## 性能考量
- 查询优化
- 为 key 建立唯一索引,提升按键查询效率。
- 为 is_public 建立索引,加速公开配置筛选。
- 缓存策略建议
- 公开配置可引入只读缓存(如 Redis定期刷新降低数据库压力。
- 对频繁读取的配置项(如站点名称、注册开关、维护模式)设置短 TTL平衡一致性与性能。
- 更新策略
- 批量更新时使用 UpdateSystemConfigValue避免不必要的字段变更导致的冗余日志与锁竞争。
- 版本与审计
- 建议引入配置版本号字段与审计日志,记录每次变更的时间、操作者与变更内容,便于回溯与合规。
## 故障排查指南
- 记录不存在
- GetSystemConfigByKey(key) 在记录不存在时返回空而非错误,调用方需显式判空。
- 公开配置为空
- 若 GetPublicSystemConfigs() 返回空,检查是否正确设置 IsPublic=true 或数据库中是否存在公开配置。
- 更新失败
- UpdateSystemConfigValue(key, value) 要求键非空;若更新无效,确认键是否存在且值合法。
- 类型解析错误
- 根据 Type 对 Value 进行解析;若解析失败,检查配置值格式与类型是否匹配。
- 环境变量问题
- 使用脚本检查关键环境变量是否缺失或过短(如 JWT 密钥长度),及时修正。
章节来源
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L11-L23)
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L25-L57)
- [internal/repository/system_config_repository_test.go](file://internal/repository/system_config_repository_test.go#L119-L145)
- [scripts/check-env.sh](file://scripts/check-env.sh#L1-L61)
## 结论
SystemConfig 模型通过统一的键值存储与类型约束,实现了灵活而安全的系统配置管理。借助 IsPublic 标志位与公开响应结构,系统在保证功能可用的同时,严格隔离了敏感配置。结合缓存、版本与审计策略,可进一步提升系统的稳定性与可运维性。建议在生产环境中严格执行输入校验、类型解析与注入防护,并建立完善的变更审计流程。
## 附录
- 常用配置键建议
- 站点名称site_nameIsPublic=true
- 站点描述site_descriptionIsPublic=true
- 注册开关registration_enabledIsPublic=true
- 维护模式maintenance_modeIsPublic=true
- 公告announcementIsPublic=true
- 管理员专用secret_key、internal_switchIsPublic=false