chore(git): 更新.gitignore以忽略新的本地文件
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
Some checks failed
SonarQube Analysis / sonarqube (push) Has been cancelled
This commit is contained in:
269
.qoder/repowiki/zh/content/服务架构/服务架构.md
Normal file
269
.qoder/repowiki/zh/content/服务架构/服务架构.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# 服务架构
|
||||
|
||||
<cite>
|
||||
**本文档引用文件**
|
||||
- [user_service.go](file://internal/service/user_service.go)
|
||||
- [profile_service.go](file://internal/service/profile_service.go)
|
||||
- [texture_service.go](file://internal/service/texture_service.go)
|
||||
- [user_repository.go](file://internal/repository/user_repository.go)
|
||||
- [profile_repository.go](file://internal/repository/profile_repository.go)
|
||||
- [texture_repository.go](file://internal/repository/texture_repository.go)
|
||||
- [user.go](file://internal/model/user.go)
|
||||
- [profile.go](file://internal/model/profile.go)
|
||||
- [texture.go](file://internal/model/texture.go)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
2. [服务层职责概述](#服务层职责概述)
|
||||
3. [核心服务详细分析](#核心服务详细分析)
|
||||
4. [服务调用关系与流程](#服务调用关系与流程)
|
||||
5. [业务规则实现](#业务规则实现)
|
||||
6. [错误处理与日志记录](#错误处理与日志记录)
|
||||
7. [结论](#结论)
|
||||
|
||||
## 简介
|
||||
|
||||
CarrotSkin项目的服务层是业务逻辑的核心,负责协调数据访问、执行业务规则并为上层处理器提供接口。本服务架构文档重点介绍`UserService`、`ProfileService`和`TextureService`三大核心服务,详细说明其职责、方法、内部逻辑以及与Repository层的协作方式。文档旨在为初学者提供清晰的服务调用视图,并为经验丰富的开发者提供代码扩展和维护的深入指导。
|
||||
|
||||
## 服务层职责概述
|
||||
|
||||
服务层(位于`internal/service/`目录)是CarrotSkin应用的业务逻辑中枢,其主要职责包括:
|
||||
- **业务逻辑封装**:将复杂的业务规则(如用户注册、档案创建、材质上传)封装在独立的服务方法中。
|
||||
- **数据访问协调**:调用Repository层的方法来持久化或检索数据,实现与数据库的解耦。
|
||||
- **事务管理**:对于涉及多个数据操作的业务(如设置活跃档案),通过Repository层的事务功能保证数据一致性。
|
||||
- **数据验证与转换**:在数据进入或离开系统时进行验证和必要的格式转换。
|
||||
- **外部服务集成**:与认证(JWT)、存储(MinIO)、邮件等外部服务进行交互。
|
||||
|
||||
服务层通过定义清晰的函数接口,为`handler`层提供稳定、可复用的业务能力,是连接用户请求与数据持久化的关键桥梁。
|
||||
|
||||
## 核心服务详细分析
|
||||
|
||||
### UserService 分析
|
||||
|
||||
`UserService`负责管理用户生命周期的核心操作,包括注册、登录、信息更新和密码管理。
|
||||
|
||||
**核心方法与职责**:
|
||||
- `RegisterUser`:处理用户注册流程。该方法首先检查用户名和邮箱的唯一性,然后使用`pkg/auth`包对密码进行加密,创建用户记录,并生成JWT Token。它还实现了头像URL的逻辑:优先使用用户提供的URL,否则从系统配置中获取默认头像。
|
||||
- `LoginUser`:处理用户登录。支持通过用户名或邮箱登录。方法会根据输入是否包含`@`符号来判断登录方式。成功登录后,会更新用户的最后登录时间,并记录登录日志(成功或失败)。
|
||||
- `ChangeUserPassword` 和 `ResetUserPassword`:分别处理用户主动修改密码和通过邮箱重置密码的场景。两者都包含密码验证和加密更新的逻辑。
|
||||
- `UpdateUserInfo` 和 `UpdateUserAvatar`:用于更新用户的基本信息和头像。
|
||||
|
||||
该服务通过调用`repository.FindUserByUsername`、`repository.FindUserByEmail`等方法与Repository层交互,确保了数据访问的抽象化。
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class UserService {
|
||||
+RegisterUser(jwtService *auth.JWTService, username, password, email, avatar string) (*model.User, string, error)
|
||||
+LoginUser(jwtService *auth.JWTService, usernameOrEmail, password, ipAddress, userAgent string) (*model.User, string, error)
|
||||
+GetUserByID(id int64) (*model.User, error)
|
||||
+UpdateUserInfo(user *model.User) error
|
||||
+UpdateUserAvatar(userID int64, avatarURL string) error
|
||||
+ChangeUserPassword(userID int64, oldPassword, newPassword string) error
|
||||
+ResetUserPassword(email, newPassword string) error
|
||||
+ChangeUserEmail(userID int64, newEmail string) error
|
||||
}
|
||||
class User {
|
||||
+ID int64
|
||||
+Username string
|
||||
+Email string
|
||||
+Avatar string
|
||||
+Points int
|
||||
+Role string
|
||||
+Status int16
|
||||
}
|
||||
class Repository {
|
||||
+FindUserByUsername(username string) (*model.User, error)
|
||||
+FindUserByEmail(email string) (*model.User, error)
|
||||
+CreateUser(user *model.User) error
|
||||
+UpdateUserFields(id int64, fields map[string]interface{}) error
|
||||
}
|
||||
class Auth {
|
||||
+HashPassword(password string) (string, error)
|
||||
+CheckPassword(hashedPassword, password string) bool
|
||||
+GenerateToken(userID int64, username, role string) (string, error)
|
||||
}
|
||||
UserService --> User : "操作"
|
||||
UserService --> Repository : "调用"
|
||||
UserService --> Auth : "依赖"
|
||||
```
|
||||
|
||||
**图示来源**
|
||||
- [user_service.go](file://internal/service/user_service.go#L12-L249)
|
||||
- [user_repository.go](file://internal/repository/user_repository.go#L1-L137)
|
||||
- [user.go](file://internal/model/user.go#L1-L71)
|
||||
|
||||
**本节来源**
|
||||
- [user_service.go](file://internal/service/user_service.go#L12-L249)
|
||||
- [user_repository.go](file://internal/repository/user_repository.go#L1-L137)
|
||||
|
||||
### ProfileService 分析
|
||||
|
||||
`ProfileService`负责管理用户的Minecraft档案(Profile),包括创建、查询、更新、删除和状态管理。
|
||||
|
||||
**核心方法与职责**:
|
||||
- `CreateProfile`:创建新的Minecraft档案。此方法首先验证用户存在且状态正常,检查角色名的唯一性,然后生成一个UUID作为档案ID,并调用`generateRSAPrivateKey`生成RSA-2048私钥(PEM格式)。创建档案后,会调用`SetActiveProfile`确保该档案成为用户的活跃档案。
|
||||
- `GetUserProfiles` 和 `GetProfileByUUID`:分别用于获取用户的所有档案列表和单个档案的详细信息。
|
||||
- `UpdateProfile`:更新档案信息,如角色名、皮肤和披风。更新角色名时会检查是否重复。更新后会重新加载数据以返回最新状态。
|
||||
- `DeleteProfile`:删除指定的档案。
|
||||
- `SetActiveProfile`:将指定档案设置为活跃状态。这是一个关键的业务方法,它在一个数据库事务中执行:首先将用户的所有档案设置为非活跃,然后将指定的档案设置为活跃,从而保证了同一时间只有一个活跃档案。
|
||||
- `CheckProfileLimit`:检查用户当前的档案数量是否达到了系统设定的上限,用于在创建新档案前进行限制。
|
||||
|
||||
该服务通过`repository.FindProfileByUUID`、`repository.CreateProfile`等方法与Repository层交互,并利用GORM的事务功能来保证`SetActiveProfile`操作的原子性。
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class ProfileService {
|
||||
+CreateProfile(db *gorm.DB, userID int64, name string) (*model.Profile, error)
|
||||
+GetProfileByUUID(db *gorm.DB, uuid string) (*model.Profile, error)
|
||||
+GetUserProfiles(db *gorm.DB, userID int64) ([]*model.Profile, error)
|
||||
+UpdateProfile(db *gorm.DB, uuid string, userID int64, name *string, skinID, capeID *int64) (*model.Profile, error)
|
||||
+DeleteProfile(db *gorm.DB, uuid string, userID int64) error
|
||||
+SetActiveProfile(db *gorm.DB, uuid string, userID int64) error
|
||||
+CheckProfileLimit(db *gorm.DB, userID int64, maxProfiles int) error
|
||||
}
|
||||
class Profile {
|
||||
+UUID string
|
||||
+UserID int64
|
||||
+Name string
|
||||
+SkinID *int64
|
||||
+CapeID *int64
|
||||
+RSAPrivateKey string
|
||||
+IsActive bool
|
||||
}
|
||||
class Repository {
|
||||
+FindProfileByUUID(uuid string) (*model.Profile, error)
|
||||
+CreateProfile(profile *model.Profile) error
|
||||
+FindProfilesByUserID(userID int64) ([]*model.Profile, error)
|
||||
+CountProfilesByUserID(userID int64) (int64, error)
|
||||
+SetActiveProfile(uuid string, userID int64) error
|
||||
+DeleteProfile(uuid string) error
|
||||
}
|
||||
ProfileService --> Profile : "操作"
|
||||
ProfileService --> Repository : "调用"
|
||||
```
|
||||
|
||||
**图示来源**
|
||||
- [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_service.go](file://internal/service/profile_service.go#L1-L253)
|
||||
- [profile_repository.go](file://internal/repository/profile_repository.go#L1-L200)
|
||||
|
||||
### TextureService 分析
|
||||
|
||||
`TextureService`负责管理用户上传的皮肤(Skin)和披风(Cape)材质。
|
||||
|
||||
**核心方法与职责**:
|
||||
- `CreateTexture`:创建新的材质记录。方法会验证上传者用户的存在性,并检查材质的Hash值是否已存在(防止重复上传)。它通过`switch`语句将字符串类型的`textureType`转换为`model.TextureType`枚举值。创建成功后,材质记录会被持久化。
|
||||
- `SearchTextures` 和 `GetUserTextures`:提供按关键词、类型、公开性等条件搜索材质,以及获取特定用户上传的材质列表的功能。
|
||||
- `UpdateTexture`:允许上传者更新材质的名称、描述和公开状态。更新时会检查权限,确保只有上传者才能修改。
|
||||
- `DeleteTexture`:允许上传者删除自己的材质。
|
||||
- `RecordTextureDownload`:记录一次材质下载行为。该方法会增加材质的下载计数,并创建一条下载日志。
|
||||
- `ToggleTextureFavorite`:切换用户对某个材质的收藏状态。如果已收藏则取消收藏并减少收藏计数,反之则添加收藏并增加收藏计数。
|
||||
- `CheckTextureUploadLimit`:检查用户上传的材质数量是否达到了系统设定的上限。
|
||||
|
||||
该服务通过`repository.FindTextureByHash`、`repository.CreateTexture`等方法与Repository层交互,并通过`IncrementTextureDownloadCount`和`DecrementTextureFavoriteCount`等原子操作来安全地更新计数。
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class TextureService {
|
||||
+CreateTexture(db *gorm.DB, uploaderID int64, name, description, textureType, url, hash string, size int, isPublic, isSlim bool) (*model.Texture, error)
|
||||
+GetTextureByID(db *gorm.DB, id int64) (*model.Texture, error)
|
||||
+GetUserTextures(db *gorm.DB, uploaderID int64, page, pageSize int) ([]*model.Texture, int64, error)
|
||||
+SearchTextures(db *gorm.DB, keyword string, textureType model.TextureType, publicOnly bool, page, pageSize int) ([]*model.Texture, int64, error)
|
||||
+UpdateTexture(db *gorm.DB, textureID, uploaderID int64, name, description string, isPublic *bool) (*model.Texture, error)
|
||||
+DeleteTexture(db *gorm.DB, textureID, uploaderID int64) error
|
||||
+RecordTextureDownload(db *gorm.DB, textureID int64, userID *int64, ipAddress, userAgent string) error
|
||||
+ToggleTextureFavorite(db *gorm.DB, userID, textureID int64) (bool, error)
|
||||
+GetUserTextureFavorites(db *gorm.DB, userID int64, page, pageSize int) ([]*model.Texture, int64, error)
|
||||
+CheckTextureUploadLimit(db *gorm.DB, uploaderID int64, maxTextures int) error
|
||||
}
|
||||
class Texture {
|
||||
+ID int64
|
||||
+UploaderID int64
|
||||
+Name string
|
||||
+Type TextureType
|
||||
+URL string
|
||||
+Hash string
|
||||
+DownloadCount int
|
||||
+FavoriteCount int
|
||||
}
|
||||
class Repository {
|
||||
+FindTextureByHash(hash string) (*model.Texture, error)
|
||||
+CreateTexture(texture *model.Texture) error
|
||||
+FindTexturesByUploaderID(uploaderID int64, page, pageSize int) ([]*model.Texture, int64, error)
|
||||
+SearchTextures(keyword string, textureType model.TextureType, publicOnly bool, page, pageSize int) ([]*model.Texture, int64, error)
|
||||
+UpdateTextureFields(id int64, fields map[string]interface{}) error
|
||||
+DeleteTexture(id int64) error
|
||||
+IncrementTextureDownloadCount(id int64) error
|
||||
+IncrementTextureFavoriteCount(id int64) error
|
||||
+DecrementTextureFavoriteCount(id int64) error
|
||||
+IsTextureFavorited(userID, textureID int64) (bool, error)
|
||||
}
|
||||
TextureService --> Texture : "操作"
|
||||
TextureService --> Repository : "调用"
|
||||
```
|
||||
|
||||
**图示来源**
|
||||
- [texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
- [texture.go](file://internal/model/texture.go#L1-L77)
|
||||
|
||||
**本节来源**
|
||||
- [texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
|
||||
## 服务调用关系与流程
|
||||
|
||||
服务层的调用流程通常始于`handler`层的HTTP请求处理器。例如,当用户发起注册请求时,`auth_handler.go`中的处理器会调用`UserService.RegisterUser`方法。该方法内部会协调多个Repository操作(检查用户、创建用户),并依赖`pkg/auth`包进行密码加密和Token生成。
|
||||
|
||||
服务之间通常是独立的,但它们都依赖于`Repository`层来访问数据。例如,`ProfileService.CreateProfile`在创建档案时,会先调用`repository.FindUserByID`来验证用户,这与`UserService`使用的Repository是同一个,体现了数据访问层的共享性。
|
||||
|
||||
一个典型的跨服务流程是用户上传材质并将其设置为档案皮肤:
|
||||
1. `TextureService.CreateTexture` 被调用,创建材质记录。
|
||||
2. `ProfileService.UpdateProfile` 被调用,传入新创建的材质ID作为`skinID`参数,更新档案的皮肤引用。
|
||||
|
||||
这种设计保证了服务的单一职责,同时通过Repository层实现了数据的统一管理。
|
||||
|
||||
## 业务规则实现
|
||||
|
||||
服务层是复杂业务规则的主要实现者。
|
||||
|
||||
**档案数量限制**:
|
||||
`ProfileService.CheckProfileLimit`方法通过调用`repository.CountProfilesByUserID`获取用户当前的档案数量,并与传入的`maxProfiles`上限进行比较。这个检查通常在`CreateProfile`方法的开头执行,以防止用户创建过多的档案。
|
||||
|
||||
**材质上传流程**:
|
||||
`TextureService.CreateTexture`方法实现了严格的上传流程:
|
||||
1. **用户验证**:确保上传者是有效的用户。
|
||||
2. **去重检查**:通过`repository.FindTextureByHash`检查材质的Hash值,避免存储重复内容。
|
||||
3. **类型验证**:使用`switch`语句确保`textureType`是有效的("SKIN"或"CAPE")。
|
||||
4. **数据创建**:构建`model.Texture`对象并调用`repository.CreateTexture`进行持久化。
|
||||
|
||||
**活跃档案管理**:
|
||||
`ProfileService.SetActiveProfile`是实现“一个用户只能有一个活跃档案”这一业务规则的核心。它利用GORM的事务功能,在一个原子操作中完成两步:
|
||||
1. 将用户所有档案的`is_active`字段更新为`false`。
|
||||
2. 将指定档案的`is_active`字段更新为`true`。
|
||||
这确保了即使在高并发场景下,也不会出现多个档案同时为活跃状态的情况。
|
||||
|
||||
## 错误处理与日志记录
|
||||
|
||||
服务层实现了细粒度的错误处理。每个方法都会返回一个`error`类型的值,用于向调用者传递错误信息。错误通常使用`fmt.Errorf`进行包装,以提供上下文信息,例如`fmt.Errorf("查询用户失败: %w", err)`。
|
||||
|
||||
此外,服务层还直接负责记录关键操作日志:
|
||||
- `UserService`中的`logSuccessLogin`和`logFailedLogin`方法会创建`UserLoginLog`记录,用于审计登录行为。
|
||||
- `TextureService`中的`RecordTextureDownload`方法会创建`TextureDownloadLog`记录,用于追踪下载量。
|
||||
|
||||
这些日志记录通过调用相应的Repository方法(如`repository.CreateLoginLog`)来持久化,为系统监控和问题排查提供了重要依据。
|
||||
|
||||
**本节来源**
|
||||
- [user_service.go](file://internal/service/user_service.go#L203-L226)
|
||||
- [texture_service.go](file://internal/service/texture_service.go#L162-L187)
|
||||
- [user_repository.go](file://internal/repository/user_repository.go#L77-L81)
|
||||
- [texture_repository.go](file://internal/repository/texture_repository.go#L153-L157)
|
||||
|
||||
## 结论
|
||||
|
||||
CarrotSkin项目的服务层设计清晰、职责分明,有效地封装了核心业务逻辑。`UserService`、`ProfileService`和`TextureService`三个核心服务各司其职,通过调用统一的Repository层来访问数据,实现了业务逻辑与数据持久化的解耦。服务层成功实现了档案数量限制、材质上传去重、活跃档案管理等复杂业务规则,并通过事务和原子操作保证了数据的一致性。其良好的错误处理和日志记录机制为系统的稳定运行和维护提供了保障。该架构为未来的功能扩展(如积分系统、审核流程)奠定了坚实的基础。
|
||||
462
.qoder/repowiki/zh/content/服务架构/材质服务.md
Normal file
462
.qoder/repowiki/zh/content/服务架构/材质服务.md
Normal file
@@ -0,0 +1,462 @@
|
||||
# 材质服务
|
||||
|
||||
<cite>
|
||||
**本文引用的文件**
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go)
|
||||
- [pkg/database/manager.go](file://pkg/database/manager.go)
|
||||
- [internal/service/texture_service_test.go](file://internal/service/texture_service_test.go)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
2. [项目结构](#项目结构)
|
||||
3. [核心组件](#核心组件)
|
||||
4. [架构总览](#架构总览)
|
||||
5. [详细组件分析](#详细组件分析)
|
||||
6. [依赖关系分析](#依赖关系分析)
|
||||
7. [性能考虑](#性能考虑)
|
||||
8. [故障排查指南](#故障排查指南)
|
||||
9. [结论](#结论)
|
||||
10. [附录](#附录)
|
||||
|
||||
## 简介
|
||||
本文件面向开发者,系统性梳理材质服务(TextureService)的职责、方法与内部逻辑,重点覆盖:
|
||||
- 材质类型(SKIN/CAPE)校验与默认值设定(状态、下载数、收藏数)
|
||||
- 材质状态管理(正常、禁用、删除)
|
||||
- 上传、搜索、收藏等业务规则及服务层与仓储层的协作
|
||||
- 状态验证逻辑(状态为-1表示已删除,无效)
|
||||
- 常见问题与错误处理策略
|
||||
- 性能优化建议(如高效查询热门材质)
|
||||
|
||||
## 项目结构
|
||||
围绕材质服务的关键文件组织如下:
|
||||
- 服务层:负责业务规则编排与跨仓储调用
|
||||
- 仓储层:封装数据库访问与聚合查询
|
||||
- 模型层:定义材质实体、收藏关联与下载日志
|
||||
- 数据库管理:统一数据库连接与自动迁移
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "服务层"
|
||||
S1["TextureService<br/>internal/service/texture_service.go"]
|
||||
end
|
||||
subgraph "仓储层"
|
||||
R1["TextureRepository<br/>internal/repository/texture_repository.go"]
|
||||
end
|
||||
subgraph "模型层"
|
||||
M1["Texture<br/>internal/model/texture.go"]
|
||||
M2["UserTextureFavorite<br/>internal/model/texture.go"]
|
||||
M3["TextureDownloadLog<br/>internal/model/texture.go"]
|
||||
end
|
||||
subgraph "基础设施"
|
||||
D1["数据库管理器<br/>pkg/database/manager.go"]
|
||||
end
|
||||
S1 --> R1
|
||||
R1 --> D1
|
||||
S1 --> M1
|
||||
S1 --> M2
|
||||
S1 --> M3
|
||||
R1 --> M1
|
||||
R1 --> M2
|
||||
R1 --> M3
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
- [pkg/database/manager.go](file://pkg/database/manager.go#L1-L114)
|
||||
|
||||
章节来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
- [pkg/database/manager.go](file://pkg/database/manager.go#L1-L114)
|
||||
|
||||
## 核心组件
|
||||
- TextureService:提供材质上传、查询、更新、删除、下载统计、收藏切换、收藏列表、上传数量限制检查等能力
|
||||
- TextureRepository:封装材质的增删改查、分页、统计、收藏与下载日志写入等仓储操作
|
||||
- Model.Texture:材质实体,包含类型、状态、公开度、下载/收藏计数、Slim标记等
|
||||
- Model.UserTextureFavorite:用户-材质收藏关联
|
||||
- Model.TextureDownloadLog:材质下载日志
|
||||
- Database Manager:数据库连接与自动迁移
|
||||
|
||||
章节来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
- [pkg/database/manager.go](file://pkg/database/manager.go#L1-L114)
|
||||
|
||||
## 架构总览
|
||||
服务层通过统一的 GORM 数据库实例访问仓储层,仓储层对数据库执行具体操作;模型层定义实体与索引约束,确保查询与统计效率。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as "调用方"
|
||||
participant S as "TextureService"
|
||||
participant R as "TextureRepository"
|
||||
participant DB as "GORM DB"
|
||||
participant M as "Model"
|
||||
C->>S : 调用上传/搜索/收藏等方法
|
||||
S->>R : 调用仓储方法查询/更新/写入
|
||||
R->>DB : 执行数据库操作
|
||||
DB-->>R : 返回结果/影响行数
|
||||
R-->>S : 返回实体/列表/统计
|
||||
S-->>C : 返回业务结果
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
- [pkg/database/manager.go](file://pkg/database/manager.go#L1-L114)
|
||||
|
||||
## 详细组件分析
|
||||
|
||||
### TextureService 方法与职责
|
||||
- 创建材质
|
||||
- 校验上传者存在性
|
||||
- 基于哈希去重
|
||||
- 类型转换与校验(仅支持 SKIN/CAPE)
|
||||
- 默认状态为“正常”,默认下载数与收藏数为 0
|
||||
- 写入仓储并返回实体
|
||||
- 获取材质详情
|
||||
- 查询材质并校验状态(状态为-1视为已删除)
|
||||
- 用户上传列表
|
||||
- 分页查询,排除已删除状态
|
||||
- 搜索材质
|
||||
- 支持公开筛选、类型筛选、关键词模糊匹配
|
||||
- 分页查询,按创建时间倒序
|
||||
- 更新材质
|
||||
- 权限校验(仅上传者可更新)
|
||||
- 动态更新名称/描述/公开状态
|
||||
- 删除材质
|
||||
- 权限校验(仅上传者可删除)
|
||||
- 采用软删除(状态置为-1)
|
||||
- 记录下载
|
||||
- 校验材质存在性
|
||||
- 原子递增下载计数
|
||||
- 写入下载日志
|
||||
- 切换收藏
|
||||
- 校验材质存在性
|
||||
- 已收藏则取消并递减收藏计数;未收藏则添加并递增收藏计数
|
||||
- 用户收藏列表
|
||||
- 分页查询收藏的材质(排除已删除)
|
||||
- 上传数量限制检查
|
||||
- 统计用户未删除材质数量并与上限比较
|
||||
|
||||
章节来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
|
||||
### 材质类型与默认值
|
||||
- 类型枚举
|
||||
- SKIN、CAPE 两种类型
|
||||
- 默认值
|
||||
- 状态:1(正常)
|
||||
- 下载数:0
|
||||
- 收藏数:0
|
||||
- Slim 标记:用于区分 Alex/Steve 角色皮肤样式
|
||||
|
||||
章节来源
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
|
||||
### 状态管理与验证
|
||||
- 状态含义
|
||||
- 1:正常
|
||||
- 0:审核中/禁用(仍存在,但不可用)
|
||||
- -1:已删除(软删除)
|
||||
- 验证规则
|
||||
- 获取详情时若状态为-1,判定为“已删除”
|
||||
- 搜索与收藏列表均排除状态为-1的材质
|
||||
- 删除操作采用软删除,不物理删除
|
||||
|
||||
章节来源
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
|
||||
### 上传、搜索与收藏业务规则
|
||||
- 上传
|
||||
- 哈希唯一性约束,避免重复
|
||||
- 类型严格校验
|
||||
- 默认状态与计数初始化
|
||||
- 搜索
|
||||
- 公开筛选、类型筛选、关键词模糊匹配
|
||||
- 分页与排序(按创建时间倒序)
|
||||
- 收藏
|
||||
- 基于用户-材质关联表维护收藏
|
||||
- 切换收藏时原子更新计数
|
||||
|
||||
章节来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
|
||||
### 服务层与仓储层协作
|
||||
- 服务层负责:
|
||||
- 参数校验与业务规则
|
||||
- 权限校验(上传者)
|
||||
- 组合仓储调用以完成复杂流程(如收藏切换)
|
||||
- 仓储层负责:
|
||||
- 数据库 CRUD、分页、统计、索引使用
|
||||
- 原子计数更新(下载/收藏)
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class TextureService {
|
||||
+CreateTexture(...)
|
||||
+GetTextureByID(...)
|
||||
+GetUserTextures(...)
|
||||
+SearchTextures(...)
|
||||
+UpdateTexture(...)
|
||||
+DeleteTexture(...)
|
||||
+RecordTextureDownload(...)
|
||||
+ToggleTextureFavorite(...)
|
||||
+GetUserTextureFavorites(...)
|
||||
+CheckTextureUploadLimit(...)
|
||||
}
|
||||
class TextureRepository {
|
||||
+CreateTexture(...)
|
||||
+FindTextureByID(...)
|
||||
+FindTextureByHash(...)
|
||||
+FindTexturesByUploaderID(...)
|
||||
+SearchTextures(...)
|
||||
+UpdateTexture(...)
|
||||
+UpdateTextureFields(...)
|
||||
+DeleteTexture(...)
|
||||
+IncrementTextureDownloadCount(...)
|
||||
+IncrementTextureFavoriteCount(...)
|
||||
+DecrementTextureFavoriteCount(...)
|
||||
+CreateTextureDownloadLog(...)
|
||||
+IsTextureFavorited(...)
|
||||
+AddTextureFavorite(...)
|
||||
+RemoveTextureFavorite(...)
|
||||
+GetUserTextureFavorites(...)
|
||||
+CountTexturesByUploaderID(...)
|
||||
}
|
||||
class Texture
|
||||
class UserTextureFavorite
|
||||
class TextureDownloadLog
|
||||
TextureService --> TextureRepository : "调用"
|
||||
TextureRepository --> Texture : "读写"
|
||||
TextureRepository --> UserTextureFavorite : "读写"
|
||||
TextureRepository --> TextureDownloadLog : "读写"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
|
||||
### 关键流程图示
|
||||
|
||||
#### 获取材质详情流程
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start(["进入 GetTextureByID"]) --> Load["仓储查询材质"]
|
||||
Load --> Found{"找到记录?"}
|
||||
Found --> |否| NotFound["返回不存在错误"]
|
||||
Found --> |是| CheckStatus["检查状态"]
|
||||
CheckStatus --> Deleted{"状态为-1?"}
|
||||
Deleted --> |是| DelErr["返回已删除错误"]
|
||||
Deleted --> |否| Return["返回材质对象"]
|
||||
NotFound --> End(["结束"])
|
||||
DelErr --> End
|
||||
Return --> End
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L66-L79)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L16-L27)
|
||||
|
||||
#### 切换收藏流程
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start(["进入 ToggleTextureFavorite"]) --> Load["仓储查询材质"]
|
||||
Load --> Found{"找到记录?"}
|
||||
Found --> |否| Err["返回不存在错误"]
|
||||
Found --> |是| CheckFav["检查是否已收藏"]
|
||||
CheckFav --> IsFav{"已收藏?"}
|
||||
IsFav --> |是| Unfav["删除收藏记录"]
|
||||
Unfav --> Dec["递减收藏计数"]
|
||||
IsFav --> |否| Fav["新增收藏记录"]
|
||||
Fav --> Inc["递增收藏计数"]
|
||||
Dec --> Done["返回false"]
|
||||
Inc --> Done2["返回true"]
|
||||
Err --> End(["结束"])
|
||||
Done --> End
|
||||
Done2 --> End
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L189-L225)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L159-L187)
|
||||
|
||||
#### 上传流程(含类型与去重校验)
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start(["进入 CreateTexture"]) --> CheckUser["校验上传者存在"]
|
||||
CheckUser --> Exists{"存在?"}
|
||||
Exists --> |否| UErr["返回用户不存在错误"]
|
||||
Exists --> |是| CheckHash["按哈希查询材质"]
|
||||
CheckHash --> Dup{"已存在?"}
|
||||
Dup --> |是| DupErr["返回材质已存在错误"]
|
||||
Dup --> |否| ParseType["解析类型(SKIN/CAPE)"]
|
||||
ParseType --> Valid{"类型有效?"}
|
||||
Valid --> |否| TErr["返回无效类型错误"]
|
||||
Valid --> |是| Build["构建材质对象(默认状态/计数)"]
|
||||
Build --> Save["仓储保存材质"]
|
||||
Save --> Ok["返回材质对象"]
|
||||
UErr --> End(["结束"])
|
||||
DupErr --> End
|
||||
TErr --> End
|
||||
Ok --> End
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L12-L64)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L29-L41)
|
||||
|
||||
## 依赖关系分析
|
||||
- 服务层依赖仓储层接口,仓储层依赖数据库管理器提供的全局 GORM 实例
|
||||
- 模型层定义了材质、收藏、下载日志三类实体,并带有索引与排序提示
|
||||
- 数据库管理器负责初始化与迁移,确保表结构与模型一致
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
TS["TextureService"] --> TR["TextureRepository"]
|
||||
TR --> DM["Database Manager"]
|
||||
TR --> M["Models(Texture/UserTextureFavorite/TextureDownloadLog)"]
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
- [pkg/database/manager.go](file://pkg/database/manager.go#L1-L114)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
|
||||
章节来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L1-L252)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L1-L232)
|
||||
- [pkg/database/manager.go](file://pkg/database/manager.go#L1-L114)
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
|
||||
## 性能考虑
|
||||
- 索引与排序
|
||||
- 材质表对公开状态、类型、状态组合建立索引,有利于搜索过滤
|
||||
- 下载数与收藏数列带索引且按降序排序,便于热门查询
|
||||
- 原子计数更新
|
||||
- 下载与收藏计数采用原生表达式递增/递减,减少锁竞争与往返
|
||||
- 分页与预加载
|
||||
- 分页查询时使用偏移与限制,结合预加载提升列表渲染效率
|
||||
- 热门查询建议
|
||||
- 使用下载数/收藏数列的降序索引进行 Top-N 查询
|
||||
- 对搜索场景,优先利用公开状态与类型过滤,缩小扫描范围
|
||||
- 缓存策略(建议)
|
||||
- 对热点材质详情与热门榜单可引入缓存层,降低数据库压力
|
||||
- 结合 TTL 与失效策略,保证数据一致性
|
||||
|
||||
章节来源
|
||||
- [internal/model/texture.go](file://internal/model/texture.go#L1-L77)
|
||||
- [internal/repository/texture_repository.go](file://internal/repository/texture_repository.go#L132-L151)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L93-L103)
|
||||
|
||||
## 故障排查指南
|
||||
- 无效类型
|
||||
- 现象:上传时返回“无效的材质类型”
|
||||
- 排查:确认传入类型为 SKIN 或 CAPE
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L32-L41)
|
||||
- 材质已存在
|
||||
- 现象:上传返回“该材质已存在”
|
||||
- 排查:确认哈希是否重复;检查去重逻辑
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L23-L31)
|
||||
- 用户不存在
|
||||
- 现象:上传返回“用户不存在”
|
||||
- 排查:确认上传者ID正确;检查用户是否存在
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L14-L21)
|
||||
- 材质不存在或已删除
|
||||
- 现象:获取详情/收藏/下载时返回“材质不存在”或“材质已删除”
|
||||
- 排查:确认ID正确;检查状态是否为-1
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L66-L79)
|
||||
- 权限不足
|
||||
- 现象:更新/删除返回“无权修改/无权删除”
|
||||
- 排查:确认请求者为上传者
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L106-L141)
|
||||
- 上传数量超限
|
||||
- 现象:检查上传限制返回已达上限
|
||||
- 排查:确认当前未删除材质数量与上限配置
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L239-L251)
|
||||
|
||||
章节来源
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L12-L64)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L66-L79)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L106-L141)
|
||||
- [internal/service/texture_service.go](file://internal/service/texture_service.go#L239-L251)
|
||||
|
||||
## 结论
|
||||
TextureService 将业务规则与仓储操作解耦,通过严格的类型与状态校验、完善的权限控制与原子计数更新,保障了材质上传、搜索、收藏与统计的可靠性。配合模型层的索引设计与服务层的分页策略,可在高并发场景下保持良好性能。建议在热点场景引入缓存与更精细的索引策略,持续优化查询与写入性能。
|
||||
|
||||
## 附录
|
||||
|
||||
### API 一览(方法与关键行为)
|
||||
- CreateTexture
|
||||
- 输入:上传者ID、名称、描述、类型、URL、哈希、大小、公开/Slim、默认状态与计数
|
||||
- 行为:校验用户与哈希唯一性,类型转换,创建并返回
|
||||
- 错误:用户不存在、哈希重复、无效类型
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L12-L64)
|
||||
- GetTextureByID
|
||||
- 输入:材质ID
|
||||
- 行为:查询并校验状态(-1视为已删除)
|
||||
- 错误:不存在、已删除
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L66-L79)
|
||||
- GetUserTextures
|
||||
- 输入:上传者ID、分页参数
|
||||
- 行为:分页查询用户上传材质(排除已删除)
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L81-L91)
|
||||
- SearchTextures
|
||||
- 输入:关键词、类型、公开筛选、分页参数
|
||||
- 行为:按条件过滤并分页,按创建时间倒序
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L93-L103)
|
||||
- UpdateTexture
|
||||
- 输入:材质ID、上传者ID、名称/描述/公开状态
|
||||
- 行为:权限校验后动态更新字段
|
||||
- 错误:不存在、权限不足
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L106-L141)
|
||||
- DeleteTexture
|
||||
- 输入:材质ID、上传者ID
|
||||
- 行为:权限校验后软删除(状态=-1)
|
||||
- 错误:不存在、权限不足
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L144-L160)
|
||||
- RecordTextureDownload
|
||||
- 输入:材质ID、用户ID/IP/UA
|
||||
- 行为:原子递增下载计数并写入日志
|
||||
- 错误:不存在
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L162-L187)
|
||||
- ToggleTextureFavorite
|
||||
- 输入:用户ID、材质ID
|
||||
- 行为:切换收藏并原子更新计数
|
||||
- 错误:不存在
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L189-L225)
|
||||
- GetUserTextureFavorites
|
||||
- 输入:用户ID、分页参数
|
||||
- 行为:分页查询收藏的材质(排除已删除)
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L227-L237)
|
||||
- CheckTextureUploadLimit
|
||||
- 输入:上传者ID、最大数量
|
||||
- 行为:统计未删除材质数量并与上限比较
|
||||
- 错误:达到上限
|
||||
- 参考路径:[internal/service/texture_service.go](file://internal/service/texture_service.go#L239-L251)
|
||||
|
||||
### 状态与类型验证(测试参考)
|
||||
- 类型验证:仅接受 SKIN/CAPE
|
||||
- 参考路径:[internal/service/texture_service_test.go](file://internal/service/texture_service_test.go#L7-L44)
|
||||
- 默认值验证:状态=1,下载数=0,收藏数=0
|
||||
- 参考路径:[internal/service/texture_service_test.go](file://internal/service/texture_service_test.go#L46-L65)
|
||||
- 状态验证:状态=-1 无效
|
||||
- 参考路径:[internal/service/texture_service_test.go](file://internal/service/texture_service_test.go#L67-L99)
|
||||
- 分页参数校验:page≥1,1≤pageSize≤100
|
||||
- 参考路径:[internal/service/texture_service_test.go](file://internal/service/texture_service_test.go#L102-L161)
|
||||
- 参考路径:[internal/service/texture_service_test.go](file://internal/service/texture_service_test.go#L163-L215)
|
||||
- 参考路径:[internal/service/texture_service_test.go](file://internal/service/texture_service_test.go#L376-L428)
|
||||
365
.qoder/repowiki/zh/content/服务架构/档案服务.md
Normal file
365
.qoder/repowiki/zh/content/服务架构/档案服务.md
Normal file
@@ -0,0 +1,365 @@
|
||||
# 档案服务
|
||||
|
||||
<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)、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: 已删除)
|
||||
|
||||
```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)
|
||||
372
.qoder/repowiki/zh/content/服务架构/用户服务.md
Normal file
372
.qoder/repowiki/zh/content/服务架构/用户服务.md
Normal file
@@ -0,0 +1,372 @@
|
||||
# 用户服务
|
||||
|
||||
<cite>
|
||||
**本文引用的文件列表**
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go)
|
||||
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go)
|
||||
- [internal/model/user.go](file://internal/model/user.go)
|
||||
- [internal/handler/auth_handler.go](file://internal/handler/auth_handler.go)
|
||||
- [pkg/auth/password.go](file://pkg/auth/password.go)
|
||||
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go)
|
||||
- [internal/types/common.go](file://internal/types/common.go)
|
||||
- [internal/service/common.go](file://internal/service/common.go)
|
||||
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go)
|
||||
- [internal/model/system_config.go](file://internal/model/system_config.go)
|
||||
- [internal/service/user_service_test.go](file://internal/service/user_service_test.go)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
2. [项目结构](#项目结构)
|
||||
3. [核心组件](#核心组件)
|
||||
4. [架构总览](#架构总览)
|
||||
5. [详细组件分析](#详细组件分析)
|
||||
6. [依赖分析](#依赖分析)
|
||||
7. [性能考虑](#性能考虑)
|
||||
8. [故障排查指南](#故障排查指南)
|
||||
9. [结论](#结论)
|
||||
10. [附录](#附录)
|
||||
|
||||
## 简介
|
||||
本文件聚焦于用户服务(UserService)的职责、方法与内部逻辑,覆盖用户注册、登录、信息更新等核心功能,并对默认角色、状态与积分的初始化进行说明;同时解释头像选择策略(优先自定义头像,否则使用默认头像)以及数据验证规则(用户名、邮箱、密码非空且邮箱格式正确)。文档还提供服务层与仓储层的调用关系示例,帮助初学者理解业务流程,并为有经验的开发者提供扩展点建议(如修改默认角色、添加新的用户属性)。
|
||||
|
||||
## 项目结构
|
||||
用户服务位于 internal/service 层,围绕 internal/model 定义的数据模型工作,通过 internal/repository 与数据库交互,对外由 internal/handler 提供 HTTP 接口。认证相关的密码处理与 JWT 令牌生成分别由 pkg/auth 下的 password 与 jwt 组件完成。
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "接口层"
|
||||
H["auth_handler<br/>HTTP接口"]
|
||||
end
|
||||
subgraph "服务层"
|
||||
S["user_service<br/>用户业务逻辑"]
|
||||
end
|
||||
subgraph "模型层"
|
||||
M["user.go<br/>User/UserLoginLog/UserPointLog"]
|
||||
SCM["system_config.go<br/>SystemConfig"]
|
||||
end
|
||||
subgraph "仓储层"
|
||||
R["user_repository<br/>CRUD/事务"]
|
||||
SCR["system_config_repository<br/>系统配置查询"]
|
||||
end
|
||||
subgraph "认证与工具"
|
||||
P["password.go<br/>密码加解密"]
|
||||
J["jwt.go<br/>JWT签发/校验"]
|
||||
T["types/common.go<br/>请求/响应结构"]
|
||||
end
|
||||
H --> S
|
||||
S --> R
|
||||
S --> SCR
|
||||
S --> P
|
||||
S --> J
|
||||
S --> M
|
||||
S --> SCM
|
||||
T --> H
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/handler/auth_handler.go](file://internal/handler/auth_handler.go#L1-L250)
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
|
||||
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
|
||||
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)
|
||||
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
|
||||
- [internal/model/system_config.go](file://internal/model/system_config.go#L1-L42)
|
||||
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
|
||||
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go#L1-L71)
|
||||
- [internal/types/common.go](file://internal/types/common.go#L1-L215)
|
||||
|
||||
章节来源
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
|
||||
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
|
||||
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
|
||||
- [internal/handler/auth_handler.go](file://internal/handler/auth_handler.go#L1-L250)
|
||||
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
|
||||
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go#L1-L71)
|
||||
- [internal/types/common.go](file://internal/types/common.go#L1-L215)
|
||||
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)
|
||||
- [internal/model/system_config.go](file://internal/model/system_config.go#L1-L42)
|
||||
|
||||
## 核心组件
|
||||
- 用户模型(User):包含用户名、邮箱、头像、积分、角色、状态、最后登录时间等字段,以及唯一索引约束与默认值。
|
||||
- 用户登录日志(UserLoginLog):记录登录尝试的IP、UA、是否成功及失败原因。
|
||||
- 用户积分日志(UserPointLog):记录积分变动明细(类型、金额、前后余额、原因等)。
|
||||
- 系统配置(SystemConfig):键值型配置,用于存储默认头像等全局设置。
|
||||
- 服务层(UserService):封装注册、登录、信息更新、头像更新、密码修改/重置、邮箱变更等业务逻辑。
|
||||
- 仓储层(UserRepository):提供用户与登录/积分日志的数据库操作,包含事务性积分更新。
|
||||
- 认证工具(Password/JWT):密码加密与校验、JWT签发与校验。
|
||||
- 接口层(AuthHandler):接收HTTP请求,绑定参数,调用服务层并返回统一响应。
|
||||
|
||||
章节来源
|
||||
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
|
||||
- [internal/model/system_config.go](file://internal/model/system_config.go#L1-L42)
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
|
||||
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
|
||||
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
|
||||
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go#L1-L71)
|
||||
- [internal/handler/auth_handler.go](file://internal/handler/auth_handler.go#L1-L250)
|
||||
|
||||
## 架构总览
|
||||
下图展示从HTTP请求到服务层再到仓储层的整体调用链路,以及关键对象之间的关系。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as "客户端"
|
||||
participant H as "AuthHandler"
|
||||
participant S as "UserService"
|
||||
participant P as "Password"
|
||||
participant J as "JWTService"
|
||||
participant R as "UserRepository"
|
||||
participant SCR as "SystemConfigRepository"
|
||||
C->>H : "POST /api/v1/auth/register"
|
||||
H->>H : "绑定RegisterRequest并校验"
|
||||
H->>S : "RegisterUser(jwtService, username, password, email, avatar)"
|
||||
S->>R : "FindUserByUsername(username)"
|
||||
S->>R : "FindUserByEmail(email)"
|
||||
S->>P : "HashPassword(password)"
|
||||
S->>SCR : "GetSystemConfigByKey('default_avatar')"
|
||||
S->>R : "CreateUser(user)"
|
||||
S->>J : "GenerateToken(user.id, user.username, user.role)"
|
||||
S-->>H : "返回user, token"
|
||||
H-->>C : "返回LoginResponse"
|
||||
C->>H : "POST /api/v1/auth/login"
|
||||
H->>S : "LoginUser(jwtService, usernameOrEmail, password, ip, ua)"
|
||||
S->>R : "按用户名或邮箱查找用户"
|
||||
S->>S : "检查用户状态"
|
||||
S->>P : "CheckPassword(user.password, password)"
|
||||
S->>R : "UpdateUserFields(last_login_at)"
|
||||
S->>J : "GenerateToken(...)"
|
||||
S-->>H : "返回user, token"
|
||||
H-->>C : "返回LoginResponse"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/handler/auth_handler.go](file://internal/handler/auth_handler.go#L1-L250)
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
|
||||
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
|
||||
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go#L1-L71)
|
||||
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
|
||||
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)
|
||||
|
||||
## 详细组件分析
|
||||
|
||||
### 用户服务(UserService)职责与方法
|
||||
- 注册(RegisterUser)
|
||||
- 校验用户名与邮箱唯一性
|
||||
- 密码加密
|
||||
- 头像选择:若传入自定义头像则使用,否则从系统配置读取默认头像
|
||||
- 初始化角色为“user”,状态为1(正常),积分初始为0
|
||||
- 创建用户并生成JWT
|
||||
- 登录(LoginUser)
|
||||
- 支持用户名或邮箱登录(通过是否包含“@”判断)
|
||||
- 校验用户状态(仅允许状态为1的用户登录)
|
||||
- 验证密码
|
||||
- 生成JWT并更新最后登录时间
|
||||
- 记录登录日志(成功/失败)
|
||||
- 查询与更新
|
||||
- 按ID获取用户
|
||||
- 更新用户信息(完整保存)
|
||||
- 更新头像(字段更新)
|
||||
- 修改密码(校验旧密码后加密新密码并更新)
|
||||
- 重置密码(通过邮箱查找用户并更新密码)
|
||||
- 更换邮箱(校验新邮箱唯一性后更新)
|
||||
|
||||
章节来源
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
|
||||
|
||||
### 数据模型与默认值
|
||||
- 用户模型字段与默认值
|
||||
- 角色:默认“user”
|
||||
- 状态:默认1(正常)
|
||||
- 积分:默认0
|
||||
- 头像:默认空字符串
|
||||
- 时间戳:createdAt/updatedAt默认当前时间
|
||||
- 登录日志与积分日志
|
||||
- 登录日志包含IP、UA、登录方式、是否成功、失败原因
|
||||
- 积分日志包含变更类型、金额、前后余额、原因等
|
||||
|
||||
章节来源
|
||||
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
|
||||
|
||||
### 头像逻辑(默认头像策略)
|
||||
- 优先使用用户提供的头像URL
|
||||
- 若未提供,则从系统配置中读取键为“default_avatar”的值作为默认头像
|
||||
- 若系统配置不存在,则返回提示信息字符串
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start(["开始"]) --> CheckProvided["是否提供了头像URL?"]
|
||||
CheckProvided --> |是| UseProvided["使用提供的头像URL"]
|
||||
CheckProvided --> |否| LoadConfig["从系统配置读取default_avatar"]
|
||||
LoadConfig --> Found{"配置是否存在?"}
|
||||
Found --> |是| UseDefault["使用系统配置中的默认头像URL"]
|
||||
Found --> |否| UseError["返回默认头像配置缺失提示"]
|
||||
UseProvided --> End(["结束"])
|
||||
UseDefault --> End
|
||||
UseError --> End
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L228-L240)
|
||||
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L23)
|
||||
- [internal/model/system_config.go](file://internal/model/system_config.go#L1-L42)
|
||||
|
||||
章节来源
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L228-L240)
|
||||
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L23)
|
||||
- [internal/model/system_config.go](file://internal/model/system_config.go#L1-L42)
|
||||
|
||||
### 数据验证规则
|
||||
- 请求体绑定规则(由接口层负责)
|
||||
- 注册:用户名必填且长度在3-50之间,邮箱必填且符合邮箱格式,密码必填且长度6-128,验证码必填且长度6,头像可选且需为合法URL
|
||||
- 登录:用户名必填(支持用户名或邮箱),密码必填且长度6-128
|
||||
- 更新用户:头像可选且为合法URL,修改密码时旧密码与新密码长度要求
|
||||
- 服务层内部验证
|
||||
- 注册时对用户名、邮箱、密码进行基本非空与邮箱格式校验
|
||||
- 登录时对状态进行检查,密码进行校验
|
||||
- 更换邮箱时确保新邮箱未被他人使用
|
||||
|
||||
章节来源
|
||||
- [internal/types/common.go](file://internal/types/common.go#L1-L215)
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
|
||||
- [internal/service/user_service_test.go](file://internal/service/user_service_test.go#L1-L200)
|
||||
|
||||
### 服务层与仓储层调用关系示例
|
||||
- 注册流程
|
||||
- Handler -> UserService.RegisterUser -> UserRepository.FindUserByUsername/ByEmail -> Password.HashPassword -> SystemConfigRepository.GetSystemConfigByKey -> UserRepository.CreateUser -> JWTService.GenerateToken
|
||||
- 登录流程
|
||||
- Handler -> UserService.LoginUser -> UserRepository.FindUserByUsername/ByEmail -> Password.CheckPassword -> UserRepository.UpdateUserFields -> JWTService.GenerateToken
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class AuthHandler {
|
||||
+Register()
|
||||
+Login()
|
||||
}
|
||||
class UserService {
|
||||
+RegisterUser(...)
|
||||
+LoginUser(...)
|
||||
+GetUserByID(...)
|
||||
+UpdateUserInfo(...)
|
||||
+UpdateUserAvatar(...)
|
||||
+ChangeUserPassword(...)
|
||||
+ResetUserPassword(...)
|
||||
+ChangeUserEmail(...)
|
||||
}
|
||||
class UserRepository {
|
||||
+CreateUser(...)
|
||||
+FindUserByID(...)
|
||||
+FindUserByUsername(...)
|
||||
+FindUserByEmail(...)
|
||||
+UpdateUser(...)
|
||||
+UpdateUserFields(...)
|
||||
+CreateLoginLog(...)
|
||||
+UpdateUserPoints(...)
|
||||
}
|
||||
class SystemConfigRepository {
|
||||
+GetSystemConfigByKey(...)
|
||||
}
|
||||
class Password {
|
||||
+HashPassword(...)
|
||||
+CheckPassword(...)
|
||||
}
|
||||
class JWTService {
|
||||
+GenerateToken(...)
|
||||
+ValidateToken(...)
|
||||
}
|
||||
AuthHandler --> UserService : "调用"
|
||||
UserService --> UserRepository : "CRUD/事务"
|
||||
UserService --> SystemConfigRepository : "读取默认头像"
|
||||
UserService --> Password : "密码处理"
|
||||
UserService --> JWTService : "签发Token"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/handler/auth_handler.go](file://internal/handler/auth_handler.go#L1-L250)
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
|
||||
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
|
||||
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)
|
||||
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
|
||||
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go#L1-L71)
|
||||
|
||||
## 依赖分析
|
||||
- 内部依赖
|
||||
- UserService 依赖 UserRepository、SystemConfigRepository、Password、JWTService、User模型、SystemConfig模型
|
||||
- AuthHandler 依赖 UserService、Types(请求/响应)、Logger、Redis(验证码)、Email(发送验证码)
|
||||
- 外部依赖
|
||||
- bcrypt 用于密码加密与校验
|
||||
- golang-jwt/jwt/v5 用于JWT签发与校验
|
||||
- GORM 用于数据库访问与事务
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
H["AuthHandler"] --> S["UserService"]
|
||||
S --> R["UserRepository"]
|
||||
S --> SCR["SystemConfigRepository"]
|
||||
S --> P["Password(bcrypt)"]
|
||||
S --> J["JWTService(gojwt)"]
|
||||
S --> M["User/SystemConfig Models"]
|
||||
R --> DB["GORM/DB"]
|
||||
SCR --> DB
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [internal/handler/auth_handler.go](file://internal/handler/auth_handler.go#L1-L250)
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
|
||||
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
|
||||
- [internal/repository/system_config_repository.go](file://internal/repository/system_config_repository.go#L1-L58)
|
||||
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
|
||||
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go#L1-L71)
|
||||
|
||||
章节来源
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
|
||||
- [internal/repository/user_repository.go](file://internal/repository/user_repository.go#L1-L137)
|
||||
- [pkg/auth/password.go](file://pkg/auth/password.go#L1-L21)
|
||||
- [pkg/auth/jwt.go](file://pkg/auth/jwt.go#L1-L71)
|
||||
|
||||
## 性能考虑
|
||||
- 密码加密成本:bcrypt 默认成本较高,建议在高并发场景下关注注册/登录的延迟,必要时评估成本参数与异步化策略
|
||||
- 数据库索引:用户名与邮箱字段具备唯一索引,有助于快速去重;登录日志与积分日志按时间建立索引,有利于查询与统计
|
||||
- 事务一致性:积分变更采用事务,避免并发写入导致的余额不一致
|
||||
- 缓存策略:默认头像配置可考虑缓存以减少数据库查询次数
|
||||
|
||||
## 故障排查指南
|
||||
- 注册失败
|
||||
- 检查用户名/邮箱是否重复
|
||||
- 确认密码加密是否成功
|
||||
- 检查系统配置中是否存在“default_avatar”
|
||||
- 登录失败
|
||||
- 用户不存在或状态非1
|
||||
- 密码校验失败
|
||||
- 检查IP与User-Agent是否正确传入
|
||||
- 头像显示异常
|
||||
- 自定义头像URL是否有效
|
||||
- 系统默认头像配置是否正确
|
||||
- 集成测试参考
|
||||
- 单元测试覆盖了默认头像逻辑、邮箱检测、常量与验证逻辑,可据此定位问题
|
||||
|
||||
章节来源
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
|
||||
- [internal/service/user_service_test.go](file://internal/service/user_service_test.go#L1-L200)
|
||||
|
||||
## 结论
|
||||
UserService 以清晰的职责划分实现了用户生命周期管理:从注册、登录到信息维护与安全控制。通过明确的默认值与验证规则,结合仓储层的事务与模型层的结构化设计,系统在保证安全性的同时具备良好的可扩展性。对于扩展需求,可在服务层增加新的默认值或属性映射,并在仓储层补充相应的查询/更新逻辑。
|
||||
|
||||
## 附录
|
||||
|
||||
### 扩展点指导
|
||||
- 修改默认角色
|
||||
- 在注册流程中调整角色初始化值
|
||||
- 若需动态分配角色,可引入系统配置或权限策略模块
|
||||
- 添加新的用户属性
|
||||
- 在 User 模型中新增字段并设置默认值
|
||||
- 在注册流程中初始化该字段
|
||||
- 在接口层的请求/响应结构中同步新增字段
|
||||
- 默认积分初始化
|
||||
- 当前初始积分为0,可通过系统配置读取或硬编码调整
|
||||
- 注册奖励积分可作为后续增强点(服务层已有TODO标记)
|
||||
|
||||
章节来源
|
||||
- [internal/model/user.go](file://internal/model/user.go#L1-L71)
|
||||
- [internal/service/user_service.go](file://internal/service/user_service.go#L1-L249)
|
||||
- [internal/types/common.go](file://internal/types/common.go#L1-L215)
|
||||
- [internal/service/common.go](file://internal/service/common.go#L1-L14)
|
||||
Reference in New Issue
Block a user