5.9.2 用户服务开发

5.9.2 用户服务开发 #

用户服务是电商系统的核心服务之一,负责用户注册、登录、认证、授权以及用户信息管理。本节将详细介绍如何设计和实现一个完整的用户服务。

服务架构设计 #

分层架构 #

用户服务采用经典的分层架构:

┌─────────────────────────────────────┐
│           Presentation Layer        │  ← HTTP/gRPC 接口
├─────────────────────────────────────┤
│           Application Layer         │  ← 应用服务层
├─────────────────────────────────────┤
│             Domain Layer            │  ← 领域模型层
├─────────────────────────────────────┤
│          Infrastructure Layer       │  ← 基础设施层
└─────────────────────────────────────┘

项目结构 #

user-service/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── application/
│   │   ├── command/
│   │   ├── query/
│   │   └── service/
│   ├── domain/
│   │   ├── entity/
│   │   ├── repository/
│   │   ├── service/
│   │   └── event/
│   ├── infrastructure/
│   │   ├── persistence/
│   │   ├── messaging/
│   │   └── config/
│   └── presentation/
│       ├── http/
│       └── grpc/
├── pkg/
│   ├── auth/
│   ├── crypto/
│   └── validator/
├── configs/
├── deployments/
└── docs/

领域模型实现 #

用户实体 #

// internal/domain/entity/user.go
package entity

import (
    "errors"
    "time"
    "golang.org/x/crypto/bcrypt"
)

type User struct {
    ID        string    `json:"id"`
    Username  string    `json:"username"`
    Email     string    `json:"email"`
    Phone     string    `json:"phone"`
    Password  string    `json:"-"` // 不序列化密码
    Profile   Profile   `json:"profile"`
    Status    UserStatus `json:"status"`
    Roles     []string  `json:"roles"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`

    // 领域事件
    events []DomainEvent
}

type Profile struct {
    FirstName string    `json:"first_name"`
    LastName  string    `json:"last_name"`
    Avatar    string    `json:"avatar"`
    Birthday  time.Time `json:"birthday"`
    Gender    Gender    `json:"gender"`
    Address   Address   `json:"address"`
}

type Address struct {
    Street   string `json:"street"`
    City     string `json:"city"`
    Province string `json:"province"`
    Country  string `json:"country"`
    ZipCode  string `json:"zip_code"`
}

type Gender int

const (
    GenderUnknown Gender = iota
    GenderMale
    GenderFemale
)

type UserStatus int

const (
    UserStatusActive UserStatus = iota + 1
    UserStatusInactive
    UserStatusSuspended
    UserStatusDeleted
)

// 用户行为方法
func NewUser(username, email, phone, password string) (*User, error) {
    if err := validateUserInput(username, email, phone, password); err != nil {
        return nil, err
    }

    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if err != nil {
        return nil, err
    }

    user := &User{
        ID:        generateID(),
        Username:  username,
        Email:     email,
        Phone:     phone,
        Password:  string(hashedPassword),
        Status:    UserStatusActive,
        Roles:     []string{"user"},
        CreatedAt: time.Now(),
        UpdatedAt: time.Now(),
        events:    make([]DomainEvent, 0),
    }

    user.addEvent(NewUserCreatedEvent(user.ID, user.Username, user.Email))
    return user, nil
}

func (u *User) VerifyPassword(password string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
    return err == nil
}

func (u *User) UpdateProfile(profile Profile) error {
    if u.Status != UserStatusActive {
        return errors.New("inactive user cannot update profile")
    }

    u.Profile = profile
    u.UpdatedAt = time.Now()

    u.addEvent(NewUserProfileUpdatedEvent(u.ID, profile))
    return nil
}

func (u *User) ChangePassword(oldPassword, newPassword string) error {
    if !u.VerifyPassword(oldPassword) {
        return errors.New("invalid old password")
    }

    if err := validatePassword(newPassword); err != nil {
        return err
    }

    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
    if err != nil {
        return err
    }

    u.Password = string(hashedPassword)
    u.UpdatedAt = time.Now()

    u.addEvent(NewUserPasswordChangedEvent(u.ID))
    return nil
}

