phase4-project.md 50 KB

第四阶段:完整项目实践 - RESTful API服务

学习目标

  • 掌握完整的Web应用开发流程
  • 理解RESTful API设计原则
  • 能够构建生产级别的Go应用
  • 掌握部署和运维技能

项目需求分析

用户管理系统功能

  • 用户注册、登录、注销
  • 用户信息管理(增删改查)
  • 权限控制与认证
  • 数据验证与错误处理
  • 日志记录与监控

技术栈选择

核心框架

  • Web框架: Gin/Echo (推荐Gin)
  • 数据库: MySQL/PostgreSQL
  • ORM: GORM
  • 认证: JWT
  • 配置管理: Viper
  • 日志: Zap

开发工具

  • 测试: Go test + testify
  • 文档: Swagger
  • 部署: Docker
  • 监控: Prometheus + Grafana

项目架构设计

目录结构

user-management/
├── cmd/
│   └── server/
│       └── main.go          # 应用入口
├── internal/
│   ├── config/              # 配置管理
│   ├── handler/             # HTTP处理器
│   ├── middleware/          # 中间件
│   ├── model/               # 数据模型
│   ├── repository/          # 数据访问层
│   ├── service/             # 业务逻辑层
│   └── utils/               # 工具函数
├── pkg/
│   ├── auth/                # 认证模块
│   ├── database/            # 数据库连接
│   └── logger/              # 日志模块
├── api/
│   └── docs/                # API文档
├── scripts/                 # 部署脚本
├── docker-compose.yml       # Docker配置
├── go.mod                   # 依赖管理
└── README.md               # 项目说明

核心代码实现

1. 配置管理 (config/config.go)

package config

import (
    "fmt"
    "os"
    "path/filepath"
    
    "github.com/spf13/viper"
)

type Config struct {
    Server   ServerConfig   `mapstructure:"server"`
    Database DatabaseConfig `mapstructure:"database"`
    JWT      JWTConfig      `mapstructure:"jwt"`
    Logger   LoggerConfig   `mapstructure:"logger"`
}

type ServerConfig struct {
    Port         string `mapstructure:"port"`
    Mode         string `mapstructure:"mode"`
    ReadTimeout  int    `mapstructure:"read_timeout"`
    WriteTimeout int    `mapstructure:"write_timeout"`
}

type DatabaseConfig struct {
    Host     string `mapstructure:"host"`
    Port     string `mapstructure:"port"`
    User     string `mapstructure:"user"`
    Password string `mapstructure:"password"`
    DBName   string `mapstructure:"dbname"`
    SSLMode  string `mapstructure:"sslmode"`
}

type JWTConfig struct {
    Secret string `mapstructure:"secret"`
    Expire int    `mapstructure:"expire"`
}

type LoggerConfig struct {
    Level  string `mapstructure:"level"`
    Format string `mapstructure:"format"`
    Output string `mapstructure:"output"`
}

// Load 加载配置文件
func Load(configPath string) (*Config, error) {
    // 设置配置文件名和路径
    if configPath == "" {
        configPath = "./config.yaml"
    }
    
    // 获取配置文件的绝对路径
    absPath, err := filepath.Abs(configPath)
    if err != nil {
        return nil, fmt.Errorf("获取配置文件绝对路径失败: %w", err)
    }
    
    // 检查配置文件是否存在
    if _, err := os.Stat(absPath); os.IsNotExist(err) {
        return nil, fmt.Errorf("配置文件不存在: %s", absPath)
    }
    
    // 设置配置文件名和路径
    viper.SetConfigFile(absPath)
    
    // 设置环境变量前缀
    viper.SetEnvPrefix("APP")
    viper.AutomaticEnv()
    
    // 读取配置文件
    if err := viper.ReadInConfig(); err != nil {
        return nil, fmt.Errorf("读取配置文件失败: %w", err)
    }
    
    // 解析配置到结构体
    var config Config
    if err := viper.Unmarshal(&config); err != nil {
        return nil, fmt.Errorf("解析配置失败: %w", err)
    }
    
    // 设置默认值
    setDefaults(&config)
    
    // 验证配置
    if err := validate(&config); err != nil {
        return nil, fmt.Errorf("配置验证失败: %w", err)
    }
    
    return &config, nil
}

// setDefaults 设置默认配置值
func setDefaults(config *Config) {
    if config.Server.Port == "" {
        config.Server.Port = "8080"
    }
    if config.Server.Mode == "" {
        config.Server.Mode = "debug"
    }
    if config.Server.ReadTimeout == 0 {
        config.Server.ReadTimeout = 30
    }
    if config.Server.WriteTimeout == 0 {
        config.Server.WriteTimeout = 30
    }
    if config.Database.SSLMode == "" {
        config.Database.SSLMode = "disable"
    }
    if config.JWT.Expire == 0 {
        config.JWT.Expire = 24 // 24小时
    }
    if config.Logger.Level == "" {
        config.Logger.Level = "info"
    }
    if config.Logger.Format == "" {
        config.Logger.Format = "json"
    }
    if config.Logger.Output == "" {
        config.Logger.Output = "stdout"
    }
}

// validate 验证配置
func validate(config *Config) error {
    if config.JWT.Secret == "" {
        return fmt.Errorf("JWT密钥不能为空")
    }
    if config.Database.Host == "" {
        return fmt.Errorf("数据库主机不能为空")
    }
    if config.Database.User == "" {
        return fmt.Errorf("数据库用户名不能为空")
    }
    if config.Database.DBName == "" {
        return fmt.Errorf("数据库名不能为空")
    }
    return nil
}

// GetDSN 获取数据库连接字符串
func (c *DatabaseConfig) GetDSN() string {
    return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
        c.User, c.Password, c.Host, c.Port, c.DBName)
}

2. 数据模型 (model/user.go)

package model

import (
    "time"
    "gorm.io/gorm"
)

