16 KiB
服务架构
**本文档引用文件** - [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)目录
简介
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层交互,确保了数据访问的抽象化。
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 : "依赖"
图示来源
本节来源
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操作的原子性。
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 : "调用"
图示来源
本节来源
TextureService 分析
TextureService负责管理用户上传的皮肤(Skin)和披风(Cape)材质。
核心方法与职责:
CreateTexture:创建新的材质记录。方法会验证上传者用户的存在性,并检查材质的Hash值是否已存在(防止重复上传)。它通过switch语句将字符串类型的textureType转换为model.TextureType枚举值。创建成功后,材质记录会被持久化。SearchTextures和GetUserTextures:提供按关键词、类型、公开性等条件搜索材质,以及获取特定用户上传的材质列表的功能。UpdateTexture:允许上传者更新材质的名称、描述和公开状态。更新时会检查权限,确保只有上传者才能修改。DeleteTexture:允许上传者删除自己的材质。RecordTextureDownload:记录一次材质下载行为。该方法会增加材质的下载计数,并创建一条下载日志。ToggleTextureFavorite:切换用户对某个材质的收藏状态。如果已收藏则取消收藏并减少收藏计数,反之则添加收藏并增加收藏计数。CheckTextureUploadLimit:检查用户上传的材质数量是否达到了系统设定的上限。
该服务通过repository.FindTextureByHash、repository.CreateTexture等方法与Repository层交互,并通过IncrementTextureDownloadCount和DecrementTextureFavoriteCount等原子操作来安全地更新计数。
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 : "调用"
图示来源
本节来源
服务调用关系与流程
服务层的调用流程通常始于handler层的HTTP请求处理器。例如,当用户发起注册请求时,auth_handler.go中的处理器会调用UserService.RegisterUser方法。该方法内部会协调多个Repository操作(检查用户、创建用户),并依赖pkg/auth包进行密码加密和Token生成。
服务之间通常是独立的,但它们都依赖于Repository层来访问数据。例如,ProfileService.CreateProfile在创建档案时,会先调用repository.FindUserByID来验证用户,这与UserService使用的Repository是同一个,体现了数据访问层的共享性。
一个典型的跨服务流程是用户上传材质并将其设置为档案皮肤:
TextureService.CreateTexture被调用,创建材质记录。ProfileService.UpdateProfile被调用,传入新创建的材质ID作为skinID参数,更新档案的皮肤引用。
这种设计保证了服务的单一职责,同时通过Repository层实现了数据的统一管理。
业务规则实现
服务层是复杂业务规则的主要实现者。
档案数量限制:
ProfileService.CheckProfileLimit方法通过调用repository.CountProfilesByUserID获取用户当前的档案数量,并与传入的maxProfiles上限进行比较。这个检查通常在CreateProfile方法的开头执行,以防止用户创建过多的档案。
材质上传流程:
TextureService.CreateTexture方法实现了严格的上传流程:
- 用户验证:确保上传者是有效的用户。
- 去重检查:通过
repository.FindTextureByHash检查材质的Hash值,避免存储重复内容。 - 类型验证:使用
switch语句确保textureType是有效的("SKIN"或"CAPE")。 - 数据创建:构建
model.Texture对象并调用repository.CreateTexture进行持久化。
活跃档案管理:
ProfileService.SetActiveProfile是实现“一个用户只能有一个活跃档案”这一业务规则的核心。它利用GORM的事务功能,在一个原子操作中完成两步:
- 将用户所有档案的
is_active字段更新为false。 - 将指定档案的
is_active字段更新为true。 这确保了即使在高并发场景下,也不会出现多个档案同时为活跃状态的情况。
错误处理与日志记录
服务层实现了细粒度的错误处理。每个方法都会返回一个error类型的值,用于向调用者传递错误信息。错误通常使用fmt.Errorf进行包装,以提供上下文信息,例如fmt.Errorf("查询用户失败: %w", err)。
此外,服务层还直接负责记录关键操作日志:
UserService中的logSuccessLogin和logFailedLogin方法会创建UserLoginLog记录,用于审计登录行为。TextureService中的RecordTextureDownload方法会创建TextureDownloadLog记录,用于追踪下载量。
这些日志记录通过调用相应的Repository方法(如repository.CreateLoginLog)来持久化,为系统监控和问题排查提供了重要依据。
本节来源
结论
CarrotSkin项目的服务层设计清晰、职责分明,有效地封装了核心业务逻辑。UserService、ProfileService和TextureService三个核心服务各司其职,通过调用统一的Repository层来访问数据,实现了业务逻辑与数据持久化的解耦。服务层成功实现了档案数量限制、材质上传去重、活跃档案管理等复杂业务规则,并通过事务和原子操作保证了数据的一致性。其良好的错误处理和日志记录机制为系统的稳定运行和维护提供了保障。该架构为未来的功能扩展(如积分系统、审核流程)奠定了坚实的基础。