func (u *User) Suspend() error {
    if u.Status == UserStatusSuspended {
        return errors.New("user already suspended")
    }

    u.Status = UserStatusSuspended
    u.UpdatedAt = time.Now()

    u.addEvent(NewUserSuspendedEvent(u.ID))
    return nil
}

func (u *User) Activate() error {
    if u.Status == UserStatusActive {
        return errors.New("user already active")
    }

    u.Status = UserStatusActive
    u.UpdatedAt = time.Now()

    u.addEvent(NewUserActivatedEvent(u.ID))
    return nil
}

func (u *User) AddRole(role string) error {
    for _, r := range u.Roles {
        if r == role {
            return errors.New("role already exists")
        }
    }

    u.Roles = append(u.Roles, role)
    u.UpdatedAt = time.Now()

    u.addEvent(NewUserRoleAddedEvent(u.ID, role))
    return nil
}

func (u *User) RemoveRole(role string) error {
    for i, r := range u.Roles {
        if r == role {
            u.Roles = append(u.Roles[:i], u.Roles[i+1:]...)
            u.UpdatedAt = time.Now()
            u.addEvent(NewUserRoleRemovedEvent(u.ID, role))
            return nil
        }
    }

    return errors.New("role not found")
}

// 获取领域事件
func (u *User) GetEvents() []DomainEvent {
    return u.events
}

// 清除领域事件
func (u *User) ClearEvents() {
    u.events = make([]DomainEvent, 0)
}

// 添加领域事件
func (u *User) addEvent(event DomainEvent) {
    u.events = append(u.events, event)
}

// 验证用户输入
func validateUserInput(username, email, phone, password string) error {
    if username == "" {
        return errors.New("username is required")
    }
    if email == "" {
        return errors.New("email is required")
    }
    if !isValidEmail(email) {
        return errors.New("invalid email format")
    }
    if phone == "" {
        return errors.New("phone is required")
    }
    if !isValidPhone(phone) {
        return errors.New("invalid phone format")
    }
    if err := validatePassword(password); err != nil {
        return err
    }

    return nil
}

func validatePassword(password string) error {
    if len(password) < 8 {
        return errors.New("password must be at least 8 characters")
    }
    // 可以添加更多密码复杂度验证
    return nil
}

领域事件 #

// internal/domain/event/user_events.go
package event

import "time"

type DomainEvent interface {
    GetEventID() string
    GetEventType() string
    GetAggregateID() string
    GetOccurredAt() time.Time
}

type BaseEvent struct {
    EventID     string    `json:"event_id"`
    EventType   string    `json:"event_type"`
    AggregateID string    `json:"aggregate_id"`
    OccurredAt  time.Time `json:"occurred_at"`
}

func (e BaseEvent) GetEventID() string     { return e.EventID }
func (e BaseEvent) GetEventType() string   { return e.EventType }
func (e BaseEvent) GetAggregateID() string { return e.AggregateID }
func (e BaseEvent) GetOccurredAt() time.Time { return e.OccurredAt }

// 用户创建事件
type UserCreatedEvent struct {
    BaseEvent
    Username string `json:"username"`
    Email    string `json:"email"`
}

func NewUserCreatedEvent(userID, username, email string) *UserCreatedEvent {
    return &UserCreatedEvent{
        BaseEvent: BaseEvent{
            EventID:     generateEventID(),
            EventType:   "user.created",
            AggregateID: userID,
            OccurredAt:  time.Now(),
        },
        Username: username,
        Email:    email,
    }
}

// 用户资料更新事件
type UserProfileUpdatedEvent struct {
    BaseEvent
    Profile Profile `json:"profile"`
}

func NewUserProfileUpdatedEvent(userID string, profile Profile) *UserProfileUpdatedEvent {
    return &UserProfileUpdatedEvent{
        BaseEvent: BaseEvent{
            EventID:     generateEventID(),
            EventType:   "user.profile.updated",
            AggregateID: userID,
            OccurredAt:  time.Now(),
        },
        Profile: profile,
    }
}

