288 lines
12 KiB
Markdown
288 lines
12 KiB
Markdown
|
|
# 激活管理
|
|||
|
|
|
|||
|
|
<cite>
|
|||
|
|
**本文引用的文件**
|
|||
|
|
- [routes.go](file://internal/handler/routes.go)
|
|||
|
|
- [profile_handler.go](file://internal/handler/profile_handler.go)
|
|||
|
|
- [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)
|
|||
|
|
- [response.go](file://internal/model/response.go)
|
|||
|
|
- [manager.go](file://pkg/database/manager.go)
|
|||
|
|
- [postgres.go](file://pkg/database/postgres.go)
|
|||
|
|
- [config.go](file://pkg/config/config.go)
|
|||
|
|
- [main.go](file://cmd/server/main.go)
|
|||
|
|
</cite>
|
|||
|
|
|
|||
|
|
## 目录
|
|||
|
|
1. [简介](#简介)
|
|||
|
|
2. [项目结构](#项目结构)
|
|||
|
|
3. [核心组件](#核心组件)
|
|||
|
|
4. [架构总览](#架构总览)
|
|||
|
|
5. [详细组件分析](#详细组件分析)
|
|||
|
|
6. [依赖关系分析](#依赖关系分析)
|
|||
|
|
7. [性能考量](#性能考量)
|
|||
|
|
8. [故障排查指南](#故障排查指南)
|
|||
|
|
9. [结论](#结论)
|
|||
|
|
|
|||
|
|
## 简介
|
|||
|
|
本文件聚焦于“档案激活管理API”的设计与实现,围绕 POST /api/v1/profile/:uuid/activate 端点展开,解释“活跃档案”的概念及其在系统中的作用:代表用户当前在游戏中使用的角色外观。调用该接口后,系统通过数据库事务确保原子性:先将用户所有其他档案的 is_active 字段设为 false,再将指定 UUID 的档案设为 active 状态;同时,该操作会更新档案的 last_used_at 时间戳,用于追踪最近使用情况。本文还提供调用示例、成功/失败响应说明,并结合 repository 层的 SetActiveProfile 事务实现,说明数据一致性保障机制。
|
|||
|
|
|
|||
|
|
## 项目结构
|
|||
|
|
该模块位于典型的分层架构中:
|
|||
|
|
- 路由层:定义 API 路由与鉴权中间件绑定
|
|||
|
|
- 处理器层:接收请求、解析参数、调用服务层并输出响应
|
|||
|
|
- 服务层:编排业务流程,进行权限校验与调用仓库层
|
|||
|
|
- 仓库层:封装数据库操作,提供事务与字段更新能力
|
|||
|
|
- 模型层:定义数据结构与表映射
|
|||
|
|
- 数据库层:GORM 初始化、连接池与迁移
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TB
|
|||
|
|
subgraph "路由层"
|
|||
|
|
R["internal/handler/routes.go"]
|
|||
|
|
end
|
|||
|
|
subgraph "处理器层"
|
|||
|
|
H["internal/handler/profile_handler.go"]
|
|||
|
|
end
|
|||
|
|
subgraph "服务层"
|
|||
|
|
S["internal/service/profile_service.go"]
|
|||
|
|
end
|
|||
|
|
subgraph "仓库层"
|
|||
|
|
RP["internal/repository/profile_repository.go"]
|
|||
|
|
end
|
|||
|
|
subgraph "模型层"
|
|||
|
|
M["internal/model/profile.go"]
|
|||
|
|
end
|
|||
|
|
subgraph "数据库层"
|
|||
|
|
DM["pkg/database/manager.go"]
|
|||
|
|
DP["pkg/database/postgres.go"]
|
|||
|
|
end
|
|||
|
|
subgraph "应用入口"
|
|||
|
|
MAIN["cmd/server/main.go"]
|
|||
|
|
end
|
|||
|
|
R --> H
|
|||
|
|
H --> S
|
|||
|
|
S --> RP
|
|||
|
|
RP --> DM
|
|||
|
|
DM --> DP
|
|||
|
|
S --> M
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [routes.go](file://internal/handler/routes.go#L63-L79)
|
|||
|
|
- [profile_handler.go](file://internal/handler/profile_handler.go#L354-L399)
|
|||
|
|
- [profile_service.go](file://internal/service/profile_service.go#L161-L188)
|
|||
|
|
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L117)
|
|||
|
|
- [profile.go](file://internal/model/profile.go#L7-L24)
|
|||
|
|
- [manager.go](file://pkg/database/manager.go#L13-L50)
|
|||
|
|
- [postgres.go](file://pkg/database/postgres.go#L13-L60)
|
|||
|
|
- [main.go](file://cmd/server/main.go#L41-L51)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [routes.go](file://internal/handler/routes.go#L63-L79)
|
|||
|
|
- [profile_handler.go](file://internal/handler/profile_handler.go#L354-L399)
|
|||
|
|
- [profile_service.go](file://internal/service/profile_service.go#L161-L188)
|
|||
|
|
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L117)
|
|||
|
|
- [profile.go](file://internal/model/profile.go#L7-L24)
|
|||
|
|
- [manager.go](file://pkg/database/manager.go#L13-L50)
|
|||
|
|
- [postgres.go](file://pkg/database/postgres.go#L13-L60)
|
|||
|
|
- [main.go](file://cmd/server/main.go#L41-L51)
|
|||
|
|
|
|||
|
|
## 核心组件
|
|||
|
|
- 路由注册:在路由组 /api/v1/profile 下注册 POST /:uuid/activate,绑定鉴权中间件
|
|||
|
|
- 处理器:从上下文提取用户ID与UUID,调用服务层设置活跃档案
|
|||
|
|
- 服务层:校验档案归属、调用仓库层执行事务设置活跃状态,并更新 last_used_at
|
|||
|
|
- 仓库层:使用 GORM 事务,先批量将用户其他档案置为非活跃,再将目标档案置为活跃;随后更新 last_used_at
|
|||
|
|
- 模型层:Profile 结构体包含 uuid、user_id、name、is_active、last_used_at 等字段
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [routes.go](file://internal/handler/routes.go#L63-L79)
|
|||
|
|
- [profile_handler.go](file://internal/handler/profile_handler.go#L354-L399)
|
|||
|
|
- [profile_service.go](file://internal/service/profile_service.go#L161-L188)
|
|||
|
|
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L117)
|
|||
|
|
- [profile.go](file://internal/model/profile.go#L7-L24)
|
|||
|
|
|
|||
|
|
## 架构总览
|
|||
|
|
下图展示了从客户端到数据库的完整调用链路,以及事务原子性保障。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant C as "客户端"
|
|||
|
|
participant R as "路由层<br/>routes.go"
|
|||
|
|
participant H as "处理器层<br/>profile_handler.go"
|
|||
|
|
participant S as "服务层<br/>profile_service.go"
|
|||
|
|
participant RP as "仓库层<br/>profile_repository.go"
|
|||
|
|
participant DB as "数据库(GORM)"
|
|||
|
|
C->>R : "POST /api/v1/profile/ : uuid/activate"
|
|||
|
|
R->>H : "绑定鉴权中间件后进入处理器"
|
|||
|
|
H->>H : "从上下文获取 user_id 并校验"
|
|||
|
|
H->>S : "调用 SetActiveProfile(db, uuid, user_id)"
|
|||
|
|
S->>RP : "FindProfileByUUID(uuid)"
|
|||
|
|
RP-->>S : "返回档案或错误"
|
|||
|
|
S->>S : "校验档案归属(user_id)"
|
|||
|
|
S->>RP : "SetActiveProfile(uuid, user_id) 事务"
|
|||
|
|
RP->>DB : "事务开始"
|
|||
|
|
DB-->>RP : "事务上下文"
|
|||
|
|
RP->>DB : "批量更新其他档案为非活跃"
|
|||
|
|
RP->>DB : "更新目标档案为活跃"
|
|||
|
|
RP-->>S : "提交事务"
|
|||
|
|
S->>RP : "UpdateProfileLastUsedAt(uuid)"
|
|||
|
|
RP->>DB : "更新 last_used_at"
|
|||
|
|
RP-->>S : "返回成功"
|
|||
|
|
S-->>H : "返回成功"
|
|||
|
|
H-->>C : "200 OK {message : 设置成功}"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [routes.go](file://internal/handler/routes.go#L63-L79)
|
|||
|
|
- [profile_handler.go](file://internal/handler/profile_handler.go#L354-L399)
|
|||
|
|
- [profile_service.go](file://internal/service/profile_service.go#L161-L188)
|
|||
|
|
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L117)
|
|||
|
|
|
|||
|
|
## 详细组件分析
|
|||
|
|
|
|||
|
|
### 活跃档案的概念与作用
|
|||
|
|
- 概念:活跃档案是用户当前在游戏中使用的角色外观,系统通过 is_active 字段标识
|
|||
|
|
- 作用:确保同一用户在同一时刻仅有一个活跃档案,避免多角色外观冲突;同时通过 last_used_at 记录最近使用时间,便于审计与统计
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [profile.go](file://internal/model/profile.go#L7-L24)
|
|||
|
|
|
|||
|
|
### 接口定义与调用流程
|
|||
|
|
- 方法与路径:POST /api/v1/profile/:uuid/activate
|
|||
|
|
- 鉴权:需携带有效 JWT,处理器从上下文提取 user_id
|
|||
|
|
- 参数:
|
|||
|
|
- 路径参数 uuid:目标档案的唯一标识
|
|||
|
|
- 成功响应:200 OK,返回统一成功响应结构
|
|||
|
|
- 失败响应:
|
|||
|
|
- 401 未授权:缺少或无效的 JWT
|
|||
|
|
- 403 禁止访问:档案不属于当前用户
|
|||
|
|
- 404 资源不存在:档案不存在
|
|||
|
|
- 500 服务器错误:其他数据库或业务异常
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [routes.go](file://internal/handler/routes.go#L63-L79)
|
|||
|
|
- [profile_handler.go](file://internal/handler/profile_handler.go#L354-L399)
|
|||
|
|
- [response.go](file://internal/model/response.go#L20-L38)
|
|||
|
|
|
|||
|
|
### 处理器层实现要点
|
|||
|
|
- 从上下文获取 user_id,若缺失返回 401
|
|||
|
|
- 调用服务层 SetActiveProfile,根据错误类型映射为 404 或 403 或 500
|
|||
|
|
- 成功返回 200 OK
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [profile_handler.go](file://internal/handler/profile_handler.go#L354-L399)
|
|||
|
|
- [response.go](file://internal/model/response.go#L20-L38)
|
|||
|
|
|
|||
|
|
### 服务层业务逻辑
|
|||
|
|
- 校验档案是否存在与归属关系
|
|||
|
|
- 调用仓库层 SetActiveProfile 执行事务
|
|||
|
|
- 事务完成后调用 UpdateProfileLastUsedAt 更新 last_used_at
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [profile_service.go](file://internal/service/profile_service.go#L161-L188)
|
|||
|
|
|
|||
|
|
### 仓库层事务实现与数据一致性
|
|||
|
|
- 使用 GORM 事务,确保以下两个更新在同一事务中:
|
|||
|
|
1) 将用户所有档案的 is_active 设为 false
|
|||
|
|
2) 将指定 uuid 的档案 is_active 设为 true
|
|||
|
|
- 提交事务后,调用 UpdateProfileLastUsedAt 将 last_used_at 更新为当前时间
|
|||
|
|
- 该实现保证了“同一时刻仅有一个活跃档案”的强一致性
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
Start(["进入 SetActiveProfile 事务"]) --> BatchDeactivate["批量更新其他档案为非活跃"]
|
|||
|
|
BatchDeactivate --> TargetActivate["更新目标档案为活跃"]
|
|||
|
|
TargetActivate --> Commit{"事务提交成功?"}
|
|||
|
|
Commit --> |是| UpdateTimestamp["更新 last_used_at"]
|
|||
|
|
Commit --> |否| Rollback["回滚事务"]
|
|||
|
|
UpdateTimestamp --> End(["返回成功"])
|
|||
|
|
Rollback --> End
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L117)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L117)
|
|||
|
|
|
|||
|
|
### 数据模型与字段说明
|
|||
|
|
- Profile 模型包含:
|
|||
|
|
- uuid:档案唯一标识
|
|||
|
|
- user_id:所属用户ID
|
|||
|
|
- name:角色名
|
|||
|
|
- is_active:是否为活跃档案
|
|||
|
|
- last_used_at:最后使用时间
|
|||
|
|
- created_at/updated_at:创建与更新时间
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [profile.go](file://internal/model/profile.go#L7-L24)
|
|||
|
|
|
|||
|
|
### 数据库连接与迁移
|
|||
|
|
- 应用启动时初始化数据库连接与 GORM 实例,执行 AutoMigrate 自动迁移
|
|||
|
|
- 连接池参数可配置,PostgreSQL 驱动通过 DSN 构建
|
|||
|
|
- Profile 表在迁移中创建,包含上述字段及索引
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [main.go](file://cmd/server/main.go#L41-L51)
|
|||
|
|
- [manager.go](file://pkg/database/manager.go#L13-L50)
|
|||
|
|
- [postgres.go](file://pkg/database/postgres.go#L13-L60)
|
|||
|
|
- [config.go](file://pkg/config/config.go#L34-L47)
|
|||
|
|
|
|||
|
|
## 依赖关系分析
|
|||
|
|
- 路由层依赖处理器层
|
|||
|
|
- 处理器层依赖服务层
|
|||
|
|
- 服务层依赖仓库层与模型层
|
|||
|
|
- 仓库层依赖数据库管理器与 GORM
|
|||
|
|
- 应用入口负责初始化数据库并执行迁移
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph LR
|
|||
|
|
Routes["routes.go"] --> Handler["profile_handler.go"]
|
|||
|
|
Handler --> Service["profile_service.go"]
|
|||
|
|
Service --> Repo["profile_repository.go"]
|
|||
|
|
Repo --> DBMgr["database/manager.go"]
|
|||
|
|
DBMgr --> PG["database/postgres.go"]
|
|||
|
|
Main["cmd/server/main.go"] --> DBMgr
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
图表来源
|
|||
|
|
- [routes.go](file://internal/handler/routes.go#L63-L79)
|
|||
|
|
- [profile_handler.go](file://internal/handler/profile_handler.go#L354-L399)
|
|||
|
|
- [profile_service.go](file://internal/service/profile_service.go#L161-L188)
|
|||
|
|
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L117)
|
|||
|
|
- [manager.go](file://pkg/database/manager.go#L13-L50)
|
|||
|
|
- [postgres.go](file://pkg/database/postgres.go#L13-L60)
|
|||
|
|
- [main.go](file://cmd/server/main.go#L41-L51)
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [routes.go](file://internal/handler/routes.go#L63-L79)
|
|||
|
|
- [profile_handler.go](file://internal/handler/profile_handler.go#L354-L399)
|
|||
|
|
- [profile_service.go](file://internal/service/profile_service.go#L161-L188)
|
|||
|
|
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L117)
|
|||
|
|
- [manager.go](file://pkg/database/manager.go#L13-L50)
|
|||
|
|
- [postgres.go](file://pkg/database/postgres.go#L13-L60)
|
|||
|
|
- [main.go](file://cmd/server/main.go#L41-L51)
|
|||
|
|
|
|||
|
|
## 性能考量
|
|||
|
|
- 事务内两次 UPDATE 操作,均基于 user_id 与 uuid 的过滤条件,建议在 user_id 上建立索引以提升批量更新效率
|
|||
|
|
- last_used_at 更新为单行更新,影响范围小
|
|||
|
|
- 数据库连接池参数可通过配置调整,以平衡并发与资源占用
|
|||
|
|
|
|||
|
|
[本节为通用性能建议,不直接分析具体文件]
|
|||
|
|
|
|||
|
|
## 故障排查指南
|
|||
|
|
- 401 未授权:确认请求头携带有效 JWT,且令牌未过期
|
|||
|
|
- 403 禁止访问:目标档案不属于当前用户,检查 uuid 对应的 user_id
|
|||
|
|
- 404 资源不存在:uuid 无效或档案已被删除
|
|||
|
|
- 500 服务器错误:数据库异常或服务层内部错误,查看日志定位具体原因
|
|||
|
|
- 事务一致性问题:若出现“多个活跃档案”,检查数据库索引与事务提交逻辑
|
|||
|
|
|
|||
|
|
章节来源
|
|||
|
|
- [profile_handler.go](file://internal/handler/profile_handler.go#L354-L399)
|
|||
|
|
- [profile_service.go](file://internal/service/profile_service.go#L161-L188)
|
|||
|
|
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L117)
|
|||
|
|
|
|||
|
|
## 结论
|
|||
|
|
POST /api/v1/profile/:uuid/activate 端点通过严格的鉴权与事务控制,确保“同一用户仅有一个活跃档案”的数据一致性,并在成功后更新 last_used_at。该设计既满足业务需求,又通过数据库层的事务保障了原子性与可靠性。建议在生产环境中关注索引与连接池配置,以进一步优化性能与稳定性。
|