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

354 lines
16 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>
**本文档引用的文件**
- [routes.go](file://internal/handler/routes.go)
- [profile_handler.go](file://internal/handler/profile_handler.go)
- [auth.go](file://internal/middleware/auth.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)
- [common.go](file://internal/types/common.go)
- [jwt.go](file://pkg/auth/jwt.go)
- [manager.go](file://pkg/database/manager.go)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
## 简介
本章节面向需要对接“档案更新与删除”接口的开发者,重点说明以下内容:
- PUT /api/v1/profile/:uuid 的更新流程:支持修改档案名称、更换关联的皮肤或披风材质;强调“只能修改自己名下的档案”的权限校验。
- DELETE /api/v1/profile/:uuid 的删除流程:删除前进行权限检查与档案存在性验证。
- 请求体格式与错误处理策略如403无权操作、404资源不存在
- 结合 service 层代码解释更新操作的事务性保证与数据一致性维护机制。
## 项目结构
围绕档案更新与删除功能,涉及如下关键模块:
- 路由层:在路由中注册了 /api/v1/profile/{uuid} 的 PUT 与 DELETE 接口,并统一使用鉴权中间件。
- 中间件层JWT 鉴权中间件负责解析 Authorization 头并校验令牌有效性,将用户标识注入上下文。
- 处理器层profile_handler 负责接收请求、绑定参数、调用 service 并输出响应。
- 服务层profile_service 执行业务逻辑,包含权限校验、唯一性检查、更新与删除等。
- 仓储层profile_repository 封装数据库访问,提供查询、更新、删除与事务性操作。
- 模型与类型profile 模型定义档案字段及关联关系types 定义请求与响应结构。
- 数据库与鉴权GORM 管理数据库连接JWT 用于用户身份验证。
```mermaid
graph TB
Client["客户端"] --> Routes["路由: /api/v1/profile/{uuid}"]
Routes --> AuthMW["鉴权中间件: JWT"]
AuthMW --> Handler["处理器: profile_handler"]
Handler --> Service["服务: profile_service"]
Service --> Repo["仓储: profile_repository"]
Repo --> DB["数据库: GORM"]
Handler --> Model["模型: Profile"]
Handler --> Types["类型: UpdateProfileRequest"]
```
图表来源
- [routes.go](file://internal/handler/routes.go#L63-L79)
- [auth.go](file://internal/middleware/auth.go#L12-L56)
- [profile_handler.go](file://internal/handler/profile_handler.go#L197-L339)
- [profile_service.go](file://internal/service/profile_service.go#L92-L159)
- [profile_repository.go](file://internal/repository/profile_repository.go#L19-L77)
- [profile.go](file://internal/model/profile.go#L7-L29)
- [common.go](file://internal/types/common.go#L201-L206)
章节来源
- [routes.go](file://internal/handler/routes.go#L63-L79)
- [auth.go](file://internal/middleware/auth.go#L12-L56)
## 核心组件
- 路由注册:在路由组 /api/v1/profile 下注册了 GET/:uuid、POST/、GET/、PUT/:uuid、DELETE/:uuid、POST/:uuid/activate 等接口,其中 PUT 与 DELETE 对应本节主题。
- 鉴权中间件:要求 Authorization 头为 Bearer 令牌,校验通过后将 user_id 等信息写入上下文。
- 处理器UpdateProfile 与 DeleteProfile 分别调用 service 层执行业务逻辑,并根据 service 返回的错误映射为合适的 HTTP 状态码。
- 服务层UpdateProfile 与 DeleteProfile 在执行前均进行“档案存在性验证”和“权限校验”,并在必要时使用数据库事务保证一致性。
- 仓储层FindProfileByUUID、UpdateProfile、DeleteProfile 提供基础 CRUDSetActiveProfile 使用事务确保“同一用户仅有一个活跃档案”。
章节来源
- [routes.go](file://internal/handler/routes.go#L63-L79)
- [profile_handler.go](file://internal/handler/profile_handler.go#L197-L339)
- [profile_service.go](file://internal/service/profile_service.go#L92-L159)
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L109)
## 架构总览
下图展示了从客户端到数据库的调用链路,以及权限校验与事务控制的关键节点。
```mermaid
sequenceDiagram
participant C as "客户端"
participant R as "路由"
participant M as "鉴权中间件"
participant H as "处理器 : UpdateProfile/DeleteProfile"
participant S as "服务 : profile_service"
participant P as "仓储 : profile_repository"
participant D as "数据库 : GORM"
C->>R : "PUT /api/v1/profile/{uuid}"
R->>M : "进入鉴权中间件"
M-->>R : "校验通过,注入 user_id"
R->>H : "转发请求"
H->>S : "调用 UpdateProfile/ DeleteProfile"
S->>P : "FindProfileByUUID"
P->>D : "查询档案"
D-->>P : "返回档案或记录不存在"
P-->>S : "返回结果"
alt "更新场景"
S->>S : "权限校验 : profile.UserID == user_id"
S->>P : "可选 : 名称唯一性检查"
S->>P : "UpdateProfile(更新字段)"
P->>D : "保存更新"
D-->>P : "成功"
P-->>S : "返回更新后的档案"
else "删除场景"
S->>S : "权限校验 : profile.UserID == user_id"
S->>P : "DeleteProfile"
P->>D : "删除记录"
D-->>P : "成功"
end
S-->>H : "返回结果或错误"
H-->>C : "HTTP 响应(200/403/404/500)"
```
图表来源
- [profile_handler.go](file://internal/handler/profile_handler.go#L197-L339)
- [profile_service.go](file://internal/service/profile_service.go#L92-L159)
- [profile_repository.go](file://internal/repository/profile_repository.go#L19-L77)
## 详细组件分析
### PUT /api/v1/profile/:uuid 更新档案
- 功能概述
- 支持修改档案名称(可选)、更换关联的皮肤或披风材质(可选)。
- 严格权限控制:仅允许修改“自己名下的档案”。
- 请求路径与方法
- 方法: PUT
- 路径: /api/v1/profile/:uuid
- 鉴权: 需要 Bearer 令牌
- 请求体结构
- 字段:
- name: 字符串,长度范围 1-16可选
- skin_id: 整数指向材质记录的ID可选
- cape_id: 整数指向材质记录的ID可选
- 示例: 仅更新名称
- 示例: 仅更换皮肤
- 示例: 同时更换皮肤与披风
- 成功响应
- 返回更新后的档案信息(包含 uuid、user_id、name、skin_id、cape_id、is_active、last_used_at、created_at、updated_at
- 错误处理
- 400: 请求参数错误(如字段校验失败)
- 401: 未授权(缺少或无效的 Authorization 头)
- 403: 无权操作(目标档案不属于当前用户)
- 404: 资源不存在档案UUID不存在
- 500: 服务器内部错误(数据库异常、唯一性冲突等)
- 权限与存在性校验
- 处理器从上下文取出 user_id若缺失则直接返回 401。
- 服务层先查询档案,若不存在返回 404随后校验 profile.UserID 是否等于 user_id否则返回 403。
- 名称唯一性与字段更新
- 当 name 发生变化时,服务层会检查同名是否已存在,若存在则返回 400。
- skin_id 与 cape_id 为可选字段,仅当传入非空值时才更新对应字段。
- 事务性与一致性
- 更新操作本身通过单条 save/update 完成,不涉及跨表事务。
- 若未来扩展为多步更新(例如同时更新多个关联字段),建议在服务层使用 GORM 事务包裹,确保原子性与一致性。
```mermaid
flowchart TD
Start(["进入 UpdateProfile"]) --> Parse["解析请求体<br/>name/skin_id/cape_id"]
Parse --> GetCtx["从上下文获取 user_id"]
GetCtx --> HasUser{"user_id 存在?"}
HasUser --> |否| Resp401["返回 401 未授权"]
HasUser --> |是| Load["查询档案 FindProfileByUUID"]
Load --> Found{"档案存在?"}
Found --> |否| Resp404["返回 404 资源不存在"]
Found --> |是| Perm{"档案归属校验<br/>profile.UserID == user_id"}
Perm --> |否| Resp403["返回 403 无权操作"]
Perm --> |是| NameChk{"是否更新 name"}
NameChk --> |是| Dup{"检查同名是否存在"}
Dup --> |是| Resp400["返回 400 参数错误"]
Dup --> |否| Apply["应用字段更新<br/>name/skin_id/cape_id"]
NameChk --> |否| Apply
Apply --> Save["保存更新 UpdateProfile"]
Save --> Reload["重新加载档案 FindProfileByUUID"]
Reload --> Resp200["返回 200 成功"]
```
图表来源
- [profile_handler.go](file://internal/handler/profile_handler.go#L197-L280)
- [profile_service.go](file://internal/service/profile_service.go#L92-L135)
- [profile_repository.go](file://internal/repository/profile_repository.go#L19-L71)
章节来源
- [profile_handler.go](file://internal/handler/profile_handler.go#L197-L280)
- [profile_service.go](file://internal/service/profile_service.go#L92-L135)
- [common.go](file://internal/types/common.go#L201-L206)
### DELETE /api/v1/profile/:uuid 删除档案
- 功能概述
- 删除指定 UUID 的档案。
- 删除前进行权限检查与档案存在性验证。
- 请求路径与方法
- 方法: DELETE
- 路径: /api/v1/profile/:uuid
- 鉴权: 需要 Bearer 令牌
- 成功响应
- 返回成功消息message: "删除成功")。
- 错误处理
- 401: 未授权(缺少或无效的 Authorization 头)
- 403: 无权操作(目标档案不属于当前用户)
- 404: 资源不存在档案UUID不存在
- 500: 服务器内部错误(数据库异常)
- 删除流程
- 处理器从上下文取出 user_id若缺失则直接返回 401。
- 服务层先查询档案,若不存在返回 404随后校验权限不匹配返回 403。
- 通过权限校验后执行删除,成功返回 200。
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "处理器 : DeleteProfile"
participant S as "服务 : profile_service"
participant P as "仓储 : profile_repository"
participant D as "数据库 : GORM"
C->>H : "DELETE /api/v1/profile/{uuid}"
H->>S : "调用 DeleteProfile(uuid, user_id)"
S->>P : "FindProfileByUUID"
P->>D : "查询"
D-->>P : "返回档案或不存在"
P-->>S : "结果"
alt "档案不存在"
S-->>H : "返回 404"
H-->>C : "404"
else "权限校验失败"
S-->>H : "返回 403"
H-->>C : "403"
else "权限通过"
S->>P : "DeleteProfile"
P->>D : "删除记录"
D-->>P : "成功"
S-->>H : "返回 nil"
H-->>C : "200 成功"
end
```
图表来源
- [profile_handler.go](file://internal/handler/profile_handler.go#L282-L339)
- [profile_service.go](file://internal/service/profile_service.go#L137-L159)
- [profile_repository.go](file://internal/repository/profile_repository.go#L73-L77)
章节来源
- [profile_handler.go](file://internal/handler/profile_handler.go#L282-L339)
- [profile_service.go](file://internal/service/profile_service.go#L137-L159)
### 权限验证与鉴权中间件
- 中间件职责
- 校验 Authorization 头格式Bearer token
- 使用 JWT 服务验证令牌有效性,并将 user_id、username、role 写入上下文。
- 处理器侧使用
- 处理器从上下文读取 user_id若不存在则返回 401。
- 服务层进一步校验档案归属,不匹配返回 403。
- JWT 服务
- 生成与验证使用 HS256 签名算法,过期时间可配置。
章节来源
- [auth.go](file://internal/middleware/auth.go#L12-L56)
- [jwt.go](file://pkg/auth/jwt.go#L10-L71)
- [profile_handler.go](file://internal/handler/profile_handler.go#L197-L339)
### 事务性保证与数据一致性
- 更新操作
- 当前 UpdateProfile 通过单次 save/update 完成,不涉及跨表事务。
- 若未来扩展为多步更新(例如同时更新多个字段或关联表),建议在服务层使用 GORM 事务包裹,确保原子性与一致性。
- 删除操作
- DeleteProfile 为单条删除,不涉及跨表事务。
- 活跃档案设置
- SetActiveProfile 使用 GORM 事务,先将用户所有档案设为非活跃,再将目标档案设为活跃,保证“同一用户仅有一个活跃档案”的约束。
章节来源
- [profile_service.go](file://internal/service/profile_service.go#L92-L135)
- [profile_service.go](file://internal/service/profile_service.go#L137-L188)
- [profile_repository.go](file://internal/repository/profile_repository.go#L89-L109)
## 依赖关系分析
- 路由依赖
- profile 路由组使用鉴权中间件,确保后续接口均需有效 JWT。
- 处理器依赖
- profile_handler 依赖鉴权中间件提供的 user_id依赖 service 层执行业务逻辑,依赖 model/types 定义的数据结构。
- 服务层依赖
- profile_service 依赖 repository 层进行数据访问,依赖数据库连接管理器。
- 仓储层依赖
- profile_repository 依赖 GORM 与数据库连接。
- 鉴权与数据库
- jwt.go 提供令牌签发与校验manager.go 提供数据库连接获取与迁移。
```mermaid
graph LR
Routes["routes.go"] --> AuthMW["auth.go"]
AuthMW --> Handler["profile_handler.go"]
Handler --> Service["profile_service.go"]
Service --> Repo["profile_repository.go"]
Repo --> DBMgr["manager.go"]
Handler --> Types["common.go"]
Handler --> Model["profile.go"]
Handler --> JWT["jwt.go"]
```
图表来源
- [routes.go](file://internal/handler/routes.go#L63-L79)
- [auth.go](file://internal/middleware/auth.go#L12-L56)
- [profile_handler.go](file://internal/handler/profile_handler.go#L197-L339)
- [profile_service.go](file://internal/service/profile_service.go#L92-L159)
- [profile_repository.go](file://internal/repository/profile_repository.go#L19-L77)
- [manager.go](file://pkg/database/manager.go#L35-L50)
- [common.go](file://internal/types/common.go#L201-L206)
- [profile.go](file://internal/model/profile.go#L7-L29)
- [jwt.go](file://pkg/auth/jwt.go#L10-L71)
章节来源
- [routes.go](file://internal/handler/routes.go#L63-L79)
- [profile_handler.go](file://internal/handler/profile_handler.go#L197-L339)
- [profile_service.go](file://internal/service/profile_service.go#L92-L159)
- [profile_repository.go](file://internal/repository/profile_repository.go#L19-L77)
- [manager.go](file://pkg/database/manager.go#L35-L50)
## 性能考量
- 查询与预加载
- 仓储层在查询档案时使用预加载关联的皮肤与披风,有助于减少 N+1 查询问题,提升响应速度。
- 事务范围
- 当前更新与删除均为单条操作,事务开销较小;若扩展为多步更新,建议将相关操作合并到事务中,避免部分成功导致的不一致。
- 唯一性检查
- 更新名称时进行同名检查,避免并发场景下的重复;建议在数据库层面增加唯一索引以降低竞争条件风险。
- 日志与可观测性
- 处理器在发生错误时记录日志,便于定位问题;建议在服务层也增加关键步骤的日志埋点。
章节来源
- [profile_repository.go](file://internal/repository/profile_repository.go#L19-L31)
- [profile_service.go](file://internal/service/profile_service.go#L92-L135)
## 故障排查指南
- 401 未授权
- 检查请求头 Authorization 是否为 Bearer 令牌格式;确认令牌未过期且签名正确。
- 403 无权操作
- 确认当前用户是否拥有目标档案;检查档案所属 user_id 与当前用户是否一致。
- 404 资源不存在
- 确认档案 UUID 是否正确;检查数据库中是否存在该记录。
- 400 参数错误
- 检查请求体字段是否符合长度与类型要求;例如 name 长度应在 1-16 之间。
- 500 服务器错误
- 查看服务端日志,关注数据库连接、唯一性冲突、事务回滚等问题。
章节来源
- [profile_handler.go](file://internal/handler/profile_handler.go#L197-L339)
- [profile_service.go](file://internal/service/profile_service.go#L92-L159)
## 结论
- PUT /api/v1/profile/:uuid 与 DELETE /api/v1/profile/:uuid 已具备完善的权限校验与存在性验证机制。
- 更新操作支持名称与材质字段的灵活更新,删除操作简洁可靠。
- 服务层与仓储层清晰分离职责,当前更新与删除为单步操作;若未来扩展为多步更新,建议引入事务以保障一致性。
- 建议在数据库层面完善唯一性约束,并在服务层增加关键步骤的日志埋点,以便于问题定位与性能优化。