// 用户密码修改事件
type UserPasswordChangedEvent struct {
    BaseEvent
}

func NewUserPasswordChangedEvent(userID string) *UserPasswordChangedEvent {
    return &UserPasswordChangedEvent{
        BaseEvent: BaseEvent{
            EventID:     generateEventID(),
            EventType:   "user.password.changed",
            AggregateID: userID,
            OccurredAt:  time.Now(),
        },
    }
}

// 用户暂停事件
type UserSuspendedEvent struct {
    BaseEvent
}

func NewUserSuspendedEvent(userID string) *UserSuspendedEvent {
    return &UserSuspendedEvent{
        BaseEvent: BaseEvent{
            EventID:     generateEventID(),
            EventType:   "user.suspended",
            AggregateID: userID,
            OccurredAt:  time.Now(),
        },
    }
}

仓储接口 #

// internal/domain/repository/user_repository.go
package repository

import (
    "context"
    "github.com/ecommerce/user-service/internal/domain/entity"
)

type UserRepository interface {
    // 基本 CRUD 操作
    Save(ctx context.Context, user *entity.User) error
    GetByID(ctx context.Context, id string) (*entity.User, error)
    GetByUsername(ctx context.Context, username string) (*entity.User, error)
    GetByEmail(ctx context.Context, email string) (*entity.User, error)
    GetByPhone(ctx context.Context, phone string) (*entity.User, error)
    Update(ctx context.Context, user *entity.User) error
    Delete(ctx context.Context, id string) error

    // 查询操作
    List(ctx context.Context, offset, limit int) ([]*entity.User, error)
    Count(ctx context.Context) (int64, error)
    Search(ctx context.Context, query string, offset, limit int) ([]*entity.User, error)

    // 业务查询
    GetActiveUsers(ctx context.Context, offset, limit int) ([]*entity.User, error)
    GetUsersByRole(ctx context.Context, role string, offset, limit int) ([]*entity.User, error)

    // 事务支持
    WithTx(ctx context.Context, fn func(repo UserRepository) error) error
}

应用服务层 #

命令处理 #

// internal/application/command/user_commands.go
package command

import (
    "context"
    "time"
)

// 创建用户命令
type CreateUserCommand struct {
    Username string `json:"username" validate:"required,min=3,max=50"`
    Email    string `json:"email" validate:"required,email"`
    Phone    string `json:"phone" validate:"required"`
    Password string `json:"password" validate:"required,min=8"`
}

// 更新用户资料命令
type UpdateUserProfileCommand struct {
    UserID    string    `json:"user_id" validate:"required"`
    FirstName string    `json:"first_name"`
    LastName  string    `json:"last_name"`
    Avatar    string    `json:"avatar"`
    Birthday  time.Time `json:"birthday"`
    Gender    int       `json:"gender"`
    Address   AddressCommand `json:"address"`
}

type AddressCommand struct {
    Street   string `json:"street"`
    City     string `json:"city"`
    Province string `json:"province"`
    Country  string `json:"country"`
    ZipCode  string `json:"zip_code"`
}

// 修改密码命令
type ChangePasswordCommand struct {
    UserID      string `json:"user_id" validate:"required"`
    OldPassword string `json:"old_password" validate:"required"`
    NewPassword string `json:"new_password" validate:"required,min=8"`
}

// 用户登录命令
type LoginCommand struct {
    Username string `json:"username" validate:"required"`
    Password string `json:"password" validate:"required"`
}

命令处理器 #

// internal/application/command/user_command_handler.go
package command

import (
    "context"
    "errors"

    "github.com/ecommerce/user-service/internal/domain/entity"
    "github.com/ecommerce/user-service/internal/domain/repository"
    "github.com/ecommerce/user-service/pkg/auth"
    "github.com/ecommerce/user-service/pkg/validator"
)

type UserCommandHandler struct {
    userRepo     repository.UserRepository
    eventBus     EventBus
    jwtService   *auth.JWTService
    validator    *validator.Validator
}

