# 档案模型 **本文引用的文件** - [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) ## 目录 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
profiles 表"] M_Texture["Texture
textures 表"] M_User["User
user 表"] M_Ygg["Yggdrasil
Yggdrasil 表"] end subgraph "服务层" S_Profile["ProfileService
档案业务逻辑"] S_Ygg["YggdrasilService
Yggdrasil协议集成"] end subgraph "仓储层" R_Profile["ProfileRepository
档案数据访问"] 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)