Files
backend/.qoder/repowiki/zh/content/服务架构/档案服务.md
lan a4b6c5011e
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
chore(git): 更新.gitignore以忽略新的本地文件
2025-11-30 08:33:17 +08:00

365 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 档案服务
<cite>
**本文引用的文件**
- [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)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本档“档案服务”文档聚焦于 ProfileService 的职责、方法与内部逻辑覆盖档案的创建、更新、删除与活跃状态管理明确档案名称与用户ID的验证规则名称非空且长度1-16用户ID大于0以及状态有效性判断仅当状态为1时表示正常可用阐述新创建档案默认为活跃状态的业务规则并说明多档案用户的活跃档案切换逻辑解释档案与用户、材质之间的关联关系最后提供常见错误场景的排查指南帮助开发者快速定位问题。
## 项目结构
档案服务位于 internal/service 层,通过 internal/handler 接收HTTP请求调用 ProfileService 完成业务逻辑,再由 ProfileService 调用 internal/repository 访问数据库;数据模型定义在 internal/model 中,类型定义在 internal/types 中。
```mermaid
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
```
图表来源
- [profile_handler.go](file://internal/handler/profile_handler.go#L1-L399)
- [profile_service.go](file://internal/service/profile_service.go#L1-L253)
- [profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [profile.go](file://internal/model/profile.go#L1-L64)
- [user.go](file://internal/model/user.go#L1-L71)
- [texture.go](file://internal/model/texture.go#L1-L77)
- [common.go](file://internal/types/common.go#L81-L207)
章节来源
- [profile_handler.go](file://internal/handler/profile_handler.go#L1-L399)
- [profile_service.go](file://internal/service/profile_service.go#L1-L253)
- [profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [profile.go](file://internal/model/profile.go#L1-L64)
- [user.go](file://internal/model/user.go#L1-L71)
- [texture.go](file://internal/model/texture.go#L1-L77)
- [common.go](file://internal/types/common.go#L81-L207)
## 核心组件
- ProfileService负责档案的创建、查询、更新、删除、活跃状态设置与数量限制检查等核心业务逻辑。
- ProfileRepository封装数据库访问包括创建、查询、更新、删除、统计、设置活跃状态等。
- Profile 模型:定义档案的数据结构及与用户、材质的关联。
- Handler接收HTTP请求解析参数调用服务层并返回统一响应。
- 类型定义CreateProfileRequest、UpdateProfileRequest、ProfileInfo 等用于API契约与响应结构。
章节来源
- [profile_service.go](file://internal/service/profile_service.go#L1-L253)
- [profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [profile.go](file://internal/model/profile.go#L1-L64)
- [profile_handler.go](file://internal/handler/profile_handler.go#L1-L399)
- [common.go](file://internal/types/common.go#L81-L207)
## 架构总览
档案服务采用典型的三层架构Handler 负责接口与参数校验Service 负责业务规则与流程编排Repository 负责数据持久化。ProfileService 在创建档案时会进行用户存在性与状态校验、角色名唯一性校验、生成UUID与RSA密钥、创建档案并将其设置为活跃状态在设置活跃状态时通过事务将该用户下的其他档案全部置为非活跃确保同一时刻仅有一个活跃档案。
```mermaid
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 成功响应"
```
图表来源
- [profile_handler.go](file://internal/handler/profile_handler.go#L28-L93)
- [profile_service.go](file://internal/service/profile_service.go#L17-L68)
- [profile_repository.go](file://internal/repository/profile_repository.go#L13-L109)
## 详细组件分析
### 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_service.go](file://internal/service/profile_service.go#L17-L253)
- [profile_repository.go](file://internal/repository/profile_repository.go#L13-L177)
- [profile.go](file://internal/model/profile.go#L1-L64)
### 数据模型与关联关系
- Profile
- 字段UUID、UserID、Name、SkinID、CapeID、RSAPrivateKey、IsActive、LastUsedAt、CreatedAt、UpdatedAt
- 关联User外键 UserID、TextureSkinID、CapeID
- User
- 字段ID、Username、Email、Role、Status1: 正常, 0: 禁用, -1: 删除)
- Texture
- 字段ID、UploaderID、Name、Type、URL、Hash、IsPublic、DownloadCount、FavoriteCount、IsSlim、Status1: 正常, 0: 审核中, -1: 已删除)
```mermaid
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)"
```
图表来源
- [profile.go](file://internal/model/profile.go#L1-L64)
- [user.go](file://internal/model/user.go#L1-L71)
- [texture.go](file://internal/model/texture.go#L1-L77)
### 验证规则与状态判断
- 档案名称
- 非空且长度在1-16之间
- 更新时仅当名称发生变更才检查唯一性
- 用户ID
- 必须大于0
- 用户状态
- 仅当用户状态为1正常时允许创建档案
- 活跃状态
- 默认 IsActive=true
- 仅当状态为1时表示“正常可用”
- 设置活跃状态时,通过事务将该用户下其他档案置为非活跃
章节来源
- [profile_service.go](file://internal/service/profile_service.go#L17-L68)
- [profile_service_test.go](file://internal/service/profile_service_test.go#L1-L77)
- [common.go](file://internal/types/common.go#L81-L207)
- [user.go](file://internal/model/user.go#L1-L71)
### 活跃档案切换逻辑
- 设计目标:同一用户在同一时刻仅有一个活跃档案
- 实现方式:在设置活跃档案时,使用数据库事务
- 先将该用户下所有档案的 IsActive=false
- 再将目标档案 IsActive=true
- 最后更新最后使用时间
```mermaid
flowchart TD
Start(["开始"]) --> Load["加载档案(uuid, userID)"]
Load --> CheckPerm{"权限校验通过?"}
CheckPerm --> |否| ErrPerm["返回无权操作"]
CheckPerm --> |是| Txn["开启事务"]
Txn --> SetAllFalse["将用户其他档案置为非活跃"]
SetAllFalse --> SetTargetTrue["将目标档案置为活跃"]
SetTargetTrue --> UpdateTime["更新最后使用时间"]
UpdateTime --> Commit["提交事务"]
Commit --> Done(["结束"])
ErrPerm --> Done
```
图表来源
- [profile_service.go](file://internal/service/profile_service.go#L161-L188)
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L109)
### API 与错误处理
- 创建档案
- 请求体CreateProfileRequest仅需 name长度1-16
- 成功:返回 ProfileInfo含 UUID、UserID、Name、IsActive、时间戳等
- 常见错误:未授权、参数错误、已达上限、服务器错误
- 获取档案列表/详情
- 成功:返回 ProfileInfo 列表/对象
- 常见错误:未授权、服务器错误、档案不存在
- 更新档案
- 请求体UpdateProfileRequest可选 name、skin_id、cape_id
- 成功:返回更新后的 ProfileInfo
- 常见错误:未授权、参数错误、无权操作、档案不存在、服务器错误
- 删除档案
- 成功:返回“删除成功”
- 常见错误:未授权、无权操作、档案不存在、服务器错误
- 设置活跃档案
- 成功:返回“设置成功”
- 常见错误:未授权、无权操作、档案不存在、服务器错误
章节来源
- [profile_handler.go](file://internal/handler/profile_handler.go#L28-L399)
- [common.go](file://internal/types/common.go#L81-L207)
## 依赖关系分析
- Handler 依赖 Service
- Service 依赖 Repository
- Repository 依赖 Model 与数据库连接
- Model 间通过外键建立关联
```mermaid
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"]
```
图表来源
- [profile_handler.go](file://internal/handler/profile_handler.go#L1-L399)
- [profile_service.go](file://internal/service/profile_service.go#L1-L253)
- [profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
- [profile.go](file://internal/model/profile.go#L1-L64)
- [user.go](file://internal/model/user.go#L1-L71)
- [texture.go](file://internal/model/texture.go#L1-L77)
## 性能考量
- 查询预加载:获取档案详情与列表时预加载 Skin 与 Cape减少 N+1 查询风险
- 事务一致性:设置活跃档案使用事务,保证原子性与一致性
- 唯一索引Name 与 Hash 等字段具备唯一索引,降低重复写入成本
- 时间戳LastUsedAt 便于后续统计与清理策略
[本节为通用建议,不涉及具体文件分析]
## 故障排查指南
- 创建失败
- 用户不存在或状态异常检查用户是否存在且状态为1
- 角色名重复:确认名称唯一性
- 达到档案数量上限:检查当前用户档案数量与上限配置
- 数据库错误:查看事务提交与唯一约束冲突
- 更新失败
- 无权操作确认请求用户ID与档案所属用户一致
- 名称重复:若修改了名称,需确保唯一性
- 删除失败
- 无权操作确认请求用户ID与档案所属用户一致
- 设置活跃失败
- 无权操作确认请求用户ID与档案所属用户一致
- 事务回滚:检查数据库事务日志
- 获取失败
- 档案不存在确认UUID正确且未被删除
章节来源
- [profile_service.go](file://internal/service/profile_service.go#L17-L253)
- [profile_repository.go](file://internal/repository/profile_repository.go#L13-L177)
- [profile_handler.go](file://internal/handler/profile_handler.go#L153-L399)
## 结论
ProfileService 提供了完整的档案生命周期管理能力,涵盖创建、查询、更新、删除与活跃状态切换,并通过严格的验证规则与事务保障确保数据一致性。档案与用户、材质的关联清晰,便于扩展更多功能。建议在生产环境中结合日志与监控,持续优化性能与稳定性。
[本节为总结性内容,不涉及具体文件分析]
## 附录
- 关键方法路径参考
- 创建档案:[CreateProfile](file://internal/service/profile_service.go#L17-L68)
- 获取档案详情:[GetProfileByUUID](file://internal/service/profile_service.go#L71-L81)
- 获取用户档案列表:[GetUserProfiles](file://internal/service/profile_service.go#L83-L90)
- 更新档案:[UpdateProfile](file://internal/service/profile_service.go#L92-L135)
- 删除档案:[DeleteProfile](file://internal/service/profile_service.go#L137-L159)
- 设置活跃档案:[SetActiveProfile](file://internal/service/profile_service.go#L161-L188)
- 数量限制检查:[CheckProfileLimit](file://internal/service/profile_service.go#L190-L202)
- 校验档案归属:[ValidateProfileByUserID](file://internal/service/profile_service.go#L222-L235)
- 名称批量查询:[GetProfilesDataByNames](file://internal/service/profile_service.go#L237-L243)
- 密钥对查询:[GetProfileKeyPair](file://internal/service/profile_service.go#L245-L253)
- Handler 路由与错误映射
- 创建/获取/更新/删除/设置活跃:[profile_handler.go](file://internal/handler/profile_handler.go#L28-L399)