func NewUserCommandHandler(
    userRepo repository.UserRepository,
    eventBus EventBus,
    jwtService *auth.JWTService,
    validator *validator.Validator,
) *UserCommandHandler {
    return &UserCommandHandler{
        userRepo:   userRepo,
        eventBus:   eventBus,
        jwtService: jwtService,
        validator:  validator,
    }
}

// 处理创建用户命令
func (h *UserCommandHandler) HandleCreateUser(ctx context.Context, cmd *CreateUserCommand) (*CreateUserResult, error) {
    // 验证命令
    if err := h.validator.Validate(cmd); err != nil {
        return nil, err
    }

    // 检查用户名是否已存在
    if existingUser, _ := h.userRepo.GetByUsername(ctx, cmd.Username); existingUser != nil {
        return nil, errors.New("username already exists")
    }

    // 检查邮箱是否已存在
    if existingUser, _ := h.userRepo.GetByEmail(ctx, cmd.Email); existingUser != nil {
        return nil, errors.New("email already exists")
    }

    // 创建用户实体
    user, err := entity.NewUser(cmd.Username, cmd.Email, cmd.Phone, cmd.Password)
    if err != nil {
        return nil, err
    }

    // 保存用户
    if err := h.userRepo.Save(ctx, user); err != nil {
        return nil, err
    }

    // 发布领域事件
    for _, event := range user.GetEvents() {
        if err := h.eventBus.Publish(ctx, event); err != nil {
            // 记录日志,但不影响主流程
            log.Error("failed to publish event", err)
        }
    }

    user.ClearEvents()

    return &CreateUserResult{
        UserID:   user.ID,
        Username: user.Username,
        Email:    user.Email,
    }, nil
}

// 处理用户登录命令
func (h *UserCommandHandler) HandleLogin(ctx context.Context, cmd *LoginCommand) (*LoginResult, error) {
    // 验证命令
    if err := h.validator.Validate(cmd); err != nil {
        return nil, err
    }

    // 获取用户
    user, err := h.userRepo.GetByUsername(ctx, cmd.Username)
    if err != nil {
        return nil, errors.New("invalid username or password")
    }

    // 验证密码
    if !user.VerifyPassword(cmd.Password) {
        return nil, errors.New("invalid username or password")
    }

    // 检查用户状态
    if user.Status != entity.UserStatusActive {
        return nil, errors.New("user account is not active")
    }

    // 生成 JWT 令牌
    token, err := h.jwtService.GenerateToken(user)
    if err != nil {
        return nil, err
    }

    return &LoginResult{
        Token:    token,
        UserID:   user.ID,
        Username: user.Username,
        Roles:    user.Roles,
    }, nil
}

// 处理更新用户资料命令
func (h *UserCommandHandler) HandleUpdateUserProfile(ctx context.Context, cmd *UpdateUserProfileCommand) error {
    // 验证命令
    if err := h.validator.Validate(cmd); err != nil {
        return err
    }

    // 获取用户
    user, err := h.userRepo.GetByID(ctx, cmd.UserID)
    if err != nil {
        return err
    }

    // 更新资料
    profile := entity.Profile{
        FirstName: cmd.FirstName,
        LastName:  cmd.LastName,
        Avatar:    cmd.Avatar,
        Birthday:  cmd.Birthday,
        Gender:    entity.Gender(cmd.Gender),
        Address: entity.Address{
            Street:   cmd.Address.Street,
            City:     cmd.Address.City,
            Province: cmd.Address.Province,
            Country:  cmd.Address.Country,
            ZipCode:  cmd.Address.ZipCode,
        },
    }

    if err := user.UpdateProfile(profile); err != nil {
        return err
    }

    // 保存更新
    if err := h.userRepo.Update(ctx, user); err != nil {
        return err
    }

    // 发布领域事件
    for _, event := range user.GetEvents() {
        if err := h.eventBus.Publish(ctx, event); err != nil {
            log.Error("failed to publish event", err)
        }
    }

    user.ClearEvents()
    return nil
}

