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

473 lines
19 KiB
Markdown
Raw Normal View History

# 档案模型
<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)