chore(git): 更新.gitignore以忽略新的本地文件
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
This commit is contained in:
546
.qoder/repowiki/zh/content/数据模型/数据模型.md
Normal file
546
.qoder/repowiki/zh/content/数据模型/数据模型.md
Normal 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 删除(软删除)
|
||||
- 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["构建基础查询<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.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)
|
||||
408
.qoder/repowiki/zh/content/数据模型/材质模型.md
Normal file
408
.qoder/repowiki/zh/content/数据模型/材质模型.md
Normal 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:文件大小(字节)
|
||||
- IsSlim:Slim 模型标识(Alex 细臂为 true,Steve 粗臂为 false)
|
||||
- 公开性与状态
|
||||
- IsPublic:是否公开到皮肤广场
|
||||
- Status:状态机(1 正常、0 审核中、-1 已删除)
|
||||
- 计数器与排序
|
||||
- DownloadCount、FavoriteCount:下载与收藏计数,分别建立降序索引以便高效排序与统计
|
||||
- 时间戳与索引
|
||||
- CreatedAt/UpdatedAt:自动维护
|
||||
- UploaderID、IsPublic+Type+Status、DownloadCount/FavoriteCount 等索引支撑查询与排序
|
||||
|
||||
章节来源
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L16-L35)
|
||||
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L39-L85)
|
||||
|
||||
### TextureType 与 Slim 模型
|
||||
- TextureType 枚举在模型与类型定义中保持一致,确保序列化与校验的一致性
|
||||
- Slim 模型标识用于区分 Alex 与 Steve 模型,便于客户端渲染与兼容
|
||||
|
||||
章节来源
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L7-L13)
|
||||
- [internal/types/common.go](file://internal/types/common.go#L127-L152)
|
||||
|
||||
### 状态机与公开性控制
|
||||
- 状态机
|
||||
- 1:正常可用
|
||||
- 0:审核中(可视为待审状态)
|
||||
- -1:已删除(软删除)
|
||||
- 公开性控制
|
||||
- IsPublic 控制是否对外可见
|
||||
- 查询侧影响
|
||||
- 搜索接口默认仅返回状态为正常的材质
|
||||
- 用户上传列表过滤掉已删除材质
|
||||
|
||||
章节来源
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L28-L31)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L93-L103)
|
||||
|
||||
### 与用户的关联关系
|
||||
- 上传者关联
|
||||
- Texture 与 User 通过 UploaderID 外键关联,查询时可预加载上传者信息
|
||||
- 角色档案关联
|
||||
- Profiles 表通过 skin_id/cape_id 引用 textures,形成角色与材质的绑定关系
|
||||
|
||||
章节来源
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L33-L35)
|
||||
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L111-L127)
|
||||
|
||||
### 收藏系统(UserTextureFavorite)
|
||||
- 关系模型
|
||||
- 用户与材质的多对多中间表,字段包含 user_id、texture_id、created_at
|
||||
- 联合唯一索引 uk_user_texture,保证同一用户对同一材质只能收藏一次
|
||||
- 业务流程
|
||||
- 切换收藏:先检查是否已收藏,再执行新增或删除,并同步更新材质的收藏计数
|
||||
- 收藏列表:子查询获取用户收藏的材质ID,再按创建时间倒序分页查询
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as "客户端"
|
||||
participant Service as "TextureService"
|
||||
participant Repo as "TextureRepository"
|
||||
participant DB as "数据库"
|
||||
Client->>Service : "ToggleTextureFavorite(userID, textureID)"
|
||||
Service->>Repo : "FindTextureByID(textureID)"
|
||||
Repo->>DB : "SELECT * FROM textures WHERE id=?"
|
||||
DB-->>Repo : "Texture"
|
||||
Repo-->>Service : "Texture"
|
||||
Service->>Repo : "IsTextureFavorited(userID, textureID)"
|
||||
Repo->>DB : "SELECT COUNT(*) FROM user_texture_favorites WHERE user_id=? AND texture_id=?"
|
||||
DB-->>Repo : "count"
|
||||
Repo-->>Service : "bool"
|
||||
alt 已收藏
|
||||
Service->>Repo : "RemoveTextureFavorite(userID, textureID)"
|
||||
Repo->>DB : "DELETE FROM user_texture_favorites WHERE user_id=? AND texture_id=?"
|
||||
Service->>Repo : "DecrementTextureFavoriteCount(textureID)"
|
||||
Repo->>DB : "UPDATE textures SET favorite_count=favorite_count-1 WHERE id=?"
|
||||
else 未收藏
|
||||
Service->>Repo : "AddTextureFavorite(userID, textureID)"
|
||||
Repo->>DB : "INSERT INTO user_texture_favorites(user_id, texture_id)"
|
||||
Service->>Repo : "IncrementTextureFavoriteCount(textureID)"
|
||||
Repo->>DB : "UPDATE textures SET favorite_count=favorite_count+1 WHERE id=?"
|
||||
end
|
||||
Service-->>Client : "返回收藏状态"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L189-L225)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L159-L201)
|
||||
|
||||
章节来源
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L42-L57)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L159-L201)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L189-L225)
|
||||
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L87-L110)
|
||||
|
||||
### 下载日志(TextureDownloadLog)
|
||||
- 记录每次下载的材质、用户、IP、UA、时间等信息
|
||||
- 下载计数器采用 GORM 表达式更新,避免并发写冲突导致的计数不准
|
||||
- 提供按时间倒序索引,便于统计与审计
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as "客户端"
|
||||
participant Service as "TextureService"
|
||||
participant Repo as "TextureRepository"
|
||||
participant DB as "数据库"
|
||||
Client->>Service : "RecordTextureDownload(textureID, userID, ip, ua)"
|
||||
Service->>Repo : "FindTextureByID(textureID)"
|
||||
Repo->>DB : "SELECT * FROM textures WHERE id=?"
|
||||
DB-->>Repo : "Texture"
|
||||
Service->>Repo : "IncrementTextureDownloadCount(textureID)"
|
||||
Repo->>DB : "UPDATE textures SET download_count=download_count+1 WHERE id=?"
|
||||
Service->>Repo : "CreateTextureDownloadLog(log)"
|
||||
Repo->>DB : "INSERT INTO texture_download_logs(texture_id, user_id, ip_address, user_agent)"
|
||||
Service-->>Client : "成功"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L162-L187)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L157)
|
||||
|
||||
章节来源
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L59-L76)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L157)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L162-L187)
|
||||
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L271-L291)
|
||||
|
||||
### GORM 使用示例与复杂查询模式
|
||||
- 创建材质
|
||||
- 参数校验与去重:先检查用户存在与哈希唯一性,再创建材质
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L12-L64)
|
||||
- 搜索材质
|
||||
- 过滤条件:状态=正常、公开筛选、类型筛选、关键词模糊匹配
|
||||
- 分页与总数:先 Count 再分页查询,支持预加载上传者
|
||||
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
|
||||
- 更新材质
|
||||
- 权限校验:仅上传者可更新
|
||||
- 动态字段更新:根据传入字段选择性更新
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L105-L141)
|
||||
- 删除材质(软删除)
|
||||
- 仅将状态置为 -1,保留历史数据与日志
|
||||
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L126-L131)
|
||||
- 收藏切换
|
||||
- 原子性:先判断是否已收藏,再执行新增/删除并更新计数
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L189-L225)
|
||||
- 收藏列表
|
||||
- 子查询:先查出用户收藏的材质ID集合,再查询材质并分页
|
||||
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L190-L221)
|
||||
- 上传限制检查
|
||||
- 统计用户已上传材质数量,与系统配置的最大值比较
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L239-L252)
|
||||
|
||||
章节来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L12-L64)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L105-L141)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L126-L131)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L189-L225)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L190-L221)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L239-L252)
|
||||
|
||||
## 依赖分析
|
||||
- 模型层依赖
|
||||
- Texture 依赖 TextureType、User
|
||||
- UserTextureFavorite 依赖 User、Texture
|
||||
- TextureDownloadLog 依赖 User、Texture
|
||||
- 仓储层依赖
|
||||
- 通过数据库连接池访问 textures、user_texture_favorites、texture_download_logs
|
||||
- 使用 GORM 的表达式更新计数器,避免并发竞争
|
||||
- 服务层依赖
|
||||
- 负责业务规则编排:权限校验、去重、状态机、计数器更新、日志记录
|
||||
- 数据库层
|
||||
- 外键约束:textures.uploader_id -> user.id;favorites 与 logs 对 textures/user 的引用
|
||||
- 索引:textures 上的多列索引与计数器降序索引;favorites/logs 上的单列索引
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Service["TextureService"] --> Repo["TextureRepository"]
|
||||
Repo --> Model["Model: Texture/User/Favorite/DownloadLog"]
|
||||
Repo --> DB["PostgreSQL 表: textures/favorites/logs"]
|
||||
Model --> DB
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L39-L110)
|
||||
|
||||
章节来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L39-L110)
|
||||
|
||||
## 性能考虑
|
||||
- 并发计数器更新
|
||||
- 下载计数与收藏计数均使用 GORM 表达式更新,避免读取-计算-写回的竞态,降低锁竞争
|
||||
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
|
||||
- 索引设计
|
||||
- textures 表的多列索引(is_public, type, status)与计数器降序索引(download_count/favorite_count)支撑高频查询与排序
|
||||
- favorites/logs 表的关键列建立索引,提升收藏去重与日志检索效率
|
||||
- 参考路径:[scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L63-L85)
|
||||
- 分页与总数
|
||||
- 搜索与收藏列表先 Count 再分页查询,避免一次性加载大量数据
|
||||
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
|
||||
- 预加载
|
||||
- 查询时预加载上传者信息,减少 N+1 查询风险
|
||||
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L18-L21)
|
||||
|
||||
章节来源
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
|
||||
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L63-L85)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
|
||||
|
||||
## 故障排查指南
|
||||
- 材质不存在或已被删除
|
||||
- 查询时若返回空或状态为已删除,服务层会抛出明确错误
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L66-L79)
|
||||
- 权限不足
|
||||
- 更新/删除材质需校验上传者身份,否则返回权限错误
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L105-L120)
|
||||
- 哈希重复
|
||||
- 创建材质前检查哈希唯一性,重复则拒绝创建
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L23-L31)
|
||||
- 收藏去重失败
|
||||
- 联合唯一索引 uk_user_texture 保证同一用户对同一材质仅一条收藏记录
|
||||
- 若出现重复插入,需检查业务层是否正确先查询后插入
|
||||
- 参考路径:[internal/model/texture.go](file://internal/model/texture.go#L42-L57),[scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L87-L110)
|
||||
- 下载计数不准确
|
||||
- 确认使用表达式更新而非普通更新,避免并发写入导致计数偏差
|
||||
- 参考路径:[internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
|
||||
|
||||
章节来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L66-L79)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L105-L120)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L23-L31)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L42-L57)
|
||||
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L87-L110)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
|
||||
|
||||
## 结论
|
||||
材质模型通过清晰的数据结构、严格的外键与索引设计、以及基于 GORM 的并发安全更新,实现了高性能、可扩展且易维护的材质管理能力。状态机与公开性控制使内容治理更加灵活,收藏与下载日志完善了用户行为追踪与运营分析基础。建议在后续迭代中持续关注索引命中率与查询计划,配合缓存与异步任务进一步优化热点查询与批量操作。
|
||||
|
||||
## 附录
|
||||
- 字段与索引对照
|
||||
- textures:uploader_id、is_public+type+status、download_count、favorite_count、hash 唯一索引
|
||||
- user_texture_favorites:user_id、texture_id、created_at、uk_user_texture
|
||||
- texture_download_logs:texture_id、user_id、ip_address、created_at
|
||||
- 关键查询模式
|
||||
- 搜索与分页:先 Count 再分页,支持关键词、类型、公开性筛选
|
||||
- 收藏列表:子查询 + 分页 + 预加载
|
||||
- 并发计数:表达式更新,避免竞态
|
||||
|
||||
章节来源
|
||||
- [scripts/carrotskin_postgres.sql](file://scripts/carrotskin_postgres.sql#L63-L110)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L71-L112)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L190-L221)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
|
||||
473
.qoder/repowiki/zh/content/数据模型/档案模型.md
Normal file
473
.qoder/repowiki/zh/content/数据模型/档案模型.md
Normal 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)
|
||||
|
||||
## 核心组件
|
||||
- Profile:Minecraft档案实体,包含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表的外键,允许为空
|
||||
- RSAPrivateKey:RSA私钥(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)
|
||||
451
.qoder/repowiki/zh/content/数据模型/用户模型.md
Normal file
451
.qoder/repowiki/zh/content/数据模型/用户模型.md
Normal 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
|
||||
- 日志字段
|
||||
- IPAddress:inet 类型,记录登录 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 唯一索引
|
||||
- 索引优化建议
|
||||
- User:Username、Email 唯一索引(已具备)
|
||||
- UserPointLog:UserID、CreatedAt(降序)、ReferenceType/ReferenceID(可选)
|
||||
- UserLoginLog:UserID、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)
|
||||
|
||||
## 性能考虑
|
||||
- 索引设计
|
||||
- User:Username、Email 唯一索引
|
||||
- UserPointLog:UserID、CreatedAt(降序)、ReferenceType/ReferenceID(可选)
|
||||
- UserLoginLog:UserID、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)
|
||||
369
.qoder/repowiki/zh/content/数据模型/系统配置模型.md
Normal file
369
.qoder/repowiki/zh/content/数据模型/系统配置模型.md
Normal 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 结构体的设计与应用,涵盖:
|
||||
- 键值配置存储机制与配置类型(ConfigType:STRING/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_name(IsPublic=true)
|
||||
- 站点描述:site_description(IsPublic=true)
|
||||
- 注册开关:registration_enabled(IsPublic=true)
|
||||
- 维护模式:maintenance_mode(IsPublic=true)
|
||||
- 公告:announcement(IsPublic=true)
|
||||
- 管理员专用:secret_key、internal_switch(IsPublic=false)
|
||||
Reference in New Issue
Block a user