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()
}
}
通过本节的学习,我们完成了用户服务的完整实现,包括领域模型、应用服务、基础设施和表现层。下一节将实现商品服务。