15 KiB
档案服务
**本文引用的文件** - [profile_service.go](file://internal/service/profile_service.go) - [profile_repository.go](file://internal/repository/profile_repository.go) - [profile.go](file://internal/model/profile.go) - [user.go](file://internal/model/user.go) - [texture.go](file://internal/model/texture.go) - [profile_handler.go](file://internal/handler/profile_handler.go) - [common.go](file://internal/types/common.go) - [profile_service_test.go](file://internal/service/profile_service_test.go) - [profile_handler_test.go](file://internal/handler/profile_handler_test.go)目录
简介
本档“档案服务”文档聚焦于 ProfileService 的职责、方法与内部逻辑,覆盖档案的创建、更新、删除与活跃状态管理;明确档案名称与用户ID的验证规则(名称非空且长度1-16,用户ID大于0),以及状态有效性判断(仅当状态为1时表示正常可用);阐述新创建档案默认为活跃状态的业务规则,并说明多档案用户的活跃档案切换逻辑;解释档案与用户、材质之间的关联关系;最后提供常见错误场景的排查指南,帮助开发者快速定位问题。
项目结构
档案服务位于 internal/service 层,通过 internal/handler 接收HTTP请求,调用 ProfileService 完成业务逻辑,再由 ProfileService 调用 internal/repository 访问数据库;数据模型定义在 internal/model 中,类型定义在 internal/types 中。
graph TB
subgraph "接口层"
H["profile_handler.go<br/>HTTP处理器"]
end
subgraph "服务层"
S["profile_service.go<br/>ProfileService"]
end
subgraph "仓储层"
R["profile_repository.go<br/>Profile仓储"]
end
subgraph "模型层"
M1["profile.go<br/>Profile模型"]
M2["user.go<br/>User模型"]
M3["texture.go<br/>Texture模型"]
end
subgraph "类型定义"
T["common.go<br/>CreateProfileRequest/UpdateProfileRequest/ProfileInfo"]
end
H --> S
S --> R
R --> M1
R --> M2
R --> M3
H --> T
图表来源
章节来源
核心组件
- ProfileService:负责档案的创建、查询、更新、删除、活跃状态设置与数量限制检查等核心业务逻辑。
- ProfileRepository:封装数据库访问,包括创建、查询、更新、删除、统计、设置活跃状态等。
- Profile 模型:定义档案的数据结构及与用户、材质的关联。
- Handler:接收HTTP请求,解析参数,调用服务层并返回统一响应。
- 类型定义:CreateProfileRequest、UpdateProfileRequest、ProfileInfo 等用于API契约与响应结构。
章节来源
架构总览
档案服务采用典型的三层架构:Handler 负责接口与参数校验,Service 负责业务规则与流程编排,Repository 负责数据持久化。ProfileService 在创建档案时会进行用户存在性与状态校验、角色名唯一性校验、生成UUID与RSA密钥、创建档案并将其设置为活跃状态;在设置活跃状态时,通过事务将该用户下的其他档案全部置为非活跃,确保同一时刻仅有一个活跃档案。
sequenceDiagram
participant C as "客户端"
participant H as "Handler"
participant S as "ProfileService"
participant R as "ProfileRepository"
participant DB as "数据库"
C->>H : "POST /api/v1/profile"
H->>H : "解析请求体(CreateProfileRequest)"
H->>S : "CheckProfileLimit(userID, maxProfiles)"
S->>R : "CountProfilesByUserID(userID)"
R-->>S : "count"
S-->>H : "通过/错误"
H->>S : "CreateProfile(userID, name)"
S->>R : "FindUserByID(userID)"
R-->>S : "User"
S->>R : "FindProfileByName(name)"
R-->>S : "Profile 或 NotFound"
S->>S : "生成UUID与RSA私钥"
S->>R : "CreateProfile(Profile)"
R->>DB : "INSERT"
DB-->>R : "OK"
S->>R : "SetActiveProfile(uuid, userID)"
R->>DB : "事务 : 先将用户其他档案置为非活跃,再将目标置为活跃"
DB-->>R : "OK"
R-->>S : "OK"
S-->>H : "Profile"
H-->>C : "200 成功响应"
图表来源
详细组件分析
ProfileService 方法与职责
- 创建档案
- 输入:用户ID、档案名称
- 校验:用户存在且状态为1;档案名称唯一;名称长度1-16
- 生成:UUID、RSA私钥(PEM格式)
- 写入:创建档案记录,默认 IsActive=true
- 并发安全:通过事务将该用户下其他档案置为非活跃,确保仅一个活跃档案
- 获取档案详情
- 输入:档案UUID
- 输出:Profile(预加载Skin与Cape)
- 获取用户档案列表
- 输入:用户ID
- 输出:Profile 列表(按创建时间倒序)
- 更新档案
- 输入:UUID、用户ID、可选名称、可选SkinID、可选CapeID
- 校验:权限(档案属于当前用户)、名称唯一性(若更改)
- 更新:保存变更并重新加载以返回最新数据
- 删除档案
- 输入:UUID、用户ID
- 校验:权限
- 删除:物理删除档案
- 设置活跃档案
- 输入:UUID、用户ID
- 校验:权限
- 事务:将该用户下其他档案置为非活跃,再将目标置为活跃
- 同步:更新最后使用时间
- 档案数量限制
- 输入:用户ID、最大数量
- 校验:统计当前档案数是否达到上限
- 校验档案归属
- 输入:用户ID、档案UUID
- 校验:UUID存在且属于该用户
- 名称批量查询
- 输入:名称数组
- 输出:Profile 列表
- 密钥对查询
- 输入:档案ID
- 输出:KeyPair(私钥、公钥、过期时间等)
章节来源
数据模型与关联关系
- Profile
- 字段:UUID、UserID、Name、SkinID、CapeID、RSAPrivateKey、IsActive、LastUsedAt、CreatedAt、UpdatedAt
- 关联:User(外键 UserID)、Texture(SkinID、CapeID)
- User
- 字段:ID、Username、Email、Role、Status(1: 正常, 0: 禁用, -1: 删除)
- Texture
- 字段:ID、UploaderID、Name、Type、URL、Hash、IsPublic、DownloadCount、FavoriteCount、IsSlim、Status(1: 正常, 0: 审核中, -1: 已删除)
erDiagram
USER {
bigint id PK
varchar username UK
varchar email UK
smallint status
}
PROFILE {
varchar uuid PK
bigint user_id FK
varchar name UK
bigint skin_id
bigint cape_id
boolean is_active
timestamp last_used_at
timestamp created_at
timestamp updated_at
}
TEXTURE {
bigint id PK
bigint uploader_id FK
varchar name
varchar type
varchar url
varchar hash UK
boolean is_public
integer download_count
integer favorite_count
boolean is_slim
smallint status
}
USER ||--o{ PROFILE : "拥有"
TEXTURE ||--o{ PROFILE : "被使用(Skin/Cape)"
图表来源
验证规则与状态判断
- 档案名称
- 非空且长度在1-16之间
- 更新时仅当名称发生变更才检查唯一性
- 用户ID
- 必须大于0
- 用户状态
- 仅当用户状态为1(正常)时允许创建档案
- 活跃状态
- 默认 IsActive=true
- 仅当状态为1时表示“正常可用”
- 设置活跃状态时,通过事务将该用户下其他档案置为非活跃
章节来源
活跃档案切换逻辑
- 设计目标:同一用户在同一时刻仅有一个活跃档案
- 实现方式:在设置活跃档案时,使用数据库事务
- 先将该用户下所有档案的 IsActive=false
- 再将目标档案 IsActive=true
- 最后更新最后使用时间
flowchart TD
Start(["开始"]) --> Load["加载档案(uuid, userID)"]
Load --> CheckPerm{"权限校验通过?"}
CheckPerm --> |否| ErrPerm["返回无权操作"]
CheckPerm --> |是| Txn["开启事务"]
Txn --> SetAllFalse["将用户其他档案置为非活跃"]
SetAllFalse --> SetTargetTrue["将目标档案置为活跃"]
SetTargetTrue --> UpdateTime["更新最后使用时间"]
UpdateTime --> Commit["提交事务"]
Commit --> Done(["结束"])
ErrPerm --> Done
图表来源
API 与错误处理
- 创建档案
- 请求体:CreateProfileRequest(仅需 name,长度1-16)
- 成功:返回 ProfileInfo(含 UUID、UserID、Name、IsActive、时间戳等)
- 常见错误:未授权、参数错误、已达上限、服务器错误
- 获取档案列表/详情
- 成功:返回 ProfileInfo 列表/对象
- 常见错误:未授权、服务器错误、档案不存在
- 更新档案
- 请求体:UpdateProfileRequest(可选 name、skin_id、cape_id)
- 成功:返回更新后的 ProfileInfo
- 常见错误:未授权、参数错误、无权操作、档案不存在、服务器错误
- 删除档案
- 成功:返回“删除成功”
- 常见错误:未授权、无权操作、档案不存在、服务器错误
- 设置活跃档案
- 成功:返回“设置成功”
- 常见错误:未授权、无权操作、档案不存在、服务器错误
章节来源
依赖关系分析
- Handler 依赖 Service
- Service 依赖 Repository
- Repository 依赖 Model 与数据库连接
- Model 间通过外键建立关联
graph LR
H["profile_handler.go"] --> S["profile_service.go"]
S --> R["profile_repository.go"]
R --> M1["profile.go"]
R --> M2["user.go"]
R --> M3["texture.go"]
图表来源
性能考量
- 查询预加载:获取档案详情与列表时预加载 Skin 与 Cape,减少 N+1 查询风险
- 事务一致性:设置活跃档案使用事务,保证原子性与一致性
- 唯一索引:Name 与 Hash 等字段具备唯一索引,降低重复写入成本
- 时间戳:LastUsedAt 便于后续统计与清理策略
[本节为通用建议,不涉及具体文件分析]
故障排查指南
- 创建失败
- 用户不存在或状态异常:检查用户是否存在且状态为1
- 角色名重复:确认名称唯一性
- 达到档案数量上限:检查当前用户档案数量与上限配置
- 数据库错误:查看事务提交与唯一约束冲突
- 更新失败
- 无权操作:确认请求用户ID与档案所属用户一致
- 名称重复:若修改了名称,需确保唯一性
- 删除失败
- 无权操作:确认请求用户ID与档案所属用户一致
- 设置活跃失败
- 无权操作:确认请求用户ID与档案所属用户一致
- 事务回滚:检查数据库事务日志
- 获取失败
- 档案不存在:确认UUID正确且未被删除
章节来源
结论
ProfileService 提供了完整的档案生命周期管理能力,涵盖创建、查询、更新、删除与活跃状态切换,并通过严格的验证规则与事务保障确保数据一致性。档案与用户、材质的关联清晰,便于扩展更多功能。建议在生产环境中结合日志与监控,持续优化性能与稳定性。
[本节为总结性内容,不涉及具体文件分析]
附录
- 关键方法路径参考
- 创建档案:CreateProfile
- 获取档案详情:GetProfileByUUID
- 获取用户档案列表:GetUserProfiles
- 更新档案:UpdateProfile
- 删除档案:DeleteProfile
- 设置活跃档案:SetActiveProfile
- 数量限制检查:CheckProfileLimit
- 校验档案归属:ValidateProfileByUserID
- 名称批量查询:GetProfilesDataByNames
- 密钥对查询:GetProfileKeyPair
- Handler 路由与错误映射
- 创建/获取/更新/删除/设置活跃:profile_handler.go