Initial commit: Basic project structure and dependencies

This commit is contained in:
2025-06-30 19:21:56 +08:00
commit 03e615a8fd
50 changed files with 11664 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
package dto
import (
"time"
"tyapi-server/internal/domains/user/entities"
)
// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
Username string `json:"username" binding:"required,min=3,max=50" example:"john_doe"`
Email string `json:"email" binding:"required,email" example:"john@example.com"`
Password string `json:"password" binding:"required,min=6,max=128" example:"password123"`
FirstName string `json:"first_name" binding:"max=50" example:"John"`
LastName string `json:"last_name" binding:"max=50" example:"Doe"`
Phone string `json:"phone" binding:"omitempty,max=20" example:"+86-13800138000"`
}
// UpdateUserRequest 更新用户请求
type UpdateUserRequest struct {
FirstName *string `json:"first_name,omitempty" binding:"omitempty,max=50" example:"John"`
LastName *string `json:"last_name,omitempty" binding:"omitempty,max=50" example:"Doe"`
Phone *string `json:"phone,omitempty" binding:"omitempty,max=20" example:"+86-13800138000"`
Avatar *string `json:"avatar,omitempty" binding:"omitempty,url" example:"https://example.com/avatar.jpg"`
}
// ChangePasswordRequest 修改密码请求
type ChangePasswordRequest struct {
OldPassword string `json:"old_password" binding:"required" example:"oldpassword123"`
NewPassword string `json:"new_password" binding:"required,min=6,max=128" example:"newpassword123"`
}
// UserResponse 用户响应
type UserResponse struct {
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
Username string `json:"username" example:"john_doe"`
Email string `json:"email" example:"john@example.com"`
FirstName string `json:"first_name" example:"John"`
LastName string `json:"last_name" example:"Doe"`
Phone string `json:"phone" example:"+86-13800138000"`
Avatar string `json:"avatar" example:"https://example.com/avatar.jpg"`
Status entities.UserStatus `json:"status" example:"active"`
LastLoginAt *time.Time `json:"last_login_at,omitempty" example:"2024-01-01T00:00:00Z"`
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
Profile *UserProfileResponse `json:"profile,omitempty"`
}
// UserProfileResponse 用户档案响应
type UserProfileResponse struct {
Bio string `json:"bio,omitempty" example:"Software Developer"`
Location string `json:"location,omitempty" example:"Beijing, China"`
Website string `json:"website,omitempty" example:"https://johndoe.com"`
Birthday *time.Time `json:"birthday,omitempty" example:"1990-01-01T00:00:00Z"`
Gender string `json:"gender,omitempty" example:"male"`
Timezone string `json:"timezone,omitempty" example:"Asia/Shanghai"`
Language string `json:"language,omitempty" example:"zh-CN"`
}
// UserListRequest 用户列表请求
type UserListRequest struct {
Page int `form:"page" binding:"omitempty,min=1" example:"1"`
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" example:"20"`
Sort string `form:"sort" binding:"omitempty,oneof=created_at updated_at username email" example:"created_at"`
Order string `form:"order" binding:"omitempty,oneof=asc desc" example:"desc"`
Status entities.UserStatus `form:"status" binding:"omitempty,oneof=active inactive suspended pending" example:"active"`
Search string `form:"search" binding:"omitempty,max=100" example:"john"`
Filters map[string]interface{} `form:"-"`
}
// UserListResponse 用户列表响应
type UserListResponse struct {
Users []*UserResponse `json:"users"`
Pagination PaginationMeta `json:"pagination"`
}
// PaginationMeta 分页元数据
type PaginationMeta struct {
Page int `json:"page" example:"1"`
PageSize int `json:"page_size" example:"20"`
Total int64 `json:"total" example:"100"`
TotalPages int `json:"total_pages" example:"5"`
HasNext bool `json:"has_next" example:"true"`
HasPrev bool `json:"has_prev" example:"false"`
}
// LoginRequest 登录请求
type LoginRequest struct {
Login string `json:"login" binding:"required" example:"john_doe"`
Password string `json:"password" binding:"required" example:"password123"`
}
// LoginResponse 登录响应
type LoginResponse struct {
User *UserResponse `json:"user"`
AccessToken string `json:"access_token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
TokenType string `json:"token_type" example:"Bearer"`
ExpiresIn int64 `json:"expires_in" example:"86400"`
}
// UpdateProfileRequest 更新用户档案请求
type UpdateProfileRequest struct {
Bio *string `json:"bio,omitempty" binding:"omitempty,max=500" example:"Software Developer"`
Location *string `json:"location,omitempty" binding:"omitempty,max=100" example:"Beijing, China"`
Website *string `json:"website,omitempty" binding:"omitempty,url" example:"https://johndoe.com"`
Birthday *time.Time `json:"birthday,omitempty" example:"1990-01-01T00:00:00Z"`
Gender *string `json:"gender,omitempty" binding:"omitempty,oneof=male female other" example:"male"`
Timezone *string `json:"timezone,omitempty" binding:"omitempty,max=50" example:"Asia/Shanghai"`
Language *string `json:"language,omitempty" binding:"omitempty,max=10" example:"zh-CN"`
}
// UserStatsResponse 用户统计响应
type UserStatsResponse struct {
TotalUsers int64 `json:"total_users" example:"1000"`
ActiveUsers int64 `json:"active_users" example:"950"`
InactiveUsers int64 `json:"inactive_users" example:"30"`
SuspendedUsers int64 `json:"suspended_users" example:"20"`
NewUsersToday int64 `json:"new_users_today" example:"5"`
NewUsersWeek int64 `json:"new_users_week" example:"25"`
NewUsersMonth int64 `json:"new_users_month" example:"120"`
}
// UserSearchRequest 用户搜索请求
type UserSearchRequest struct {
Query string `form:"q" binding:"required,min=1,max=100" example:"john"`
Page int `form:"page" binding:"omitempty,min=1" example:"1"`
PageSize int `form:"page_size" binding:"omitempty,min=1,max=50" example:"10"`
}
// 转换方法
func (r *CreateUserRequest) ToEntity() *entities.User {
return &entities.User{
Username: r.Username,
Email: r.Email,
Password: r.Password,
FirstName: r.FirstName,
LastName: r.LastName,
Phone: r.Phone,
Status: entities.UserStatusActive,
}
}
func FromEntity(user *entities.User) *UserResponse {
if user == nil {
return nil
}
return &UserResponse{
ID: user.ID,
Username: user.Username,
Email: user.Email,
FirstName: user.FirstName,
LastName: user.LastName,
Phone: user.Phone,
Avatar: user.Avatar,
Status: user.Status,
LastLoginAt: user.LastLoginAt,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
}
func FromEntities(users []*entities.User) []*UserResponse {
if users == nil {
return []*UserResponse{}
}
responses := make([]*UserResponse, len(users))
for i, user := range users {
responses[i] = FromEntity(user)
}
return responses
}

View File

@@ -0,0 +1,138 @@
package entities
import (
"time"
"gorm.io/gorm"
)
// User 用户实体
type User struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
Username string `gorm:"uniqueIndex;type:varchar(50);not null" json:"username"`
Email string `gorm:"uniqueIndex;type:varchar(100);not null" json:"email"`
Password string `gorm:"type:varchar(255);not null" json:"-"`
FirstName string `gorm:"type:varchar(50)" json:"first_name"`
LastName string `gorm:"type:varchar(50)" json:"last_name"`
Phone string `gorm:"type:varchar(20)" json:"phone"`
Avatar string `gorm:"type:varchar(255)" json:"avatar"`
Status UserStatus `gorm:"type:varchar(20);default:'active'" json:"status"`
LastLoginAt *time.Time `json:"last_login_at"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 软删除字段
IsDeleted bool `gorm:"default:false" json:"is_deleted"`
// 版本控制
Version int `gorm:"default:1" json:"version"`
}
// UserStatus 用户状态枚举
type UserStatus string
const (
UserStatusActive UserStatus = "active"
UserStatusInactive UserStatus = "inactive"
UserStatusSuspended UserStatus = "suspended"
UserStatusPending UserStatus = "pending"
)
// 实现 Entity 接口
func (u *User) GetID() string {
return u.ID
}
func (u *User) GetCreatedAt() time.Time {
return u.CreatedAt
}
func (u *User) GetUpdatedAt() time.Time {
return u.UpdatedAt
}
// 业务方法
func (u *User) IsActive() bool {
return u.Status == UserStatusActive && !u.IsDeleted
}
func (u *User) GetFullName() string {
if u.FirstName == "" && u.LastName == "" {
return u.Username
}
return u.FirstName + " " + u.LastName
}
func (u *User) CanLogin() bool {
return u.IsActive() && u.Status != UserStatusSuspended
}
func (u *User) MarkAsDeleted() {
u.IsDeleted = true
u.Status = UserStatusInactive
}
func (u *User) Restore() {
u.IsDeleted = false
u.Status = UserStatusActive
}
func (u *User) UpdateLastLogin() {
now := time.Now()
u.LastLoginAt = &now
}
// 验证方法
func (u *User) Validate() error {
if u.Username == "" {
return NewValidationError("username is required")
}
if u.Email == "" {
return NewValidationError("email is required")
}
if u.Password == "" {
return NewValidationError("password is required")
}
return nil
}
// TableName 指定表名
func (User) TableName() string {
return "users"
}
// ValidationError 验证错误
type ValidationError struct {
Message string
}
func (e *ValidationError) Error() string {
return e.Message
}
func NewValidationError(message string) *ValidationError {
return &ValidationError{Message: message}
}
// UserProfile 用户档案(扩展信息)
type UserProfile struct {
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id"`
Bio string `gorm:"type:text" json:"bio"`
Location string `gorm:"type:varchar(100)" json:"location"`
Website string `gorm:"type:varchar(255)" json:"website"`
Birthday *time.Time `json:"birthday"`
Gender string `gorm:"type:varchar(10)" json:"gender"`
Timezone string `gorm:"type:varchar(50)" json:"timezone"`
Language string `gorm:"type:varchar(10);default:'zh-CN'" json:"language"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
// 关联关系
User *User `gorm:"foreignKey:UserID;references:ID" json:"user,omitempty"`
}
func (UserProfile) TableName() string {
return "user_profiles"
}

View File

@@ -0,0 +1,299 @@
package events
import (
"encoding/json"
"time"
"tyapi-server/internal/domains/user/entities"
"github.com/google/uuid"
)
// UserEventType 用户事件类型
type UserEventType string
const (
UserCreatedEvent UserEventType = "user.created"
UserUpdatedEvent UserEventType = "user.updated"
UserDeletedEvent UserEventType = "user.deleted"
UserRestoredEvent UserEventType = "user.restored"
UserLoggedInEvent UserEventType = "user.logged_in"
UserLoggedOutEvent UserEventType = "user.logged_out"
UserPasswordChangedEvent UserEventType = "user.password_changed"
UserStatusChangedEvent UserEventType = "user.status_changed"
UserProfileUpdatedEvent UserEventType = "user.profile_updated"
)
// BaseUserEvent 用户事件基础结构
type BaseUserEvent struct {
ID string `json:"id"`
Type string `json:"type"`
Version string `json:"version"`
Timestamp time.Time `json:"timestamp"`
Source string `json:"source"`
AggregateID string `json:"aggregate_id"`
AggregateType string `json:"aggregate_type"`
Metadata map[string]interface{} `json:"metadata"`
Payload interface{} `json:"payload"`
// DDD特有字段
DomainVersion string `json:"domain_version"`
CausationID string `json:"causation_id"`
CorrelationID string `json:"correlation_id"`
}
// 实现 Event 接口
func (e *BaseUserEvent) GetID() string {
return e.ID
}
func (e *BaseUserEvent) GetType() string {
return e.Type
}
func (e *BaseUserEvent) GetVersion() string {
return e.Version
}
func (e *BaseUserEvent) GetTimestamp() time.Time {
return e.Timestamp
}
func (e *BaseUserEvent) GetPayload() interface{} {
return e.Payload
}
func (e *BaseUserEvent) GetMetadata() map[string]interface{} {
return e.Metadata
}
func (e *BaseUserEvent) GetSource() string {
return e.Source
}
func (e *BaseUserEvent) GetAggregateID() string {
return e.AggregateID
}
func (e *BaseUserEvent) GetAggregateType() string {
return e.AggregateType
}
func (e *BaseUserEvent) GetDomainVersion() string {
return e.DomainVersion
}
func (e *BaseUserEvent) GetCausationID() string {
return e.CausationID
}
func (e *BaseUserEvent) GetCorrelationID() string {
return e.CorrelationID
}
func (e *BaseUserEvent) Marshal() ([]byte, error) {
return json.Marshal(e)
}
func (e *BaseUserEvent) Unmarshal(data []byte) error {
return json.Unmarshal(data, e)
}
// UserCreated 用户创建事件
type UserCreated struct {
*BaseUserEvent
User *entities.User `json:"user"`
}
func NewUserCreatedEvent(user *entities.User, correlationID string) *UserCreated {
return &UserCreated{
BaseUserEvent: &BaseUserEvent{
ID: uuid.New().String(),
Type: string(UserCreatedEvent),
Version: "1.0",
Timestamp: time.Now(),
Source: "user-service",
AggregateID: user.ID,
AggregateType: "User",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"user_id": user.ID,
"username": user.Username,
"email": user.Email,
},
},
User: user,
}
}
func (e *UserCreated) GetPayload() interface{} {
return e.User
}
// UserUpdated 用户更新事件
type UserUpdated struct {
*BaseUserEvent
UserID string `json:"user_id"`
Changes map[string]interface{} `json:"changes"`
OldValues map[string]interface{} `json:"old_values"`
NewValues map[string]interface{} `json:"new_values"`
}
func NewUserUpdatedEvent(userID string, changes, oldValues, newValues map[string]interface{}, correlationID string) *UserUpdated {
return &UserUpdated{
BaseUserEvent: &BaseUserEvent{
ID: uuid.New().String(),
Type: string(UserUpdatedEvent),
Version: "1.0",
Timestamp: time.Now(),
Source: "user-service",
AggregateID: userID,
AggregateType: "User",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"user_id": userID,
"changed_fields": len(changes),
},
},
UserID: userID,
Changes: changes,
OldValues: oldValues,
NewValues: newValues,
}
}
// UserDeleted 用户删除事件
type UserDeleted struct {
*BaseUserEvent
UserID string `json:"user_id"`
Username string `json:"username"`
Email string `json:"email"`
SoftDelete bool `json:"soft_delete"`
}
func NewUserDeletedEvent(userID, username, email string, softDelete bool, correlationID string) *UserDeleted {
return &UserDeleted{
BaseUserEvent: &BaseUserEvent{
ID: uuid.New().String(),
Type: string(UserDeletedEvent),
Version: "1.0",
Timestamp: time.Now(),
Source: "user-service",
AggregateID: userID,
AggregateType: "User",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"user_id": userID,
"username": username,
"email": email,
"soft_delete": softDelete,
},
},
UserID: userID,
Username: username,
Email: email,
SoftDelete: softDelete,
}
}
// UserLoggedIn 用户登录事件
type UserLoggedIn struct {
*BaseUserEvent
UserID string `json:"user_id"`
Username string `json:"username"`
IPAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
}
func NewUserLoggedInEvent(userID, username, ipAddress, userAgent, correlationID string) *UserLoggedIn {
return &UserLoggedIn{
BaseUserEvent: &BaseUserEvent{
ID: uuid.New().String(),
Type: string(UserLoggedInEvent),
Version: "1.0",
Timestamp: time.Now(),
Source: "user-service",
AggregateID: userID,
AggregateType: "User",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"user_id": userID,
"username": username,
"ip_address": ipAddress,
"user_agent": userAgent,
},
},
UserID: userID,
Username: username,
IPAddress: ipAddress,
UserAgent: userAgent,
}
}
// UserPasswordChanged 用户密码修改事件
type UserPasswordChanged struct {
*BaseUserEvent
UserID string `json:"user_id"`
Username string `json:"username"`
}
func NewUserPasswordChangedEvent(userID, username, correlationID string) *UserPasswordChanged {
return &UserPasswordChanged{
BaseUserEvent: &BaseUserEvent{
ID: uuid.New().String(),
Type: string(UserPasswordChangedEvent),
Version: "1.0",
Timestamp: time.Now(),
Source: "user-service",
AggregateID: userID,
AggregateType: "User",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"user_id": userID,
"username": username,
},
},
UserID: userID,
Username: username,
}
}
// UserStatusChanged 用户状态变更事件
type UserStatusChanged struct {
*BaseUserEvent
UserID string `json:"user_id"`
Username string `json:"username"`
OldStatus entities.UserStatus `json:"old_status"`
NewStatus entities.UserStatus `json:"new_status"`
}
func NewUserStatusChangedEvent(userID, username string, oldStatus, newStatus entities.UserStatus, correlationID string) *UserStatusChanged {
return &UserStatusChanged{
BaseUserEvent: &BaseUserEvent{
ID: uuid.New().String(),
Type: string(UserStatusChangedEvent),
Version: "1.0",
Timestamp: time.Now(),
Source: "user-service",
AggregateID: userID,
AggregateType: "User",
DomainVersion: "1.0",
CorrelationID: correlationID,
Metadata: map[string]interface{}{
"user_id": userID,
"username": username,
"old_status": oldStatus,
"new_status": newStatus,
},
},
UserID: userID,
Username: username,
OldStatus: oldStatus,
NewStatus: newStatus,
}
}

View File

@@ -0,0 +1,455 @@
package handlers
import (
"strconv"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"tyapi-server/internal/domains/user/dto"
"tyapi-server/internal/domains/user/services"
"tyapi-server/internal/shared/interfaces"
"tyapi-server/internal/shared/middleware"
)
// UserHandler 用户HTTP处理器
type UserHandler struct {
userService *services.UserService
response interfaces.ResponseBuilder
validator interfaces.RequestValidator
logger *zap.Logger
jwtAuth *middleware.JWTAuthMiddleware
}
// NewUserHandler 创建用户处理器
func NewUserHandler(
userService *services.UserService,
response interfaces.ResponseBuilder,
validator interfaces.RequestValidator,
logger *zap.Logger,
jwtAuth *middleware.JWTAuthMiddleware,
) *UserHandler {
return &UserHandler{
userService: userService,
response: response,
validator: validator,
logger: logger,
jwtAuth: jwtAuth,
}
}
// GetPath 返回处理器路径
func (h *UserHandler) GetPath() string {
return "/users"
}
// GetMethod 返回HTTP方法
func (h *UserHandler) GetMethod() string {
return "GET" // 主要用于列表,具体方法在路由注册时指定
}
// GetMiddlewares 返回中间件
func (h *UserHandler) GetMiddlewares() []gin.HandlerFunc {
return []gin.HandlerFunc{
// 这里可以添加特定的中间件
}
}
// Handle 主处理函数(用于列表)
func (h *UserHandler) Handle(c *gin.Context) {
h.List(c)
}
// RequiresAuth 是否需要认证
func (h *UserHandler) RequiresAuth() bool {
return true
}
// GetPermissions 获取所需权限
func (h *UserHandler) GetPermissions() []string {
return []string{"user:read"}
}
// REST操作实现
// Create 创建用户
func (h *UserHandler) Create(c *gin.Context) {
var req dto.CreateUserRequest
// 验证请求体
if err := h.validator.BindAndValidate(c, &req); err != nil {
return // 响应已在验证器中处理
}
// 创建用户
user, err := h.userService.Create(c.Request.Context(), &req)
if err != nil {
h.logger.Error("Failed to create user", zap.Error(err))
h.response.BadRequest(c, err.Error())
return
}
// 返回响应
response := dto.FromEntity(user)
h.response.Created(c, response, "User created successfully")
}
// GetByID 根据ID获取用户
func (h *UserHandler) GetByID(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.response.BadRequest(c, "User ID is required")
return
}
// 获取用户
user, err := h.userService.GetByID(c.Request.Context(), id)
if err != nil {
h.logger.Error("Failed to get user", zap.Error(err))
h.response.NotFound(c, "User not found")
return
}
// 返回响应
response := dto.FromEntity(user)
h.response.Success(c, response)
}
// Update 更新用户
func (h *UserHandler) Update(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.response.BadRequest(c, "User ID is required")
return
}
var req dto.UpdateUserRequest
// 验证请求体
if err := h.validator.BindAndValidate(c, &req); err != nil {
return
}
// 更新用户
user, err := h.userService.Update(c.Request.Context(), id, &req)
if err != nil {
h.logger.Error("Failed to update user", zap.Error(err))
h.response.BadRequest(c, err.Error())
return
}
// 返回响应
response := dto.FromEntity(user)
h.response.Success(c, response, "User updated successfully")
}
// Delete 删除用户
func (h *UserHandler) Delete(c *gin.Context) {
id := c.Param("id")
if id == "" {
h.response.BadRequest(c, "User ID is required")
return
}
// 删除用户
if err := h.userService.Delete(c.Request.Context(), id); err != nil {
h.logger.Error("Failed to delete user", zap.Error(err))
h.response.BadRequest(c, err.Error())
return
}
// 返回响应
h.response.Success(c, nil, "User deleted successfully")
}
// List 获取用户列表
func (h *UserHandler) List(c *gin.Context) {
var req dto.UserListRequest
// 验证查询参数
if err := h.validator.ValidateQuery(c, &req); err != nil {
return
}
// 设置默认值
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 {
req.PageSize = 20
}
// 构建查询选项
options := interfaces.ListOptions{
Page: req.Page,
PageSize: req.PageSize,
Sort: req.Sort,
Order: req.Order,
Search: req.Search,
Filters: req.Filters,
}
// 获取用户列表
users, err := h.userService.List(c.Request.Context(), options)
if err != nil {
h.logger.Error("Failed to get user list", zap.Error(err))
h.response.InternalError(c, "Failed to get user list")
return
}
// 获取总数
countOptions := interfaces.CountOptions{
Search: req.Search,
Filters: req.Filters,
}
total, err := h.userService.Count(c.Request.Context(), countOptions)
if err != nil {
h.logger.Error("Failed to count users", zap.Error(err))
h.response.InternalError(c, "Failed to count users")
return
}
// 构建响应
userResponses := dto.FromEntities(users)
pagination := buildPagination(req.Page, req.PageSize, total)
h.response.Paginated(c, userResponses, pagination)
}
// Login 用户登录
func (h *UserHandler) Login(c *gin.Context) {
var req dto.LoginRequest
// 验证请求体
if err := h.validator.BindAndValidate(c, &req); err != nil {
return
}
// 用户登录
user, err := h.userService.Login(c.Request.Context(), &req)
if err != nil {
h.logger.Error("Login failed", zap.Error(err))
h.response.Unauthorized(c, "Invalid credentials")
return
}
// 生成JWT token
accessToken, err := h.jwtAuth.GenerateToken(user.ID, user.Username, user.Email)
if err != nil {
h.logger.Error("Failed to generate token", zap.Error(err))
h.response.InternalError(c, "Failed to generate access token")
return
}
// 构建登录响应
loginResponse := &dto.LoginResponse{
User: dto.FromEntity(user),
AccessToken: accessToken,
TokenType: "Bearer",
ExpiresIn: 86400, // 24小时从配置获取
}
h.response.Success(c, loginResponse, "Login successful")
}
// Logout 用户登出
func (h *UserHandler) Logout(c *gin.Context) {
// 简单实现客户端删除token即可
// 如果需要服务端黑名单,可以在这里实现
h.response.Success(c, nil, "Logout successful")
}
// GetProfile 获取当前用户信息
func (h *UserHandler) GetProfile(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "User not authenticated")
return
}
// 获取用户信息
user, err := h.userService.GetByID(c.Request.Context(), userID)
if err != nil {
h.logger.Error("Failed to get user profile", zap.Error(err))
h.response.NotFound(c, "User not found")
return
}
// 返回响应
response := dto.FromEntity(user)
h.response.Success(c, response)
}
// UpdateProfile 更新当前用户信息
func (h *UserHandler) UpdateProfile(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "User not authenticated")
return
}
var req dto.UpdateUserRequest
// 验证请求体
if err := h.validator.BindAndValidate(c, &req); err != nil {
return
}
// 更新用户
user, err := h.userService.Update(c.Request.Context(), userID, &req)
if err != nil {
h.logger.Error("Failed to update profile", zap.Error(err))
h.response.BadRequest(c, err.Error())
return
}
// 返回响应
response := dto.FromEntity(user)
h.response.Success(c, response, "Profile updated successfully")
}
// ChangePassword 修改密码
func (h *UserHandler) ChangePassword(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "User not authenticated")
return
}
var req dto.ChangePasswordRequest
// 验证请求体
if err := h.validator.BindAndValidate(c, &req); err != nil {
return
}
// 修改密码
if err := h.userService.ChangePassword(c.Request.Context(), userID, &req); err != nil {
h.logger.Error("Failed to change password", zap.Error(err))
h.response.BadRequest(c, err.Error())
return
}
h.response.Success(c, nil, "Password changed successfully")
}
// Search 搜索用户
func (h *UserHandler) Search(c *gin.Context) {
var req dto.UserSearchRequest
// 验证查询参数
if err := h.validator.ValidateQuery(c, &req); err != nil {
return
}
// 设置默认值
if req.Page <= 0 {
req.Page = 1
}
if req.PageSize <= 0 {
req.PageSize = 10
}
// 构建查询选项
options := interfaces.ListOptions{
Page: req.Page,
PageSize: req.PageSize,
Search: req.Query,
}
// 搜索用户
users, err := h.userService.Search(c.Request.Context(), req.Query, options)
if err != nil {
h.logger.Error("Failed to search users", zap.Error(err))
h.response.InternalError(c, "Failed to search users")
return
}
// 获取搜索结果总数
countOptions := interfaces.CountOptions{
Search: req.Query,
}
total, err := h.userService.Count(c.Request.Context(), countOptions)
if err != nil {
h.logger.Error("Failed to count search results", zap.Error(err))
h.response.InternalError(c, "Failed to count search results")
return
}
// 构建响应
userResponses := dto.FromEntities(users)
pagination := buildPagination(req.Page, req.PageSize, total)
h.response.Paginated(c, userResponses, pagination)
}
// GetStats 获取用户统计
func (h *UserHandler) GetStats(c *gin.Context) {
stats, err := h.userService.GetStats(c.Request.Context())
if err != nil {
h.logger.Error("Failed to get user stats", zap.Error(err))
h.response.InternalError(c, "Failed to get user statistics")
return
}
h.response.Success(c, stats)
}
// 私有方法
// getCurrentUserID 获取当前用户ID
func (h *UserHandler) getCurrentUserID(c *gin.Context) string {
if userID, exists := c.Get("user_id"); exists {
if id, ok := userID.(string); ok {
return id
}
}
return ""
}
// parsePageSize 解析页面大小
func (h *UserHandler) parsePageSize(str string, defaultValue int) int {
if str == "" {
return defaultValue
}
if size, err := strconv.Atoi(str); err == nil && size > 0 && size <= 100 {
return size
}
return defaultValue
}
// parsePage 解析页码
func (h *UserHandler) parsePage(str string, defaultValue int) int {
if str == "" {
return defaultValue
}
if page, err := strconv.Atoi(str); err == nil && page > 0 {
return page
}
return defaultValue
}
// buildPagination 构建分页元数据
func buildPagination(page, pageSize int, total int64) interfaces.PaginationMeta {
totalPages := int(float64(total) / float64(pageSize))
if float64(total)/float64(pageSize) > float64(totalPages) {
totalPages++
}
if totalPages < 1 {
totalPages = 1
}
return interfaces.PaginationMeta{
Page: page,
PageSize: pageSize,
Total: total,
TotalPages: totalPages,
HasNext: page < totalPages,
HasPrev: page > 1,
}
}

View File

@@ -0,0 +1,339 @@
package repositories
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
"tyapi-server/internal/domains/user/entities"
"tyapi-server/internal/shared/interfaces"
)
// UserRepository 用户仓储实现
type UserRepository struct {
db *gorm.DB
cache interfaces.CacheService
logger *zap.Logger
}
// NewUserRepository 创建用户仓储
func NewUserRepository(db *gorm.DB, cache interfaces.CacheService, logger *zap.Logger) *UserRepository {
return &UserRepository{
db: db,
cache: cache,
logger: logger,
}
}
// Create 创建用户
func (r *UserRepository) Create(ctx context.Context, entity *entities.User) error {
if err := r.db.WithContext(ctx).Create(entity).Error; err != nil {
r.logger.Error("Failed to create user", zap.Error(err))
return err
}
// 清除相关缓存
r.invalidateUserCaches(ctx, entity.ID)
return nil
}
// GetByID 根据ID获取用户
func (r *UserRepository) GetByID(ctx context.Context, id string) (*entities.User, error) {
// 先尝试从缓存获取
cacheKey := r.GetCacheKey(id)
var user entities.User
if err := r.cache.Get(ctx, cacheKey, &user); err == nil {
return &user, nil
}
// 从数据库获取
if err := r.db.WithContext(ctx).Where("id = ? AND is_deleted = false", id).First(&user).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("user not found")
}
return nil, err
}
// 缓存结果
r.cache.Set(ctx, cacheKey, &user, 1*time.Hour)
return &user, nil
}
// Update 更新用户
func (r *UserRepository) Update(ctx context.Context, entity *entities.User) error {
if err := r.db.WithContext(ctx).Save(entity).Error; err != nil {
r.logger.Error("Failed to update user", zap.Error(err))
return err
}
// 清除相关缓存
r.invalidateUserCaches(ctx, entity.ID)
return nil
}
// Delete 删除用户
func (r *UserRepository) Delete(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Delete(&entities.User{}, "id = ?", id).Error; err != nil {
r.logger.Error("Failed to delete user", zap.Error(err))
return err
}
// 清除相关缓存
r.invalidateUserCaches(ctx, id)
return nil
}
// CreateBatch 批量创建用户
func (r *UserRepository) CreateBatch(ctx context.Context, entities []*entities.User) error {
if err := r.db.WithContext(ctx).CreateInBatches(entities, 100).Error; err != nil {
r.logger.Error("Failed to create users in batch", zap.Error(err))
return err
}
// 清除列表缓存
r.cache.DeletePattern(ctx, "users:list:*")
return nil
}
// GetByIDs 根据ID列表获取用户
func (r *UserRepository) GetByIDs(ctx context.Context, ids []string) ([]*entities.User, error) {
var users []entities.User
if err := r.db.WithContext(ctx).
Where("id IN ? AND is_deleted = false", ids).
Find(&users).Error; err != nil {
return nil, err
}
// 转换为指针切片
result := make([]*entities.User, len(users))
for i := range users {
result[i] = &users[i]
}
return result, nil
}
// UpdateBatch 批量更新用户
func (r *UserRepository) UpdateBatch(ctx context.Context, entities []*entities.User) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for _, entity := range entities {
if err := tx.Save(entity).Error; err != nil {
return err
}
}
return nil
})
}
// DeleteBatch 批量删除用户
func (r *UserRepository) DeleteBatch(ctx context.Context, ids []string) error {
if err := r.db.WithContext(ctx).
Where("id IN ?", ids).
Delete(&entities.User{}).Error; err != nil {
return err
}
// 清除相关缓存
for _, id := range ids {
r.invalidateUserCaches(ctx, id)
}
return nil
}
// List 获取用户列表
func (r *UserRepository) List(ctx context.Context, options interfaces.ListOptions) ([]*entities.User, error) {
// 尝试从缓存获取
cacheKey := fmt.Sprintf("users:list:%d:%d:%s", options.Page, options.PageSize, options.Sort)
var users []*entities.User
if err := r.cache.Get(ctx, cacheKey, &users); err == nil {
return users, nil
}
// 从数据库查询
query := r.db.WithContext(ctx).Where("is_deleted = false")
// 应用过滤条件
if options.Search != "" {
query = query.Where("username ILIKE ? OR email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?",
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
}
// 应用排序
if options.Sort != "" {
order := options.Order
if order == "" {
order = "asc"
}
query = query.Order(fmt.Sprintf("%s %s", options.Sort, order))
} else {
query = query.Order("created_at desc")
}
// 应用分页
if options.Page > 0 && options.PageSize > 0 {
offset := (options.Page - 1) * options.PageSize
query = query.Offset(offset).Limit(options.PageSize)
}
var userEntities []entities.User
if err := query.Find(&userEntities).Error; err != nil {
return nil, err
}
// 转换为指针切片
users = make([]*entities.User, len(userEntities))
for i := range userEntities {
users[i] = &userEntities[i]
}
// 缓存结果
r.cache.Set(ctx, cacheKey, users, 30*time.Minute)
return users, nil
}
// Count 统计用户数量
func (r *UserRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
query := r.db.WithContext(ctx).Model(&entities.User{}).Where("is_deleted = false")
// 应用过滤条件
if options.Search != "" {
query = query.Where("username ILIKE ? OR email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?",
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
}
var count int64
if err := query.Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// Exists 检查用户是否存在
func (r *UserRepository) Exists(ctx context.Context, id string) (bool, error) {
var count int64
if err := r.db.WithContext(ctx).
Model(&entities.User{}).
Where("id = ? AND is_deleted = false", id).
Count(&count).Error; err != nil {
return false, err
}
return count > 0, nil
}
// SoftDelete 软删除用户
func (r *UserRepository) SoftDelete(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).
Model(&entities.User{}).
Where("id = ?", id).
Update("is_deleted", true).Error; err != nil {
return err
}
// 清除相关缓存
r.invalidateUserCaches(ctx, id)
return nil
}
// Restore 恢复用户
func (r *UserRepository) Restore(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).
Model(&entities.User{}).
Where("id = ?", id).
Update("is_deleted", false).Error; err != nil {
return err
}
// 清除相关缓存
r.invalidateUserCaches(ctx, id)
return nil
}
// WithTx 使用事务
func (r *UserRepository) WithTx(tx interface{}) interfaces.Repository[*entities.User] {
gormTx, ok := tx.(*gorm.DB)
if !ok {
return r
}
return &UserRepository{
db: gormTx,
cache: r.cache,
logger: r.logger,
}
}
// InvalidateCache 清除缓存
func (r *UserRepository) InvalidateCache(ctx context.Context, keys ...string) error {
return r.cache.Delete(ctx, keys...)
}
// WarmupCache 预热缓存
func (r *UserRepository) WarmupCache(ctx context.Context) error {
// 预热热门用户数据
// 这里可以实现具体的预热逻辑
return nil
}
// GetCacheKey 获取缓存键
func (r *UserRepository) GetCacheKey(id string) string {
return fmt.Sprintf("user:%s", id)
}
// FindByUsername 根据用户名查找用户
func (r *UserRepository) FindByUsername(ctx context.Context, username string) (*entities.User, error) {
var user entities.User
if err := r.db.WithContext(ctx).
Where("username = ? AND is_deleted = false", username).
First(&user).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("user not found")
}
return nil, err
}
return &user, nil
}
// FindByEmail 根据邮箱查找用户
func (r *UserRepository) FindByEmail(ctx context.Context, email string) (*entities.User, error) {
var user entities.User
if err := r.db.WithContext(ctx).
Where("email = ? AND is_deleted = false", email).
First(&user).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("user not found")
}
return nil, err
}
return &user, nil
}
// invalidateUserCaches 清除用户相关缓存
func (r *UserRepository) invalidateUserCaches(ctx context.Context, userID string) {
keys := []string{
r.GetCacheKey(userID),
}
r.cache.Delete(ctx, keys...)
r.cache.DeletePattern(ctx, "users:list:*")
}

View File

@@ -0,0 +1,133 @@
package routes
import (
"tyapi-server/internal/domains/user/handlers"
"tyapi-server/internal/shared/middleware"
"github.com/gin-gonic/gin"
)
// UserRoutes 用户路由注册器
type UserRoutes struct {
handler *handlers.UserHandler
jwtAuth *middleware.JWTAuthMiddleware
optionalAuth *middleware.OptionalAuthMiddleware
}
// NewUserRoutes 创建用户路由注册器
func NewUserRoutes(
handler *handlers.UserHandler,
jwtAuth *middleware.JWTAuthMiddleware,
optionalAuth *middleware.OptionalAuthMiddleware,
) *UserRoutes {
return &UserRoutes{
handler: handler,
jwtAuth: jwtAuth,
optionalAuth: optionalAuth,
}
}
// RegisterRoutes 注册用户路由
func (r *UserRoutes) RegisterRoutes(router *gin.Engine) {
// API版本组
v1 := router.Group("/api/v1")
// 公开路由(不需要认证)
public := v1.Group("/auth")
{
public.POST("/login", r.handler.Login)
public.POST("/register", r.handler.Create)
}
// 需要认证的路由
protected := v1.Group("/users")
protected.Use(r.jwtAuth.Handle())
{
// 用户管理(管理员)
protected.GET("", r.handler.List)
protected.POST("", r.handler.Create)
protected.GET("/:id", r.handler.GetByID)
protected.PUT("/:id", r.handler.Update)
protected.DELETE("/:id", r.handler.Delete)
// 用户搜索
protected.GET("/search", r.handler.Search)
// 用户统计
protected.GET("/stats", r.handler.GetStats)
}
// 用户个人操作路由
profile := v1.Group("/profile")
profile.Use(r.jwtAuth.Handle())
{
profile.GET("", r.handler.GetProfile)
profile.PUT("", r.handler.UpdateProfile)
profile.POST("/change-password", r.handler.ChangePassword)
profile.POST("/logout", r.handler.Logout)
}
}
// RegisterPublicRoutes 注册公开路由
func (r *UserRoutes) RegisterPublicRoutes(router *gin.Engine) {
v1 := router.Group("/api/v1")
// 公开的用户相关路由
public := v1.Group("/public")
{
// 可选认证的路由(用户可能登录也可能未登录)
public.Use(r.optionalAuth.Handle())
// 这里可以添加一些公开的用户信息查询接口
// 比如根据用户名查看公开信息(如果用户设置为公开)
}
}
// RegisterAdminRoutes 注册管理员路由
func (r *UserRoutes) RegisterAdminRoutes(router *gin.Engine) {
admin := router.Group("/admin/v1")
admin.Use(r.jwtAuth.Handle())
// 这里可以添加管理员权限检查中间件
// 管理员用户管理
users := admin.Group("/users")
{
users.GET("", r.handler.List)
users.GET("/:id", r.handler.GetByID)
users.PUT("/:id", r.handler.Update)
users.DELETE("/:id", r.handler.Delete)
users.GET("/stats", r.handler.GetStats)
users.GET("/search", r.handler.Search)
// 批量操作
users.POST("/batch-delete", r.handleBatchDelete)
users.POST("/batch-update", r.handleBatchUpdate)
}
}
// 批量删除处理器
func (r *UserRoutes) handleBatchDelete(c *gin.Context) {
// 实现批量删除逻辑
// 这里可以接收用户ID列表并调用服务进行批量删除
c.JSON(200, gin.H{"message": "Batch delete not implemented yet"})
}
// 批量更新处理器
func (r *UserRoutes) handleBatchUpdate(c *gin.Context) {
// 实现批量更新逻辑
c.JSON(200, gin.H{"message": "Batch update not implemented yet"})
}
// RegisterHealthRoutes 注册健康检查路由
func (r *UserRoutes) RegisterHealthRoutes(router *gin.Engine) {
health := router.Group("/health")
{
health.GET("/users", func(c *gin.Context) {
// 用户服务健康检查
c.JSON(200, gin.H{
"service": "users",
"status": "healthy",
})
})
}
}

View File

@@ -0,0 +1,469 @@
package services
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
"tyapi-server/internal/domains/user/dto"
"tyapi-server/internal/domains/user/entities"
"tyapi-server/internal/domains/user/events"
"tyapi-server/internal/domains/user/repositories"
"tyapi-server/internal/shared/interfaces"
)
// UserService 用户服务实现
type UserService struct {
repo *repositories.UserRepository
eventBus interfaces.EventBus
logger *zap.Logger
}
// NewUserService 创建用户服务
func NewUserService(
repo *repositories.UserRepository,
eventBus interfaces.EventBus,
logger *zap.Logger,
) *UserService {
return &UserService{
repo: repo,
eventBus: eventBus,
logger: logger,
}
}
// Name 返回服务名称
func (s *UserService) Name() string {
return "user-service"
}
// Initialize 初始化服务
func (s *UserService) Initialize(ctx context.Context) error {
s.logger.Info("User service initialized")
return nil
}
// HealthCheck 健康检查
func (s *UserService) HealthCheck(ctx context.Context) error {
// 简单检查:尝试查询用户数量
_, err := s.repo.Count(ctx, interfaces.CountOptions{})
return err
}
// Shutdown 关闭服务
func (s *UserService) Shutdown(ctx context.Context) error {
s.logger.Info("User service shutdown")
return nil
}
// Create 创建用户
func (s *UserService) Create(ctx context.Context, createDTO interface{}) (*entities.User, error) {
req, ok := createDTO.(*dto.CreateUserRequest)
if !ok {
return nil, fmt.Errorf("invalid DTO type for user creation")
}
// 验证业务规则
if err := s.ValidateCreate(ctx, req); err != nil {
return nil, err
}
// 检查用户名和邮箱是否已存在
if err := s.checkDuplicates(ctx, req.Username, req.Email); err != nil {
return nil, err
}
// 创建用户实体
user := req.ToEntity()
user.ID = uuid.New().String()
// 加密密码
hashedPassword, err := s.hashPassword(req.Password)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %w", err)
}
user.Password = hashedPassword
// 保存用户
if err := s.repo.Create(ctx, user); err != nil {
s.logger.Error("Failed to create user", zap.Error(err))
return nil, fmt.Errorf("failed to create user: %w", err)
}
// 发布用户创建事件
event := events.NewUserCreatedEvent(user, s.getCorrelationID(ctx))
if err := s.eventBus.Publish(ctx, event); err != nil {
s.logger.Warn("Failed to publish user created event", zap.Error(err))
}
s.logger.Info("User created successfully",
zap.String("user_id", user.ID),
zap.String("username", user.Username))
return user, nil
}
// GetByID 根据ID获取用户
func (s *UserService) GetByID(ctx context.Context, id string) (*entities.User, error) {
if id == "" {
return nil, fmt.Errorf("user ID is required")
}
user, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("user not found: %w", err)
}
return user, nil
}
// Update 更新用户
func (s *UserService) Update(ctx context.Context, id string, updateDTO interface{}) (*entities.User, error) {
req, ok := updateDTO.(*dto.UpdateUserRequest)
if !ok {
return nil, fmt.Errorf("invalid DTO type for user update")
}
// 验证业务规则
if err := s.ValidateUpdate(ctx, id, req); err != nil {
return nil, err
}
// 获取现有用户
user, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("user not found: %w", err)
}
// 记录变更前的值
oldValues := s.captureUserValues(user)
// 应用更新
s.applyUserUpdates(user, req)
// 保存更新
if err := s.repo.Update(ctx, user); err != nil {
s.logger.Error("Failed to update user", zap.Error(err))
return nil, fmt.Errorf("failed to update user: %w", err)
}
// 发布用户更新事件
newValues := s.captureUserValues(user)
changes := s.findChanges(oldValues, newValues)
if len(changes) > 0 {
event := events.NewUserUpdatedEvent(user.ID, changes, oldValues, newValues, s.getCorrelationID(ctx))
if err := s.eventBus.Publish(ctx, event); err != nil {
s.logger.Warn("Failed to publish user updated event", zap.Error(err))
}
}
s.logger.Info("User updated successfully",
zap.String("user_id", user.ID),
zap.Int("changes", len(changes)))
return user, nil
}
// Delete 删除用户
func (s *UserService) Delete(ctx context.Context, id string) error {
if id == "" {
return fmt.Errorf("user ID is required")
}
// 获取用户信息用于事件
user, err := s.repo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("user not found: %w", err)
}
// 软删除用户
if err := s.repo.SoftDelete(ctx, id); err != nil {
s.logger.Error("Failed to delete user", zap.Error(err))
return fmt.Errorf("failed to delete user: %w", err)
}
// 发布用户删除事件
event := events.NewUserDeletedEvent(user.ID, user.Username, user.Email, true, s.getCorrelationID(ctx))
if err := s.eventBus.Publish(ctx, event); err != nil {
s.logger.Warn("Failed to publish user deleted event", zap.Error(err))
}
s.logger.Info("User deleted successfully", zap.String("user_id", id))
return nil
}
// List 获取用户列表
func (s *UserService) List(ctx context.Context, options interfaces.ListOptions) ([]*entities.User, error) {
return s.repo.List(ctx, options)
}
// Search 搜索用户
func (s *UserService) Search(ctx context.Context, query string, options interfaces.ListOptions) ([]*entities.User, error) {
// 设置搜索关键字
searchOptions := options
searchOptions.Search = query
return s.repo.List(ctx, searchOptions)
}
// Count 统计用户数量
func (s *UserService) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
return s.repo.Count(ctx, options)
}
// Validate 验证用户实体
func (s *UserService) Validate(ctx context.Context, entity *entities.User) error {
return entity.Validate()
}
// ValidateCreate 验证创建请求
func (s *UserService) ValidateCreate(ctx context.Context, createDTO interface{}) error {
req, ok := createDTO.(*dto.CreateUserRequest)
if !ok {
return fmt.Errorf("invalid DTO type")
}
// 基础验证已经由binding标签处理这里添加业务规则验证
if req.Username == "admin" || req.Username == "root" {
return fmt.Errorf("username '%s' is reserved", req.Username)
}
return nil
}
// ValidateUpdate 验证更新请求
func (s *UserService) ValidateUpdate(ctx context.Context, id string, updateDTO interface{}) error {
_, ok := updateDTO.(*dto.UpdateUserRequest)
if !ok {
return fmt.Errorf("invalid DTO type")
}
if id == "" {
return fmt.Errorf("user ID is required")
}
return nil
}
// 业务方法
// Login 用户登录
func (s *UserService) Login(ctx context.Context, loginReq *dto.LoginRequest) (*entities.User, error) {
// 根据用户名或邮箱查找用户
var user *entities.User
var err error
if s.isEmail(loginReq.Login) {
user, err = s.repo.FindByEmail(ctx, loginReq.Login)
} else {
user, err = s.repo.FindByUsername(ctx, loginReq.Login)
}
if err != nil {
return nil, fmt.Errorf("invalid credentials")
}
// 验证密码
if !s.checkPassword(loginReq.Password, user.Password) {
return nil, fmt.Errorf("invalid credentials")
}
// 检查用户状态
if !user.CanLogin() {
return nil, fmt.Errorf("account is disabled or suspended")
}
// 更新最后登录时间
user.UpdateLastLogin()
if err := s.repo.Update(ctx, user); err != nil {
s.logger.Warn("Failed to update last login time", zap.Error(err))
}
// 发布登录事件
event := events.NewUserLoggedInEvent(
user.ID, user.Username,
s.getClientIP(ctx), s.getUserAgent(ctx),
s.getCorrelationID(ctx))
if err := s.eventBus.Publish(ctx, event); err != nil {
s.logger.Warn("Failed to publish user logged in event", zap.Error(err))
}
s.logger.Info("User logged in successfully",
zap.String("user_id", user.ID),
zap.String("username", user.Username))
return user, nil
}
// ChangePassword 修改密码
func (s *UserService) ChangePassword(ctx context.Context, userID string, req *dto.ChangePasswordRequest) error {
// 获取用户
user, err := s.repo.GetByID(ctx, userID)
if err != nil {
return fmt.Errorf("user not found: %w", err)
}
// 验证旧密码
if !s.checkPassword(req.OldPassword, user.Password) {
return fmt.Errorf("current password is incorrect")
}
// 加密新密码
hashedPassword, err := s.hashPassword(req.NewPassword)
if err != nil {
return fmt.Errorf("failed to hash new password: %w", err)
}
// 更新密码
user.Password = hashedPassword
if err := s.repo.Update(ctx, user); err != nil {
return fmt.Errorf("failed to update password: %w", err)
}
// 发布密码修改事件
event := events.NewUserPasswordChangedEvent(user.ID, user.Username, s.getCorrelationID(ctx))
if err := s.eventBus.Publish(ctx, event); err != nil {
s.logger.Warn("Failed to publish password changed event", zap.Error(err))
}
s.logger.Info("Password changed successfully", zap.String("user_id", userID))
return nil
}
// GetStats 获取用户统计
func (s *UserService) GetStats(ctx context.Context) (*dto.UserStatsResponse, error) {
total, err := s.repo.Count(ctx, interfaces.CountOptions{})
if err != nil {
return nil, err
}
// 这里可以并行查询不同状态的用户数量
// 简化实现,返回基础统计
return &dto.UserStatsResponse{
TotalUsers: total,
ActiveUsers: total, // 简化
InactiveUsers: 0,
SuspendedUsers: 0,
NewUsersToday: 0,
NewUsersWeek: 0,
NewUsersMonth: 0,
}, nil
}
// 私有方法
// checkDuplicates 检查重复的用户名和邮箱
func (s *UserService) checkDuplicates(ctx context.Context, username, email string) error {
// 检查用户名
if existingUser, err := s.repo.FindByUsername(ctx, username); err == nil && existingUser != nil {
return fmt.Errorf("username already exists")
}
// 检查邮箱
if existingUser, err := s.repo.FindByEmail(ctx, email); err == nil && existingUser != nil {
return fmt.Errorf("email already exists")
}
return nil
}
// hashPassword 加密密码
func (s *UserService) hashPassword(password string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hash), nil
}
// checkPassword 验证密码
func (s *UserService) checkPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
// isEmail 检查是否为邮箱格式
func (s *UserService) isEmail(str string) bool {
return len(str) > 0 && len(str) < 255 &&
len(str) > 5 &&
str[len(str)-4:] != ".." &&
(len(str) > 6 && str[len(str)-4:] == ".com") ||
(len(str) > 5 && str[len(str)-3:] == ".cn") ||
(len(str) > 6 && str[len(str)-4:] == ".org") ||
(len(str) > 6 && str[len(str)-4:] == ".net")
// 简化的邮箱检查,实际应该使用正则表达式
}
// applyUserUpdates 应用用户更新
func (s *UserService) applyUserUpdates(user *entities.User, req *dto.UpdateUserRequest) {
if req.FirstName != nil {
user.FirstName = *req.FirstName
}
if req.LastName != nil {
user.LastName = *req.LastName
}
if req.Phone != nil {
user.Phone = *req.Phone
}
if req.Avatar != nil {
user.Avatar = *req.Avatar
}
user.UpdatedAt = time.Now()
}
// captureUserValues 捕获用户值用于变更比较
func (s *UserService) captureUserValues(user *entities.User) map[string]interface{} {
return map[string]interface{}{
"first_name": user.FirstName,
"last_name": user.LastName,
"phone": user.Phone,
"avatar": user.Avatar,
}
}
// findChanges 找出变更的字段
func (s *UserService) findChanges(oldValues, newValues map[string]interface{}) map[string]interface{} {
changes := make(map[string]interface{})
for key, newValue := range newValues {
if oldValue, exists := oldValues[key]; !exists || oldValue != newValue {
changes[key] = newValue
}
}
return changes
}
// getCorrelationID 获取关联ID
func (s *UserService) getCorrelationID(ctx context.Context) string {
if id := ctx.Value("correlation_id"); id != nil {
if correlationID, ok := id.(string); ok {
return correlationID
}
}
return uuid.New().String()
}
// getClientIP 获取客户端IP
func (s *UserService) getClientIP(ctx context.Context) string {
if ip := ctx.Value("client_ip"); ip != nil {
if clientIP, ok := ip.(string); ok {
return clientIP
}
}
return "unknown"
}
// getUserAgent 获取用户代理
func (s *UserService) getUserAgent(ctx context.Context) string {
if ua := ctx.Value("user_agent"); ua != nil {
if userAgent, ok := ua.(string); ok {
return userAgent
}
}
return "unknown"
}