--- title: 综合实战项目 icon: mdi:shield-account date: 2025-12-23 category: - Go - 后端 - 工程化 - 实战项目 tag: - API - 用户认证 - 密码加密 - JWT - 综合实战 --- 整合 Gin、GORM、Viper、Zap 等框架,开发一个完整的用户注册/登录 API。这个项目包含用户认证、密码加密、JWT 令牌和数据库操作,是学习 Go Web 开发的完美案例。 --- # Go 综合实战:用户注册/登录 API 完整指南 这是一个完整的 Web 应用示例,整合了前面学到的所有知识:Gin、GORM、Viper、Zap、中间件等。 --- ## 一、项目结构 ``` go-auth-api/ ├── config/ │ ├── app.yaml │ ├── app.dev.yaml │ └── app.prod.yaml ├── logs/ │ └── app.log ├── main.go ├── config.go ├── logger.go ├── db.go ├── models.go ├── handlers.go ├── middleware.go ├── jwt.go ├── utils.go └── go.mod ``` --- ## 二、项目初始化 ### 2.1 创建项目 ```bash cd ~/GolandProjects mkdir go-auth-api && cd go-auth-api go mod init go-auth-api # 安装依赖 go get -u github.com/gin-gonic/gin go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite go get -u github.com/spf13/viper go get -u go.uber.org/zap go get -u github.com/golang-jwt/jwt/v4 go get -u golang.org/x/crypto go get -u gopkg.in/natefinch/lumberjack.v2 ``` > **版本说明**:代码兼容 Go 1.18+ 版本(建议 1.22+ 体验最佳性能) ### 2.2 创建目录 ```bash mkdir -p config logs ``` --- ## 三、配置文件 ### 3.1 config/app.yaml ```yaml app: name: AuthAPI version: 1.0.0 port: 8080 env: dev database: driver: sqlite path: auth.db jwt: secret: your-secret-key-change-in-production expire: 86400 # 24 小时 password: bcrypt_cost: 10 logging: level: info format: json ``` ### 3.2 config/app.prod.yaml ```yaml app: port: 80 env: prod logging: level: warn ``` --- ## 四、模型定义(models.go) ```go package main import ( "time" "gorm.io/gorm" ) type User struct { ID uint `gorm:"primaryKey" json:"id"` Name string `gorm:"size:100;not null" json:"name"` Email string `gorm:"size:100;unique;not null" json:"email"` Password string `gorm:"size:255;not null" json:"-"` // 不在 JSON 中显示 Phone string `gorm:"size:20" json:"phone,omitempty"` Age int `json:"age,omitempty"` Active bool `gorm:"default:true" json:"active"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `json:"-"` } func (User) TableName() string { return "users" } // 请求体 type RegisterRequest struct { Name string `json:"name" binding:"required"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=6"` Phone string `json:"phone" binding:"omitempty,len=11"` } type LoginRequest struct { Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required"` } type LoginResponse struct { Token string `json:"token"` User User `json:"user"` } ``` --- ## 五、数据库初始化(db.go) ```go package main import ( "fmt" "gorm.io/driver/sqlite" "gorm.io/gorm" ) var DB *gorm.DB func InitDB(cfg *Config) error { var dsn string if cfg.Database.Driver == "sqlite" { dsn = cfg.Database.Path } var err error DB, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{}) if err != nil { return fmt.Errorf("failed to connect database: %w", err) } // 自动迁移 if err = DB.AutoMigrate(&User{}); err != nil { return fmt.Errorf("failed to migrate database: %w", err) } Logger.Info("Database initialized successfully") return nil } ``` --- ## 六、JWT 和密码工具(jwt.go + utils.go) ### 6.1 jwt.go ```go package main import ( "fmt" "time" "github.com/golang-jwt/jwt/v4" ) type Claims struct { UserID uint `json:"user_id"` Email string `json:"email"` jwt.RegisteredClaims } func GenerateToken(userID uint, email string, secret string, expire int64) (string, error) { claims := Claims{ UserID: userID, Email: email, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expire) * time.Second)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString([]byte(secret)) if err != nil { return "", err } return tokenString, nil } func VerifyToken(tokenString string, secret string) (*Claims, error) { claims := &Claims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(secret), nil }) if err != nil { return nil, err } if !token.Valid { return nil, fmt.Errorf("invalid token") } return claims, nil } ``` ### 6.2 utils.go ```go package main import ( "golang.org/x/crypto/bcrypt" ) // 密码加密 func HashPassword(password string) (string, error) { hash, err := bcrypt.GenerateFromPassword([]byte(password), 10) if err != nil { return "", err } return string(hash), nil } // 验证密码 func VerifyPassword(hashedPassword, password string) bool { err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) return err == nil } // 检查邮箱是否已注册 func EmailExists(email string) bool { var count int64 DB.Model(&User{}).Where("email = ?", email).Count(&count) return count > 0 } ``` --- ## 七、中间件(middleware.go) ```go package main import ( "strings" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // 请求日志中间件 func LoggingMiddleware() gin.HandlerFunc { return func(c *gin.Context) { Logger.Info("Request", zap.String("method", c.Request.Method), zap.String("path", c.Request.URL.Path), zap.String("ip", c.ClientIP()), ) c.Next() Logger.Info("Response", zap.String("path", c.Request.URL.Path), zap.Int("status", c.Writer.Status()), ) } } // JWT 认证中间件 func AuthMiddleware(cfg *Config) gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { c.JSON(401, gin.H{"error": "Missing authorization header"}) c.Abort() return } parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { c.JSON(401, gin.H{"error": "Invalid authorization header"}) c.Abort() return } claims, err := VerifyToken(parts[1], cfg.JWT.Secret) if err != nil { Logger.Error("Token verification failed", zap.Error(err)) c.JSON(401, gin.H{"error": "Invalid token"}) c.Abort() return } // 将用户信息存储在上下文中 c.Set("user_id", claims.UserID) c.Set("email", claims.Email) c.Next() } } // CORS 中间件 func CORSMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } } ``` --- ## 八、业务逻辑处理器(handlers.go) ```go package main import ( "github.com/gin-gonic/gin" "go.uber.org/zap" ) // 注册用户 func Register(cfg *Config) gin.HandlerFunc { return func(c *gin.Context) { var req RegisterRequest if err := c.ShouldBindJSON(&req); err != nil { Logger.Error("Invalid registration request", zap.Error(err)) c.JSON(400, gin.H{"error": err.Error()}) return } // 检查邮箱是否已存在 if EmailExists(req.Email) { c.JSON(400, gin.H{"error": "Email already registered"}) return } // 密码加密 hashedPassword, err := HashPassword(req.Password) if err != nil { Logger.Error("Failed to hash password", zap.Error(err)) c.JSON(500, gin.H{"error": "Internal server error"}) return } // 创建用户 user := User{ Name: req.Name, Email: req.Email, Password: hashedPassword, Phone: req.Phone, } if err := DB.Create(&user).Error; err != nil { Logger.Error("Failed to create user", zap.Error(err)) c.JSON(500, gin.H{"error": "Failed to register user"}) return } Logger.Info("User registered successfully", zap.String("email", user.Email)) c.JSON(201, gin.H{ "message": "User registered successfully", "user_id": user.ID, }) } } // 用户登录 func Login(cfg *Config) gin.HandlerFunc { return func(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // 查找用户 var user User if err := DB.Where("email = ?", req.Email).First(&user).Error; err != nil { Logger.Warn("User not found", zap.String("email", req.Email)) c.JSON(401, gin.H{"error": "Invalid email or password"}) return } // 验证密码 if !VerifyPassword(user.Password, req.Password) { Logger.Warn("Invalid password", zap.String("email", req.Email)) c.JSON(401, gin.H{"error": "Invalid email or password"}) return } // 生成 JWT token token, err := GenerateToken(user.ID, user.Email, cfg.JWT.Secret, int64(cfg.JWT.Expire)) if err != nil { Logger.Error("Failed to generate token", zap.Error(err)) c.JSON(500, gin.H{"error": "Failed to generate token"}) return } Logger.Info("User logged in successfully", zap.String("email", user.Email)) c.JSON(200, LoginResponse{ Token: token, User: User{ ID: user.ID, Name: user.Name, Email: user.Email, Phone: user.Phone, Age: user.Age, }, }) } } // 获取用户信息 func GetProfile(c *gin.Context) { userID, _ := c.Get("user_id") var user User if err := DB.First(&user, userID.(uint)).Error; err != nil { c.JSON(404, gin.H{"error": "User not found"}) return } c.JSON(200, user) } // 更新用户信息 func UpdateProfile(c *gin.Context) { userID, _ := c.Get("user_id") var req struct { Name string `json:"name"` Phone string `json:"phone"` Age int `json:"age"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } if err := DB.Model(&User{}).Where("id = ?", userID.(uint)).Updates(req).Error; err != nil { Logger.Error("Failed to update user", zap.Error(err)) c.JSON(500, gin.H{"error": "Failed to update profile"}) return } Logger.Info("User profile updated", zap.Uint("user_id", userID.(uint))) c.JSON(200, gin.H{"message": "Profile updated successfully"}) } // 健康检查 func HealthCheck(c *gin.Context) { c.JSON(200, gin.H{ "status": "ok", "app": "AuthAPI", }) } ``` --- ## 九、配置加载(config.go) ```go package main import ( "fmt" "os" "github.com/spf13/viper" ) type Config struct { App struct { Name string `mapstructure:"name"` Version string `mapstructure:"version"` Port int `mapstructure:"port"` Env string `mapstructure:"env"` } `mapstructure:"app"` Database struct { Driver string `mapstructure:"driver"` Path string `mapstructure:"path"` } `mapstructure:"database"` JWT struct { Secret string `mapstructure:"secret"` Expire int `mapstructure:"expire"` } `mapstructure:"jwt"` Logging struct { Level string `mapstructure:"level"` Format string `mapstructure:"format"` } `mapstructure:"logging"` } var GlobalConfig *Config func LoadConfig() (*Config, error) { env := os.Getenv("GO_ENV") if env == "" { env = "dev" } viper.SetConfigName("app") viper.SetConfigType("yaml") viper.AddConfigPath("./config") if err := viper.ReadInConfig(); err != nil { return nil, fmt.Errorf("failed to read config: %w", err) } viper.SetConfigName("app." + env) viper.MergeInConfig() var cfg Config if err := viper.Unmarshal(&cfg); err != nil { return nil, fmt.Errorf("failed to unmarshal config: %w", err) } GlobalConfig = &cfg return &cfg, nil } ``` --- ## 十、日志初始化(logger.go) ```go package main import ( "os" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) var Logger *zap.Logger func InitLogger(env string) error { var level zapcore.Level switch env { case "prod": level = zapcore.WarnLevel case "test": level = zapcore.DebugLevel default: level = zapcore.InfoLevel } logFile := &lumberjack.Logger{ Filename: "logs/app.log", MaxSize: 100, MaxBackups: 10, MaxAge: 7, Compress: true, } encoderConfig := zapcore.EncoderConfig{ TimeKey: "ts", LevelKey: "level", MessageKey: "msg", CallerKey: "caller", EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } core := zapcore.NewCore( zapcore.NewJSONEncoder(encoderConfig), zapcore.NewMultiWriteSyncer( zapcore.AddSync(os.Stdout), zapcore.AddSync(logFile), ), level, ) Logger = zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(Logger) return nil } ``` --- ## 十一、主程序(main.go) ```go package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { // 加载配置 cfg, err := LoadConfig() if err != nil { panic(err) } // 初始化日志 if err = InitLogger(cfg.App.Env); err != nil { panic(err) } defer Logger.Sync() // 初始化数据库 if err = InitDB(cfg); err != nil { Logger.Fatal("Failed to initialize database", zap.Error(err)) } // 创建 Gin 应用 r := gin.Default() // 应用中间件 r.Use(LoggingMiddleware()) r.Use(CORSMiddleware()) // 公开路由 public := r.Group("/api") { public.GET("/health", HealthCheck) public.POST("/register", Register(cfg)) public.POST("/login", Login(cfg)) } // 受保护的路由 protected := r.Group("/api") protected.Use(AuthMiddleware(cfg)) { protected.GET("/profile", GetProfile) protected.PUT("/profile", UpdateProfile) } // 启动服务器 addr := fmt.Sprintf(":%d", cfg.App.Port) Logger.Info("Server starting", zap.String("app", cfg.App.Name), zap.Int("port", cfg.App.Port), zap.String("env", cfg.App.Env), ) if err = r.Run(addr); err != nil { Logger.Fatal("Server error", zap.Error(err)) } } ``` --- ## 十二、API 使用示例 ### 12.1 注册用户 ```bash curl -X POST http://localhost:8080/api/register \ -H "Content-Type: application/json" \ -d '{ "name": "Alice", "email": "alice@example.com", "password": "password123", "phone": "13800138000" }' # 响应 { "message": "User registered successfully", "user_id": 1 } ``` ### 12.2 用户登录 ```bash curl -X POST http://localhost:8080/api/login \ -H "Content-Type: application/json" \ -d '{ "email": "alice@example.com", "password": "password123" }' # 响应 { "token": "eyJhbGciOiJIUzI1NiIs...", "user": { "id": 1, "name": "Alice", "email": "alice@example.com", "phone": "13800138000" } } ``` ### 12.3 获取用户信息(需要认证) ```bash curl -X GET http://localhost:8080/api/profile \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." # 响应 { "id": 1, "name": "Alice", "email": "alice@example.com", "phone": "13800138000", "age": 0, "active": true, "created_at": "2025-12-23T14:30:45Z" } ``` --- ## 十三、项目验收清单 - ✅ 用户注册(密码加密、邮箱验证) - ✅ 用户登录(JWT 令牌生成) - ✅ 用户认证(JWT 验证中间件) - ✅ 用户信息管理(获取、更新) - ✅ 配置管理(多环境) - ✅ 日志记录(结构化、日志轮转) - ✅ 错误处理(友好的错误消息) - ✅ 代码组织(清晰的文件结构) --- ## 十四、进阶扩展方向 1. **添加邮箱验证** - 发送验证码验证邮箱 2. **实现刷新令牌** - 增加安全性 3. **添加速率限制** - 防止暴力破解 4. **用户权限管理** - 基于角色的访问控制 5. **社交登录** - 集成 OAuth2(GitHub、Google) 6. **单元测试** - 为关键业务逻辑编写测试 7. **Docker 打包** - 容器化部署 8. **CI/CD 流程** - 自动化测试和部署 --- 祝你编码愉快!🚀 这个项目整合了 Go Web 开发的所有核心知识,是学习和面试的好材料。