// User 用户模型
type User struct {
    ID        uint           `gorm:"primaryKey" json:"id"`
    Username  string         `gorm:"uniqueIndex;not null;size:50" json:"username" binding:"required,min=3,max=50"`
    Email     string         `gorm:"uniqueIndex;not null;size:100" json:"email" binding:"required,email"`
    Password  string         `gorm:"not null;size:255" json:"-" binding:"required,min=6"`
    FirstName string         `gorm:"size:50" json:"first_name"`
    LastName  string         `gorm:"size:50" json:"last_name"`
    Avatar   string         `gorm:"size:255" json:"avatar"`
    IsActive bool           `gorm:"default:true" json:"is_active"`
    LastLogin *time.Time    `json:"last_login"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}

// TableName 指定表名
func (User) TableName() string {
    return "users"
}

// BeforeCreate GORM钩子 - 创建前
func (u *User) BeforeCreate(tx *gorm.DB) error {
    now := time.Now()
    u.CreatedAt = now
    u.UpdatedAt = now
    return nil
}

// BeforeUpdate GORM钩子 - 更新前
func (u *User) BeforeUpdate(tx *gorm.DB) error {
    u.UpdatedAt = time.Now()
    return nil
}

// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
    Username  string `json:"username" binding:"required,min=3,max=50"`
    Email     string `json:"email" binding:"required,email"`
    Password  string `json:"password" binding:"required,min=6"`
    FirstName string `json:"first_name" binding:"max=50"`
    LastName  string `json:"last_name" binding:"max=50"`
}

// UpdateUserRequest 更新用户请求
type UpdateUserRequest struct {
    Username  *string `json:"username" binding:"omitempty,min=3,max=50"`
    Email     *string `json:"email" binding:"omitempty,email"`
    FirstName *string `json:"first_name" binding:"omitempty,max=50"`
    LastName  *string `json:"last_name" binding:"omitempty,max=50"`
    Avatar    *string `json:"avatar"`
    IsActive  *bool   `json:"is_active"`
}

// ChangePasswordRequest 修改密码请求
type ChangePasswordRequest struct {
    OldPassword string `json:"old_password" binding:"required"`
    NewPassword string `json:"new_password" binding:"required,min=6"`
}

// LoginRequest 登录请求
type LoginRequest struct {
    Username string `json:"username" binding:"required"`
    Password string `json:"password" binding:"required"`
}

// UserResponse 用户响应
type UserResponse struct {
    ID        uint       `json:"id"`
    Username  string     `json:"username"`
    Email     string     `json:"email"`
    FirstName string     `json:"first_name"`
    LastName  string     `json:"last_name"`
    Avatar    string     `json:"avatar"`
    IsActive  bool       `json:"is_active"`
    LastLogin *time.Time `json:"last_login"`
    CreatedAt time.Time  `json:"created_at"`
    UpdatedAt time.Time  `json:"updated_at"`
}

// LoginResponse 登录响应
type LoginResponse struct {
    Token string       `json:"token"`
    User  UserResponse `json:"user"`
}

// UserListResponse 用户列表响应
type UserListResponse struct {
    Users []UserResponse `json:"users"`
    Total int64          `json:"total"`
    Page  int            `json:"page"`
    Size  int            `json:"size"`
}

// ToResponse 转换为响应结构
func (u *User) ToResponse() UserResponse {
    return UserResponse{
        ID:        u.ID,
        Username:  u.Username,
        Email:     u.Email,
        FirstName: u.FirstName,
        LastName:  u.LastName,
        Avatar:    u.Avatar,
        IsActive:  u.IsActive,
        LastLogin: u.LastLogin,
        CreatedAt: u.CreatedAt,
        UpdatedAt: u.UpdatedAt,
    }
}

// UpdateLastLogin 更新最后登录时间
func (u *User) UpdateLastLogin() {
    now := time.Now()
    u.LastLogin = &now
}

3. 业务逻辑层 (service/user_service.go)

package service

import (
    "errors"
    "fmt"
    "time"
    
    "user-management/internal/model"
    "user-management/internal/repository"
    "user-management/pkg/logger"
    "golang.org/x/crypto/bcrypt"
)

// UserService 用户服务
type UserService struct {
    userRepo repository.UserRepository
    logger   logger.Logger
}

// NewUserService 创建用户服务
func NewUserService(repo repository.UserRepository, logger logger.Logger) *UserService {
    return &UserService{
        userRepo: repo,
        logger:   logger,
    }
}

// CreateUser 创建用户
func (s *UserService) CreateUser(req model.CreateUserRequest) (*model.UserResponse, error) {
    // 检查用户名是否已存在
    existingUser, err := s.userRepo.FindByUsername(req.Username)
    if err == nil && existingUser != nil {
        return nil, errors.New("用户名已存在")
    }
    
    // 检查邮箱是否已存在
    existingUser, err = s.userRepo.FindByEmail(req.Email)
    if err == nil && existingUser != nil {
        return nil, errors.New("邮箱已存在")
    }
    
    // 密码加密
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
    if err != nil {
        s.logger.Error("密码加密失败", "error", err)
        return nil, fmt.Errorf("密码加密失败: %w", err)
    }
    
    // 创建用户
    user := &model.User{
        Username:  req.Username,
        Email:     req.Email,
        Password:  string(hashedPassword),
        FirstName: req.FirstName,
        LastName:  req.LastName,
        IsActive:  true,
    }
    
    if err := s.userRepo.Create(user); err != nil {
        s.logger.Error("创建用户失败", "username", req.Username, "error", err)
        return nil, fmt.Errorf("创建用户失败: %w", err)
    }
    
    s.logger.Info("用户创建成功", "username", req.Username, "id", user.ID)
    return &user.ToResponse(), nil
}

// Authenticate 用户认证
func (s *UserService) Authenticate(req model.LoginRequest) (*model.User, error) {
    // 查找用户
    user, err := s.userRepo.FindByUsername(req.Username)
    if err != nil {
        s.logger.Warn("用户登录失败", "username", req.Username, "reason", "用户不存在")
        return nil, errors.New("用户名或密码错误")
    }
    
    // 检查用户是否激活
    if !user.IsActive {
        s.logger.Warn("用户登录失败", "username", req.Username, "reason", "用户未激活")
        return nil, errors.New("用户账户已被禁用")
    }
    
    // 验证密码
    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
        s.logger.Warn("用户登录失败", "username", req.Username, "reason", "密码错误")
        return nil, errors.New("用户名或密码错误")
    }
    
    // 更新最后登录时间
    user.UpdateLastLogin()
    if err := s.userRepo.Update(user); err != nil {
        s.logger.Error("更新最后登录时间失败", "username", req.Username, "error", err)
        // 不返回错误,因为这不是关键操作
    }
    
    s.logger.Info("用户登录成功", "username", req.Username, "id", user.ID)
    return user, nil
}

// GetUserByID 根据ID获取用户
func (s *UserService) GetUserByID(id uint) (*model.UserResponse, error) {
    user, err := s.userRepo.FindByID(id)
    if err != nil {
        s.logger.Error("获取用户失败", "id", id, "error", err)
        return nil, fmt.Errorf("获取用户失败: %w", err)
    }
    
    return &user.ToResponse(), nil
}

// UpdateUser 更新用户信息
func (s *UserService) UpdateUser(id uint, req model.UpdateUserRequest) (*model.UserResponse, error) {
    // 获取现有用户
    user, err := s.userRepo.FindByID(id)
    if err != nil {
        s.logger.Error("获取用户失败", "id", id, "error", err)
        return nil, fmt.Errorf("获取用户失败: %w", err)
    }
    
    // 检查用户名是否已存在(如果要更新用户名)
    if req.Username != nil && *req.Username != user.Username {
        existingUser, err := s.userRepo.FindByUsername(*req.Username)
        if err == nil && existingUser != nil && existingUser.ID != id {
            return nil, errors.New("用户名已存在")
        }
        user.Username = *req.Username
    }
    
    // 检查邮箱是否已存在(如果要更新邮箱)
    if req.Email != nil && *req.Email != user.Email {
        existingUser, err := s.userRepo.FindByEmail(*req.Email)
        if err == nil && existingUser != nil && existingUser.ID != id {
            return nil, errors.New("邮箱已存在")
        }
        user.Email = *req.Email
    }
    
    // 更新其他字段
    if req.FirstName != nil {
        user.FirstName = *req.FirstName
    }
    if req.LastName != nil {
        user.LastName = *req.LastName
    }
    if req.Avatar != nil {
        user.Avatar = *req.Avatar
    }
    if req.IsActive != nil {
        user.IsActive = *req.IsActive
    }
    
    // 保存更新
    if err := s.userRepo.Update(user); err != nil {
        s.logger.Error("更新用户失败", "id", id, "error", err)
        return nil, fmt.Errorf("更新用户失败: %w", err)
    }
    
    s.logger.Info("用户更新成功", "id", id, "username", user.Username)
    return &user.ToResponse(), nil
}

// DeleteUser 删除用户
func (s *UserService) DeleteUser(id uint) error {
    // 检查用户是否存在
    _, err := s.userRepo.FindByID(id)
    if err != nil {
        s.logger.Error("获取用户失败", "id", id, "error", err)
        return fmt.Errorf("获取用户失败: %w", err)
    }
    
    // 删除用户
    if err := s.userRepo.Delete(id); err != nil {
        s.logger.Error("删除用户失败", "id", id, "error", err)
        return fmt.Errorf("删除用户失败: %w", err)
    }
    
    s.logger.Info("用户删除成功", "id", id)
    return nil
}

// ListUsers 获取用户列表
func (s *UserService) ListUsers(page, size int) (*model.UserListResponse, error) {
    if page <= 0 {
        page = 1
    }
    if size <= 0 || size > 100 {
        size = 10
    }
    
    offset := (page - 1) * size
    
    users, total, err := s.userRepo.FindAll(offset, size)
    if err != nil {
        s.logger.Error("获取用户列表失败", "error", err)
        return nil, fmt.Errorf("获取用户列表失败: %w", err)
    }
    
    // 转换为响应格式
    userResponses := make([]model.UserResponse, len(users))
    for i, user := range users {
        userResponses[i] = user.ToResponse()
    }
    
    return &model.UserListResponse{
        Users: userResponses,
        Total: total,
        Page:  page,
        Size:  size,
    }, nil
}

// ChangePassword 修改密码
func (s *UserService) ChangePassword(id uint, req model.ChangePasswordRequest) error {
    // 获取用户
    user, err := s.userRepo.FindByID(id)
    if err != nil {
        s.logger.Error("获取用户失败", "id", id, "error", err)
        return fmt.Errorf("获取用户失败: %w", err)
    }
    
    // 验证旧密码
    if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword)); err != nil {
        s.logger.Warn("修改密码失败", "id", id, "reason", "旧密码错误")
        return errors.New("旧密码错误")
    }
    
    // 加密新密码
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
    if err != nil {
        s.logger.Error("密码加密失败", "id", id, "error", err)
        return fmt.Errorf("密码加密失败: %w", err)
    }
    
    // 更新密码
    user.Password = string(hashedPassword)
    if err := s.userRepo.Update(user); err != nil {
        s.logger.Error("更新密码失败", "id", id, "error", err)
        return fmt.Errorf("更新密码失败: %w", err)
    }
    
    s.logger.Info("密码修改成功", "id", id)
    return nil
}

4. HTTP处理器 (handler/user_handler.go)

package handler

import (
    "net/http"
    "strconv"
    
    "user-management/internal/model"
    "user-management/internal/service"
    "user-management/pkg/logger"
    "user-management/pkg/response"
    "github.com/gin-gonic/gin"
)

// UserHandler 用户处理器
type UserHandler struct {
    userService *service.UserService
    authService *service.AuthService
    logger      logger.Logger
}

// NewUserHandler 创建用户处理器
func NewUserHandler(userService *service.UserService, authService *service.AuthService, logger logger.Logger) *UserHandler {
    return &UserHandler{
        userService: userService,
        authService: authService,
        logger:      logger,
    }
}

// Register 用户注册
// @Summary 用户注册
// @Tags users
// @Accept json
// @Produce json
// @Param user body model.CreateUserRequest true "用户信息"
// @Success 201 {object} response.Response{data=model.UserResponse}
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/v1/register [post]
func (h *UserHandler) Register(c *gin.Context) {
    var req model.CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        h.logger.Error("注册请求参数错误", "error", err)
        response.Error(c, http.StatusBadRequest, "请求参数错误", err.Error())
        return
    }
    
    user, err := h.userService.CreateUser(req)
    if err != nil {
        h.logger.Error("创建用户失败", "error", err)
        response.Error(c, http.StatusInternalServerError, "创建用户失败", err.Error())
        return
    }
    
    h.logger.Info("用户注册成功", "username", req.Username)
    response.Success(c, http.StatusCreated, "注册成功", user)
}

// Login 用户登录
// @Summary 用户登录
// @Tags users
// @Accept json
// @Produce json
// @Param credentials body model.LoginRequest true "登录凭证"
// @Success 200 {object} response.Response{data=model.LoginResponse}
// @Failure 400 {object} response.Response
// @Failure 401 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/v1/login [post]
func (h *UserHandler) Login(c *gin.Context) {
    var req model.LoginRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        h.logger.Error("登录请求参数错误", "error", err)
        response.Error(c, http.StatusBadRequest, "请求参数错误", err.Error())
        return
    }
    
    user, err := h.userService.Authenticate(req)
    if err != nil {
        h.logger.Warn("用户登录失败", "username", req.Username, "error", err)
        response.Error(c, http.StatusUnauthorized, "登录失败", "用户名或密码错误")
        return
    }
    
    token, err := h.authService.GenerateToken(user.ID)
    if err != nil {
        h.logger.Error("生成令牌失败", "userID", user.ID, "error", err)
        response.Error(c, http.StatusInternalServerError, "登录失败", "生成令牌失败")
        return
    }
    
    loginResp := model.LoginResponse{
        Token: token,
        User:  user.ToResponse(),
    }
    
    h.logger.Info("用户登录成功", "username", req.Username)
    response.Success(c, http.StatusOK, "登录成功", loginResp)
}

// GetProfile 获取当前用户信息
// @Summary 获取当前用户信息
// @Tags users
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Success 200 {object} response.Response{data=model.UserResponse}
// @Failure 401 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/v1/profile [get]
func (h *UserHandler) GetProfile(c *gin.Context) {
    userID, exists := c.Get("userID")
    if !exists {
        response.Error(c, http.StatusUnauthorized, "未授权", "用户ID不存在")
        return
    }
    
    user, err := h.userService.GetUserByID(userID.(uint))
    if err != nil {
        h.logger.Error("获取用户信息失败", "userID", userID, "error", err)
        response.Error(c, http.StatusInternalServerError, "获取用户信息失败", err.Error())
        return
    }
    
    response.Success(c, http.StatusOK, "获取成功", user)
}

// UpdateProfile 更新当前用户信息
// @Summary 更新当前用户信息
// @Tags users
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param user body model.UpdateUserRequest true "用户信息"
// @Success 200 {object} response.Response{data=model.UserResponse}
// @Failure 400 {object} response.Response
// @Failure 401 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/v1/profile [put]
func (h *UserHandler) UpdateProfile(c *gin.Context) {
    userID, exists := c.Get("userID")
    if !exists {
        response.Error(c, http.StatusUnauthorized, "未授权", "用户ID不存在")
        return
    }
    
    var req model.UpdateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        h.logger.Error("更新用户请求参数错误", "userID", userID, "error", err)
        response.Error(c, http.StatusBadRequest, "请求参数错误", err.Error())
        return
    }
    
    user, err := h.userService.UpdateUser(userID.(uint), req)
    if err != nil {
        h.logger.Error("更新用户失败", "userID", userID, "error", err)
        response.Error(c, http.StatusInternalServerError, "更新用户失败", err.Error())
        return
    }
    
    h.logger.Info("用户更新成功", "userID", userID)
    response.Success(c, http.StatusOK, "更新成功", user)
}

// ChangePassword 修改密码
// @Summary 修改密码
// @Tags users
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param password body model.ChangePasswordRequest true "密码信息"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.Response
// @Failure 401 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/v1/change-password [post]
func (h *UserHandler) ChangePassword(c *gin.Context) {
    userID, exists := c.Get("userID")
    if !exists {
        response.Error(c, http.StatusUnauthorized, "未授权", "用户ID不存在")
        return
    }
    
    var req model.ChangePasswordRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        h.logger.Error("修改密码请求参数错误", "userID", userID, "error", err)
        response.Error(c, http.StatusBadRequest, "请求参数错误", err.Error())
        return
    }
    
    if err := h.userService.ChangePassword(userID.(uint), req); err != nil {
        h.logger.Error("修改密码失败", "userID", userID, "error", err)
        response.Error(c, http.StatusBadRequest, "修改密码失败", err.Error())
        return
    }
    
    h.logger.Info("密码修改成功", "userID", userID)
    response.Success(c, http.StatusOK, "密码修改成功", nil)
}

// GetUser 获取用户信息(管理员)
// @Summary 获取用户信息(管理员)
// @Tags users
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param id path int true "用户ID"
// @Success 200 {object} response.Response{data=model.UserResponse}
// @Failure 400 {object} response.Response
// @Failure 401 {object} response.Response
// @Failure 404 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/v1/users/{id} [get]
func (h *UserHandler) GetUser(c *gin.Context) {
    idStr := c.Param("id")
    id, err := strconv.ParseUint(idStr, 10, 32)
    if err != nil {
        response.Error(c, http.StatusBadRequest, "请求参数错误", "无效的用户ID")
        return
    }
    
    user, err := h.userService.GetUserByID(uint(id))
    if err != nil {
        h.logger.Error("获取用户失败", "id", id, "error", err)
        response.Error(c, http.StatusNotFound, "用户不存在", err.Error())
        return
    }
    
    response.Success(c, http.StatusOK, "获取成功", user)
}

// ListUsers 获取用户列表(管理员)
// @Summary 获取用户列表(管理员)
// @Tags users
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param page query int false "页码" default(1)
// @Param size query int false "每页数量" default(10)
// @Success 200 {object} response.Response{data=model.UserListResponse}
// @Failure 400 {object} response.Response
// @Failure 401 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/v1/users [get]
func (h *UserHandler) ListUsers(c *gin.Context) {
    page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
    size, _ := strconv.Atoi(c.DefaultQuery("size", "10"))
    
    userList, err := h.userService.ListUsers(page, size)
    if err != nil {
        h.logger.Error("获取用户列表失败", "error", err)
        response.Error(c, http.StatusInternalServerError, "获取用户列表失败", err.Error())
        return
    }
    
    response.Success(c, http.StatusOK, "获取成功", userList)
}

// DeleteUser 删除用户(管理员)
// @Summary 删除用户(管理员)
// @Tags users
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param id path int true "用户ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.Response
// @Failure 401 {object} response.Response
// @Failure 404 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /api/v1/users/{id} [delete]
func (h *UserHandler) DeleteUser(c *gin.Context) {
    idStr := c.Param("id")
    id, err := strconv.ParseUint(idStr, 10, 32)
    if err != nil {
        response.Error(c, http.StatusBadRequest, "请求参数错误", "无效的用户ID")
        return
    }
    
    if err := h.userService.DeleteUser(uint(id)); err != nil {
        h.logger.Error("删除用户失败", "id", id, "error", err)
        response.Error(c, http.StatusInternalServerError, "删除用户失败", err.Error())
        return
    }
    
    h.logger.Info("用户删除成功", "id", id)
    response.Success(c, http.StatusOK, "删除成功", nil)
}

5. 中间件 (middleware/auth.go)

package middleware

import (
    "net/http"
    "strings"
    
    "user-management/pkg/auth"
    "user-management/pkg/logger"
    "user-management/pkg/response"
    "github.com/gin-gonic/gin"
)

// AuthMiddleware JWT认证中间件
func AuthMiddleware(jwtService *auth.JWTService, logger logger.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            logger.Warn("缺少认证令牌", "path", c.Request.URL.Path)
            response.Error(c, http.StatusUnauthorized, "未授权", "缺少认证令牌")
            c.Abort()
            return
        }
        
        parts := strings.Split(authHeader, " ")
        if len(parts) != 2 || parts[0] != "Bearer" {
            logger.Warn("令牌格式错误", "path", c.Request.URL.Path)
            response.Error(c, http.StatusUnauthorized, "未授权", "令牌格式错误")
            c.Abort()
            return
        }
        
        token := parts[1]
        claims, err := jwtService.ValidateToken(token)
        if err != nil {
            logger.Warn("无效令牌", "path", c.Request.URL.Path, "error", err)
            response.Error(c, http.StatusUnauthorized, "未授权", "无效令牌")
            c.Abort()
            return
        }
        
        // 将用户信息存储到上下文
        c.Set("userID", claims.UserID)
        c.Set("username", claims.Username)
        c.Next()
    }
}

// AdminMiddleware 管理员权限中间件
func AdminMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 这里简化处理,实际应用中应该检查用户角色
        // 可以从数据库或JWT中获取用户角色信息
        userID, exists := c.Get("userID")
        if !exists {
            response.Error(c, http.StatusUnauthorized, "未授权", "用户ID不存在")
            c.Abort()
            return
        }
        
        // 简化处理:假设ID为1的用户是管理员
        if userID.(uint) != 1 {
            response.Error(c, http.StatusForbidden, "权限不足", "需要管理员权限")
            c.Abort()
            return
        }
        
        c.Next()
    }
}

// LoggerMiddleware 日志中间件
func LoggerMiddleware(logger logger.Logger) gin.HandlerFunc {
    return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
        logger.Info("HTTP请求",
            "method", param.Method,
            "path", param.Path,
            "status", param.StatusCode,
            "latency", param.Latency,
            "client_ip", param.ClientIP,
            "user_agent", param.Request.UserAgent(),
            "error", param.ErrorMessage,
        )
        return ""
    })
}

// RecoveryMiddleware 恢复中间件
func RecoveryMiddleware(logger logger.Logger) gin.HandlerFunc {
    return gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
        logger.Error("HTTP请求panic",
            "method", c.Request.Method,
            "path", c.Request.URL.Path,
            "error", recovered,
            "stack", gin.Stack(),
        )
        
        response.Error(c, http.StatusInternalServerError, "服务器内部错误", "请求处理过程中发生错误")
        c.Abort()
    })
}

// CORSMiddleware CORS中间件
func CORSMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Header("Access-Control-Allow-Origin", "*")
        c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
        c.Header("Access-Control-Expose-Headers", "Content-Length")
        c.Header("Access-Control-Allow-Credentials", "true")
        
        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(http.StatusNoContent)
            return
        }
        
        c.Next()
    }
}

// RateLimitMiddleware 简单的限流中间件
func RateLimitMiddleware() gin.HandlerFunc {
    // 这里简化处理,实际应用中应该使用更复杂的限流算法
    // 可以使用Redis等存储请求计数
    return func(c *gin.Context) {
        // 简化处理:不做实际限流
        c.Next()
    }
}

API接口设计

用户管理接口

方法 路径 描述 认证
POST /api/v1/register 用户注册
POST /api/v1/login 用户登录
GET /api/v1/users 获取用户列表
GET /api/v1/users/:id 获取用户详情
PUT /api/v1/users/:id 更新用户信息
DELETE /api/v1/users/:id 删除用户

部署配置

Docker配置 (docker-compose.yml)

version: '3.8'

services:
  app:
    build: 
      context: .
      dockerfile: Dockerfile
    container_name: user-management-app
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - APP_SERVER_PORT=8080
      - APP_SERVER_MODE=release
      - APP_DATABASE_HOST=db
      - APP_DATABASE_PORT=3306
      - APP_DATABASE_USER=user
      - APP_DATABASE_PASSWORD=password
      - APP_DATABASE_DBNAME=user_management
      - APP_DATABASE_SSLMODE=disable
      - APP_JWT_SECRET=your-secret-key-change-in-production
      - APP_JWT_EXPIRE=24
      - APP_LOGGER_LEVEL=info
      - APP_LOGGER_FORMAT=json
      - APP_LOGGER_OUTPUT=stdout
    depends_on:
      db:
        condition: service_healthy
    networks:
      - app-network

  db:
    image: mysql:8.0
    container_name: user-management-db
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=user_management
      - MYSQL_USER=user
      - MYSQL_PASSWORD=password
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql
      - ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    container_name: user-management-redis
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      timeout: 3s
      retries: 5
    networks:
      - app-network

  adminer:
    image: adminer
    container_name: user-management-adminer
    restart: unless-stopped
    ports:
      - "8081:8080"
    environment:
      - ADMINER_DEFAULT_SERVER=db
    depends_on:
      - db
    networks:
      - app-network

volumes:
  db_data:
    driver: local
  redis_data:
    driver: local

networks:
  app-network:
    driver: bridge

Dockerfile

# 构建阶段
FROM golang:1.19-alpine AS builder

# 设置工作目录
WORKDIR /app

# 安装必要的包
RUN apk add --no-cache git ca-certificates tzdata

# 复制go mod文件
COPY go.mod go.sum ./

# 下载依赖
RUN go mod download

# 复制源代码
COPY . .

# 构建应用
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main cmd/server/main.go

# 运行阶段
FROM alpine:latest

# 安装ca-certificates以支持HTTPS请求
RUN apk --no-cache add ca-certificates tzdata

# 创建非root用户
RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup

# 设置工作目录
WORKDIR /app

# 从构建阶段复制二进制文件
COPY --from=builder /app/main .

# 复制配置文件
COPY --from=builder /app/config ./config

# 更改文件所有者
RUN chown -R appuser:appgroup /app

# 切换到非root用户
USER appuser

# 暴露端口
EXPOSE 8080

# 运行应用
CMD ["./main"]

数据库初始化脚本 (scripts/init.sql)

-- 创建数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS user_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 使用数据库
USE user_management;

-- 创建用户表(如果不存在)
CREATE TABLE IF NOT EXISTS users (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    avatar VARCHAR(255),
    is_active BOOLEAN DEFAULT TRUE,
    last_login TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL,
    INDEX idx_username (username),
    INDEX idx_email (email),
    INDEX idx_deleted_at (deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- 插入默认管理员用户(密码:admin123)
INSERT INTO users (username, email, password, first_name, last_name, is_active) 
VALUES ('admin', '[email protected]', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Admin', 'User', TRUE)
ON DUPLICATE KEY UPDATE username = username;

测试策略

单元测试

package service

import (
    "errors"
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
    
    "user-management/internal/model"
    "user-management/mocks"
)

func TestUserService_CreateUser(t *testing.T) {
    // 创建模拟仓库
    mockRepo := new(mocks.UserRepository)
    mockLogger := new(mocks.Logger)
    
    service := NewUserService(mockRepo, mockLogger)
    
    // 测试用例1:成功创建用户
    t.Run("成功创建用户", func(t *testing.T) {
        req := model.CreateUserRequest{
            Username:  "testuser",
            Email:     "[email protected]",
            Password:  "password123",
            FirstName: "Test",
            LastName:  "User",
        }
        
        // 设置模拟期望
        mockRepo.On("FindByUsername", req.Username).Return(nil, errors.New("not found"))
        mockRepo.On("FindByEmail", req.Email).Return(nil, errors.New("not found"))
        mockRepo.On("Create", mock.AnythingOfType("*model.User")).Return(nil)
        mockLogger.On("Info", mock.Anything, mock.Anything, mock.Anything).Return()
        
        // 执行测试
        user, err := service.CreateUser(req)
        
        // 断言
        assert.NoError(t, err)
        assert.NotNil(t, user)
        assert.Equal(t, req.Username, user.Username)
        assert.Equal(t, req.Email, user.Email)
        
        // 验证模拟调用
        mockRepo.AssertExpectations(t)
    })
    
    // 测试用例2:用户名已存在
    t.Run("用户名已存在", func(t *testing.T) {
        req := model.CreateUserRequest{
            Username: "existinguser",
            Email:    "[email protected]",
            Password: "password123",
        }
        
        existingUser := &model.User{
            Username: req.Username,
            Email:    "[email protected]",
        }
        
        // 设置模拟期望
        mockRepo.On("FindByUsername", req.Username).Return(existingUser, nil)
        mockLogger.On("Error", mock.Anything, mock.Anything, mock.Anything).Return()
        
        // 执行测试
        user, err := service.CreateUser(req)
        
        // 断言
        assert.Error(t, err)
        assert.Nil(t, user)
        assert.Contains(t, err.Error(), "用户名已存在")
        
        // 验证模拟调用
        mockRepo.AssertExpectations(t)
    })
}

func TestUserService_Authenticate(t *testing.T) {
    // 创建模拟仓库
    mockRepo := new(mocks.UserRepository)
    mockLogger := new(mocks.Logger)
    
    service := NewUserService(mockRepo, mockLogger)
    
    // 测试用例1:成功认证
    t.Run("成功认证", func(t *testing.T) {
        req := model.LoginRequest{
            Username: "testuser",
            Password: "password123",
        }
        
        hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
        user := &model.User{
            ID:       1,
            Username: req.Username,
            Email:    "[email protected]",
            Password: string(hashedPassword),
            IsActive: true,
        }
        
        // 设置模拟期望
        mockRepo.On("FindByUsername", req.Username).Return(user, nil)
        mockRepo.On("Update", mock.AnythingOfType("*model.User")).Return(nil)
        mockLogger.On("Info", mock.Anything, mock.Anything, mock.Anything).Return()
        
        // 执行测试
        result, err := service.Authenticate(req)
        
        // 断言
        assert.NoError(t, err)
        assert.NotNil(t, result)
        assert.Equal(t, user.ID, result.ID)
        assert.Equal(t, user.Username, result.Username)
        
        // 验证模拟调用
        mockRepo.AssertExpectations(t)
    })
    
    // 测试用例2:用户不存在
    t.Run("用户不存在", func(t *testing.T) {
        req := model.LoginRequest{
            Username: "nonexistentuser",
            Password: "password123",
        }
        
        // 设置模拟期望
        mockRepo.On("FindByUsername", req.Username).Return(nil, errors.New("not found"))
        mockLogger.On("Warn", mock.Anything, mock.Anything, mock.Anything).Return()
        
        // 执行测试
        result, err := service.Authenticate(req)
        
        // 断言
        assert.Error(t, err)
        assert.Nil(t, result)
        assert.Contains(t, err.Error(), "用户名或密码错误")
        
        // 验证模拟调用
        mockRepo.AssertExpectations(t)
    })
}

集成测试

package integration

import (
    "bytes"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"
    
    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/suite"
    
    "user-management/internal/config"
    "user-management/internal/handler"
    "user-management/internal/service"
    "user-management/internal/model"
    "user-management/pkg/auth"
    "user-management/pkg/database"
    "user-management/pkg/logger"
)

type UserAPITestSuite struct {
    suite.Suite
    router      *gin.Engine
    db          *gorm.DB
    userService *service.UserService
    authService *service.AuthService
    userHandler *handler.UserHandler
}

func (suite *UserAPITestSuite) SetupSuite() {
    // 设置测试数据库
    db, err := database.NewTestDB()
    suite.Require().NoError(err)
    suite.db = db
    
    // 自动迁移
    err = db.AutoMigrate(&model.User{})
    suite.Require().NoError(err)
    
    // 创建服务
    testLogger := logger.NewTestLogger()
    jwtService := auth.NewJWTService("test-secret", 24)
    userRepo := repository.NewUserRepository(db)
    suite.userService = service.NewUserService(userRepo, testLogger)
    suite.authService = service.NewAuthService(jwtService, userRepo, testLogger)
    suite.userHandler = handler.NewUserHandler(suite.userService, suite.authService, testLogger)
    
    // 设置路由
    suite.router = gin.New()
    api := suite.router.Group("/api/v1")
    {
        api.POST("/register", suite.userHandler.Register)
        api.POST("/login", suite.userHandler.Login)
        
        // 需要认证的路由
        authorized := api.Group("")
        authorized.Use(middleware.AuthMiddleware(jwtService, testLogger))
        {
            authorized.GET("/profile", suite.userHandler.GetProfile)
            authorized.PUT("/profile", suite.userHandler.UpdateProfile)
            authorized.POST("/change-password", suite.userHandler.ChangePassword)
        }
        
        // 需要管理员权限的路由
        admin := authorized.Group("")
        admin.Use(middleware.AdminMiddleware())
        {
            admin.GET("/users", suite.userHandler.ListUsers)
            admin.GET("/users/:id", suite.userHandler.GetUser)
            admin.DELETE("/users/:id", suite.userHandler.DeleteUser)
        }
    }
}

func (suite *UserAPITestSuite) TearDownSuite() {
    // 清理测试数据库
    sqlDB, _ := suite.db.DB()
    sqlDB.Close()
}

func (suite *UserAPITestSuite) TestUserAPI_Register() {
    // 准备测试数据
    req := model.CreateUserRequest{
        Username:  "testuser",
        Email:     "[email protected]",
        Password:  "password123",
        FirstName: "Test",
        LastName:  "User",
    }
    
    reqBody, _ := json.Marshal(req)
    
    // 发送请求
    w := httptest.NewRecorder()
    request, _ := http.NewRequest("POST", "/api/v1/register", bytes.NewBuffer(reqBody))
    request.Header.Set("Content-Type", "application/json")
    suite.router.ServeHTTP(w, request)
    
    // 断言
    assert.Equal(suite.T(), http.StatusCreated, w.Code)
    
    var response map[string]interface{}
    err := json.Unmarshal(w.Body.Bytes(), &response)
    assert.NoError(suite.T(), err)
    assert.Equal(suite.T(), float64(201), response["code"])
    assert.Equal(suite.T(), "注册成功", response["message"])
    assert.NotNil(suite.T(), response["data"])
}

func (suite *UserAPITestSuite) TestUserAPI_Login() {
    // 先创建用户
    hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
    user := model.User{
        Username: "testuser",
        Email:    "[email protected]",
        Password: string(hashedPassword),
        IsActive: true,
    }
    suite.db.Create(&user)
    
    // 准备登录请求
    req := model.LoginRequest{
        Username: "testuser",
        Password: "password123",
    }
    
    reqBody, _ := json.Marshal(req)
    
    // 发送请求
    w := httptest.NewRecorder()
    request, _ := http.NewRequest("POST", "/api/v1/login", bytes.NewBuffer(reqBody))
    request.Header.Set("Content-Type", "application/json")
    suite.router.ServeHTTP(w, request)
    
    // 断言
    assert.Equal(suite.T(), http.StatusOK, w.Code)
    
    var response map[string]interface{}
    err := json.Unmarshal(w.Body.Bytes(), &response)
    assert.NoError(suite.T(), err)
    assert.Equal(suite.T(), float64(200), response["code"])
    assert.Equal(suite.T(), "登录成功", response["message"])
    
    data := response["data"].(map[string]interface{})
    assert.NotNil(suite.T(), data["token"])
    assert.NotNil(suite.T(), data["user"])
}

func TestUserAPITestSuite(t *testing.T) {
    suite.Run(t, new(UserAPITestSuite))
}

性能测试

package benchmark

import (
    "testing"
    
    "user-management/internal/model"
    "user-management/internal/service"
    "user-management/mocks"
    "github.com/stretchr/testify/mock"
)

func BenchmarkUserCreation(b *testing.B) {
    // 创建模拟仓库
    mockRepo := new(mocks.UserRepository)
    mockLogger := new(mocks.Logger)
    
    service := NewUserService(mockRepo, mockLogger)
    
    // 设置模拟期望
    mockRepo.On("FindByUsername", mock.AnythingOfType("string")).Return(nil, errors.New("not found"))
    mockRepo.On("FindByEmail", mock.AnythingOfType("string")).Return(nil, errors.New("not found"))
    mockRepo.On("Create", mock.AnythingOfType("*model.User")).Return(nil)
    mockLogger.On("Info", mock.Anything, mock.Anything, mock.Anything).Return()
    
    // 重置计时器
    b.ResetTimer()
    
    // 运行基准测试
    for i := 0; i < b.N; i++ {
        req := model.CreateUserRequest{
            Username:  "testuser",
            Email:     "[email protected]",
            Password:  "password123",
            FirstName: "Test",
            LastName:  "User",
        }
        
        _, _ = service.CreateUser(req)
    }
}

func BenchmarkUserAuthentication(b *testing.B) {
    // 创建模拟仓库
    mockRepo := new(mocks.UserRepository)
    mockLogger := new(mocks.Logger)
    
    service := NewUserService(mockRepo, mockLogger)
    
    // 准备测试数据
    hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
    user := &model.User{
        ID:       1,
        Username: "testuser",
        Email:    "[email protected]",
        Password: string(hashedPassword),
        IsActive: true,
    }
    
    // 设置模拟期望
    mockRepo.On("FindByUsername", "testuser").Return(user, nil)
    mockRepo.On("Update", mock.AnythingOfType("*model.User")).Return(nil)
    mockLogger.On("Info", mock.Anything, mock.Anything, mock.Anything).Return()
    
    // 重置计时器
    b.ResetTimer()
    
    // 运行基准测试
    for i := 0; i < b.N; i++ {
        req := model.LoginRequest{
            Username: "testuser",
            Password: "password123",
        }
        
        _, _ = service.Authenticate(req)
    }
}

评估标准

功能完整性 (40%)

  • 所有API接口正常工作
  • 错误处理完整
  • 数据验证有效

代码质量 (30%)

  • 代码结构清晰
  • 遵循Go最佳实践
  • 测试覆盖率达标

性能与安全 (20%)

  • 响应时间合理
  • 安全措施到位
  • 资源使用优化

文档与部署 (10%)

  • API文档完整
  • 部署流程顺畅
  • 监控配置完善

时间安排 (4-5周)

第1周:项目搭建与基础功能

  • 项目结构设计
  • 数据库设计
  • 基础CRUD功能

第2周:认证与权限

  • JWT认证实现
  • 权限控制
  • 中间件开发

第3周:高级功能

  • 数据验证
  • 错误处理
  • 日志记录

第4周:测试与优化

  • 单元测试
  • 性能优化
  • 安全加固

第5周:部署与文档

  • Docker部署
  • API文档
  • 监控配置

项目启动指南

1. 环境准备

# 克隆项目
git clone https://github.com/your-username/user-management.git
cd user-management

# 安装依赖
go mod download

# 复制配置文件
cp config/config.example.yaml config/config.yaml

2. 本地开发

# 启动数据库
docker-compose up -d db redis

# 运行数据库迁移
go run cmd/migrate/main.go up

# 启动应用
go run cmd/server/main.go

3. Docker部署

# 构建并启动所有服务
docker-compose up -d

# 查看日志
docker-compose logs -f app

# 停止服务
docker-compose down

4. API测试

# 用户注册
curl -X POST http://localhost:8080/api/v1/register \
  -H "Content-Type: application/json" \
  -d '{
    "username": "testuser",
    "email": "[email protected]",
    "password": "password123",
    "first_name": "Test",
    "last_name": "User"
  }'

# 用户登录
curl -X POST http://localhost:8080/api/v1/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "testuser",
    "password": "password123"
  }'

# 获取用户信息(需要认证)
curl -X GET http://localhost:8080/api/v1/profile \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

项目扩展建议

1. 功能扩展

  • 用户角色与权限管理
  • 邮箱验证与密码重置
  • 用户头像上传
  • 用户活动日志
  • 批量操作功能

2. 技术优化

  • 添加Redis缓存
  • 实现API限流
  • 添加监控指标
  • 实现分布式会话
  • 添加API版本控制

3. 安全增强

  • 实现CSRF保护
  • 添加输入验证中间件
  • 实现SQL注入防护
  • 添加XSS防护
  • 实现HTTPS强制

4. 性能优化

  • 数据库查询优化
  • 添加分页缓存
  • 实现连接池
  • 添加CDN支持
  • 实现异步任务处理

项目成果

完成本项目后,您将拥有:

  1. 一个完整的RESTful API服务
  2. 生产级别的Go应用开发经验
  3. 完整的开发-测试-部署流程
  4. 可复用的项目模板
  5. 微服务架构设计能力
  6. 容器化部署经验
  7. 性能优化与安全防护知识

这个项目将为您后续的Go开发工作奠定坚实的基础! ```