# 第四阶段:完整项目实践 - 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) ```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) ```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) ```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) ```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) ```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) ```yaml 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 ```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) ```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', 'admin@example.com', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Admin', 'User', TRUE) ON DUPLICATE KEY UPDATE username = username; ``` ## 测试策略 ### 单元测试 ```go 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: "test@example.com", 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: "new@example.com", Password: "password123", } existingUser := &model.User{ Username: req.Username, Email: "existing@example.com", } // 设置模拟期望 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: "test@example.com", 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) }) } ``` ### 集成测试 ```go 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: "test@example.com", 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: "test@example.com", 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)) } ``` ### 性能测试 ```go 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: "test@example.com", 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: "test@example.com", 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. 环境准备 ```bash # 克隆项目 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. 本地开发 ```bash # 启动数据库 docker-compose up -d db redis # 运行数据库迁移 go run cmd/migrate/main.go up # 启动应用 go run cmd/server/main.go ``` ### 3. Docker部署 ```bash # 构建并启动所有服务 docker-compose up -d # 查看日志 docker-compose logs -f app # 停止服务 docker-compose down ``` ### 4. API测试 ```bash # 用户注册 curl -X POST http://localhost:8080/api/v1/register \ -H "Content-Type: application/json" \ -d '{ "username": "testuser", "email": "test@example.com", "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开发工作奠定坚实的基础! ```