// 处理修改密码命令
func (h *UserCommandHandler) HandleChangePassword(ctx context.Context, cmd *ChangePasswordCommand) error {
    // 验证命令
    if err := h.validator.Validate(cmd); err != nil {
        return err
    }

    // 获取用户
    user, err := h.userRepo.GetByID(ctx, cmd.UserID)
    if err != nil {
        return err
    }

    // 修改密码
    if err := user.ChangePassword(cmd.OldPassword, cmd.NewPassword); err != nil {
        return err
    }

    // 保存更新
    if err := h.userRepo.Update(ctx, user); err != nil {
        return err
    }

    // 发布领域事件
    for _, event := range user.GetEvents() {
        if err := h.eventBus.Publish(ctx, event); err != nil {
            log.Error("failed to publish event", err)
        }
    }

    user.ClearEvents()
    return nil
}

// 结果类型
type CreateUserResult struct {
    UserID   string `json:"user_id"`
    Username string `json:"username"`
    Email    string `json:"email"`
}

type LoginResult struct {
    Token    string   `json:"token"`
    UserID   string   `json:"user_id"`
    Username string   `json:"username"`
    Roles    []string `json:"roles"`
}

查询处理 #

// internal/application/query/user_queries.go
package query

import (
    "context"
    "github.com/ecommerce/user-service/internal/domain/entity"
    "github.com/ecommerce/user-service/internal/domain/repository"
)

type UserQueryHandler struct {
    userRepo repository.UserRepository
}

func NewUserQueryHandler(userRepo repository.UserRepository) *UserQueryHandler {
    return &UserQueryHandler{
        userRepo: userRepo,
    }
}

// 获取用户详情
func (h *UserQueryHandler) GetUserByID(ctx context.Context, userID string) (*UserDTO, error) {
    user, err := h.userRepo.GetByID(ctx, userID)
    if err != nil {
        return nil, err
    }

    return h.toUserDTO(user), nil
}

// 获取用户列表
func (h *UserQueryHandler) GetUsers(ctx context.Context, offset, limit int) (*UserListResult, error) {
    users, err := h.userRepo.List(ctx, offset, limit)
    if err != nil {
        return nil, err
    }

    total, err := h.userRepo.Count(ctx)
    if err != nil {
        return nil, err
    }

    userDTOs := make([]*UserDTO, len(users))
    for i, user := range users {
        userDTOs[i] = h.toUserDTO(user)
    }

    return &UserListResult{
        Users: userDTOs,
        Total: total,
        Offset: offset,
        Limit:  limit,
    }, nil
}

// 搜索用户
func (h *UserQueryHandler) SearchUsers(ctx context.Context, query string, offset, limit int) (*UserListResult, error) {
    users, err := h.userRepo.Search(ctx, query, offset, limit)
    if err != nil {
        return nil, err
    }

    userDTOs := make([]*UserDTO, len(users))
    for i, user := range users {
        userDTOs[i] = h.toUserDTO(user)
    }

    return &UserListResult{
        Users: userDTOs,
        Total: int64(len(users)),
        Offset: offset,
        Limit:  limit,
    }, nil
}

// 转换为 DTO
func (h *UserQueryHandler) toUserDTO(user *entity.User) *UserDTO {
    return &UserDTO{
        ID:       user.ID,
        Username: user.Username,
        Email:    user.Email,
        Phone:    user.Phone,
        Profile: ProfileDTO{
            FirstName: user.Profile.FirstName,
            LastName:  user.Profile.LastName,
            Avatar:    user.Profile.Avatar,
            Birthday:  user.Profile.Birthday,
            Gender:    int(user.Profile.Gender),
            Address: AddressDTO{
                Street:   user.Profile.Address.Street,
                City:     user.Profile.Address.City,
                Province: user.Profile.Address.Province,
                Country:  user.Profile.Address.Country,
                ZipCode:  user.Profile.Address.ZipCode,
            },
        },
        Status:    int(user.Status),
        Roles:     user.Roles,
        CreatedAt: user.CreatedAt,
        UpdatedAt: user.UpdatedAt,
    }
}

