Initial commit: Basic project structure and dependencies
This commit is contained in:
173
internal/domains/user/dto/user_dto.go
Normal file
173
internal/domains/user/dto/user_dto.go
Normal 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
|
||||
}
|
||||
138
internal/domains/user/entities/user.go
Normal file
138
internal/domains/user/entities/user.go
Normal 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"
|
||||
}
|
||||
299
internal/domains/user/events/user_events.go
Normal file
299
internal/domains/user/events/user_events.go
Normal 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,
|
||||
}
|
||||
}
|
||||
455
internal/domains/user/handlers/user_handler.go
Normal file
455
internal/domains/user/handlers/user_handler.go
Normal 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,
|
||||
}
|
||||
}
|
||||
339
internal/domains/user/repositories/user_repository.go
Normal file
339
internal/domains/user/repositories/user_repository.go
Normal 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:*")
|
||||
}
|
||||
133
internal/domains/user/routes/user_routes.go
Normal file
133
internal/domains/user/routes/user_routes.go
Normal 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",
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
469
internal/domains/user/services/user_service.go
Normal file
469
internal/domains/user/services/user_service.go
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user