// DTO 类型
type UserDTO struct {
    ID        string     `json:"id"`
    Username  string     `json:"username"`
    Email     string     `json:"email"`
    Phone     string     `json:"phone"`
    Profile   ProfileDTO `json:"profile"`
    Status    int        `json:"status"`
    Roles     []string   `json:"roles"`
    CreatedAt time.Time  `json:"created_at"`
    UpdatedAt time.Time  `json:"updated_at"`
}

type ProfileDTO struct {
    FirstName string     `json:"first_name"`
    LastName  string     `json:"last_name"`
    Avatar    string     `json:"avatar"`
    Birthday  time.Time  `json:"birthday"`
    Gender    int        `json:"gender"`
    Address   AddressDTO `json:"address"`
}

type AddressDTO struct {
    Street   string `json:"street"`
    City     string `json:"city"`
    Province string `json:"province"`
    Country  string `json:"country"`
    ZipCode  string `json:"zip_code"`
}

type UserListResult struct {
    Users  []*UserDTO `json:"users"`
    Total  int64      `json:"total"`
    Offset int        `json:"offset"`
    Limit  int        `json:"limit"`
}

基础设施层 #

数据库实现 #

// internal/infrastructure/persistence/user_repository_impl.go
package persistence

import (
    "context"
    "database/sql"
    "fmt"

    "github.com/ecommerce/user-service/internal/domain/entity"
    "github.com/ecommerce/user-service/internal/domain/repository"
    "gorm.io/gorm"
)

type userRepositoryImpl struct {
    db *gorm.DB
}

func NewUserRepository(db *gorm.DB) repository.UserRepository {
    return &userRepositoryImpl{db: db}
}

// 用户数据模型
type UserModel struct {
    ID        string    `gorm:"primaryKey;type:varchar(36)"`
    Username  string    `gorm:"uniqueIndex;type:varchar(50);not null"`
    Email     string    `gorm:"uniqueIndex;type:varchar(100);not null"`
    Phone     string    `gorm:"uniqueIndex;type:varchar(20);not null"`
    Password  string    `gorm:"type:varchar(255);not null"`
    FirstName string    `gorm:"type:varchar(50)"`
    LastName  string    `gorm:"type:varchar(50)"`
    Avatar    string    `gorm:"type:varchar(255)"`
    Birthday  time.Time `gorm:"type:date"`
    Gender    int       `gorm:"type:tinyint;default:0"`
    Street    string    `gorm:"type:varchar(255)"`
    City      string    `gorm:"type:varchar(100)"`
    Province  string    `gorm:"type:varchar(100)"`
    Country   string    `gorm:"type:varchar(100)"`
    ZipCode   string    `gorm:"type:varchar(20)"`
    Status    int       `gorm:"type:tinyint;default:1"`
    Roles     string    `gorm:"type:text"` // JSON 字符串
    CreatedAt time.Time `gorm:"autoCreateTime"`
    UpdatedAt time.Time `gorm:"autoUpdateTime"`
}

func (UserModel) TableName() string {
    return "users"
}

// 保存用户
func (r *userRepositoryImpl) Save(ctx context.Context, user *entity.User) error {
    model := r.toModel(user)
    return r.db.WithContext(ctx).Create(model).Error
}

// 根据 ID 获取用户
func (r *userRepositoryImpl) GetByID(ctx context.Context, id string) (*entity.User, error) {
    var model UserModel
    err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error
    if err != nil {
        if err == gorm.ErrRecordNotFound {
            return nil, repository.ErrUserNotFound
        }
        return nil, err
    }

    return r.toEntity(&model), nil
}

// 根据用户名获取用户
func (r *userRepositoryImpl) GetByUsername(ctx context.Context, username string) (*entity.User, error) {
    var model UserModel
    err := r.db.WithContext(ctx).Where("username = ?", username).First(&model).Error
    if err != nil {
        if err == gorm.ErrRecordNotFound {
            return nil, repository.ErrUserNotFound
        }
        return nil, err
    }

    return r.toEntity(&model), nil
}

// 根据邮箱获取用户
func (r *userRepositoryImpl) GetByEmail(ctx context.Context, email string) (*entity.User, error) {
    var model UserModel
    err := r.db.WithContext(ctx).Where("email = ?", email).First(&model).Error
    if err != nil {
        if err == gorm.ErrRecordNotFound {
            return nil, repository.ErrUserNotFound
        }
        return nil, err
    }

    return r.toEntity(&model), nil
}

// 更新用户
func (r *userRepositoryImpl) Update(ctx context.Context, user *entity.User) error {
    model := r.toModel(user)
    return r.db.WithContext(ctx).Save(model).Error
}

// 删除用户
func (r *userRepositoryImpl) Delete(ctx context.Context, id string) error {
    return r.db.WithContext(ctx).Where("id = ?", id).Delete(&UserModel{}).Error
}

// 获取用户列表
func (r *userRepositoryImpl) List(ctx context.Context, offset, limit int) ([]*entity.User, error) {
    var models []UserModel
    err := r.db.WithContext(ctx).Offset(offset).Limit(limit).Find(&models).Error
    if err != nil {
        return nil, err
    }

    users := make([]*entity.User, len(models))
    for i, model := range models {
        users[i] = r.toEntity(&model)
    }

    return users, nil
}

// 获取用户总数
func (r *userRepositoryImpl) Count(ctx context.Context) (int64, error) {
    var count int64
    err := r.db.WithContext(ctx).Model(&UserModel{}).Count(&count).Error
    return count, err
}

// 搜索用户
func (r *userRepositoryImpl) Search(ctx context.Context, query string, offset, limit int) ([]*entity.User, error) {
    var models []UserModel
    searchPattern := fmt.Sprintf("%%%s%%", query)

    err := r.db.WithContext(ctx).
        Where("username LIKE ? OR email LIKE ? OR first_name LIKE ? OR last_name LIKE ?",
            searchPattern, searchPattern, searchPattern, searchPattern).
        Offset(offset).Limit(limit).Find(&models).Error

    if err != nil {
        return nil, err
    }

    users := make([]*entity.User, len(models))
    for i, model := range models {
        users[i] = r.toEntity(&model)
    }

    return users, nil
}

// 事务支持
func (r *userRepositoryImpl) WithTx(ctx context.Context, fn func(repo repository.UserRepository) error) error {
    return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
        txRepo := &userRepositoryImpl{db: tx}
        return fn(txRepo)
    })
}

// 模型转换
func (r *userRepositoryImpl) toModel(user *entity.User) *UserModel {
    rolesJSON, _ := json.Marshal(user.Roles)

    return &UserModel{
        ID:        user.ID,
        Username:  user.Username,
        Email:     user.Email,
        Phone:     user.Phone,
        Password:  user.Password,
        FirstName: user.Profile.FirstName,
        LastName:  user.Profile.LastName,
        Avatar:    user.Profile.Avatar,
        Birthday:  user.Profile.Birthday,
        Gender:    int(user.Profile.Gender),
        Street:    user.Profile.Address.Street,
        City:      user.Profile.Address.City,
        Province:  user.Profile.Address.Province,
        Country:   user.Profile.Address.Country,
        ZipCode:   user.Profile.Address.ZipCode,
        Status:    int(user.Status),
        Roles:     string(rolesJSON),
        CreatedAt: user.CreatedAt,
        UpdatedAt: user.UpdatedAt,
    }
}

func (r *userRepositoryImpl) toEntity(model *UserModel) *entity.User {
    var roles []string
    json.Unmarshal([]byte(model.Roles), &roles)

    return &entity.User{
        ID:       model.ID,
        Username: model.Username,
        Email:    model.Email,
        Phone:    model.Phone,
        Password: model.Password,
        Profile: entity.Profile{
            FirstName: model.FirstName,
            LastName:  model.LastName,
            Avatar:    model.Avatar,
            Birthday:  model.Birthday,
            Gender:    entity.Gender(model.Gender),
            Address: entity.Address{
                Street:   model.Street,
                City:     model.City,
                Province: model.Province,
                Country:  model.Country,
                ZipCode:  model.ZipCode,
            },
        },
        Status:    entity.UserStatus(model.Status),
        Roles:     roles,
        CreatedAt: model.CreatedAt,
        UpdatedAt: model.UpdatedAt,
    }
}

表现层实现 #

HTTP 接口 #

// internal/presentation/http/user_handler.go
package http

import (
    "net/http"
    "strconv"

    "github.com/gin-gonic/gin"
    "github.com/ecommerce/user-service/internal/application/command"
    "github.com/ecommerce/user-service/internal/application/query"
)

type UserHandler struct {
    commandHandler *command.UserCommandHandler
    queryHandler   *query.UserQueryHandler
}

func NewUserHandler(
    commandHandler *command.UserCommandHandler,
    queryHandler *query.UserQueryHandler,
) *UserHandler {
    return &UserHandler{
        commandHandler: commandHandler,
        queryHandler:   queryHandler,
    }
}

// 注册路由
func (h *UserHandler) RegisterRoutes(r *gin.Engine) {
    api := r.Group("/api/v1")

    // 公开接口
    api.POST("/users/register", h.Register)
    api.POST("/users/login", h.Login)

    // 需要认证的接口
    auth := api.Group("/users")
    auth.Use(AuthMiddleware())
    {
        auth.GET("/:id", h.GetUser)
        auth.PUT("/:id/profile", h.UpdateProfile)
        auth.PUT("/:id/password", h.ChangePassword)
        auth.GET("", h.ListUsers)
        auth.GET("/search", h.SearchUsers)
    }
}

// 用户注册
func (h *UserHandler) Register(c *gin.Context) {
    var req command.CreateUserCommand
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    result, err := h.commandHandler.HandleCreateUser(c.Request.Context(), &req)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, result)
}

// 用户登录
func (h *UserHandler) Login(c *gin.Context) {
    var req command.LoginCommand
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    result, err := h.commandHandler.HandleLogin(c.Request.Context(), &req)
    if err != nil {
        c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, result)
}

// 获取用户信息
func (h *UserHandler) GetUser(c *gin.Context) {
    userID := c.Param("id")

    user, err := h.queryHandler.GetUserByID(c.Request.Context(), userID)
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
        return
    }

    c.JSON(http.StatusOK, user)
}

// 更新用户资料
func (h *UserHandler) UpdateProfile(c *gin.Context) {
    userID := c.Param("id")

    var req command.UpdateUserProfileCommand
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    req.UserID = userID

    if err := h.commandHandler.HandleUpdateUserProfile(c.Request.Context(), &req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "profile updated successfully"})
}

// 修改密码
func (h *UserHandler) ChangePassword(c *gin.Context) {
    userID := c.Param("id")

    var req command.ChangePasswordCommand
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    req.UserID = userID

    if err := h.commandHandler.HandleChangePassword(c.Request.Context(), &req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "password changed successfully"})
}

// 获取用户列表
func (h *UserHandler) ListUsers(c *gin.Context) {
    offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
    limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))

    result, err := h.queryHandler.GetUsers(c.Request.Context(), offset, limit)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, result)
}

// 搜索用户
func (h *UserHandler) SearchUsers(c *gin.Context) {
    query := c.Query("q")
    offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
    limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))

    result, err := h.queryHandler.SearchUsers(c.Request.Context(), query, offset, limit)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, result)
}

认证中间件 #

// internal/presentation/http/middleware.go
package http

import (
    "net/http"
    "strings"

    "github.com/gin-gonic/gin"
    "github.com/ecommerce/user-service/pkg/auth"
)

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "authorization header required"})
            c.Abort()
            return
        }

        tokenString := strings.TrimPrefix(authHeader, "Bearer ")
        if tokenString == authHeader {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid authorization header format"})
            c.Abort()
            return
        }

        jwtService := auth.NewJWTService([]byte("your-secret-key"), "user-service", time.Hour*24)
        claims, err := jwtService.ValidateToken(tokenString)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
            c.Abort()
            return
        }

        c.Set("user_id", claims.UserID)
        c.Set("username", claims.Username)
        c.Set("roles", claims.Roles)

        c.Next()
    }
}

通过本节的学习,我们完成了用户服务的完整实现,包括领域模型、应用服务、基础设施和表现层。下一节将实现商品服务。