Files
learn-golang/Web开发/06go-auth-api/21综合实战项目.md
liumangmang b010f82221 feat(auth): 添加完整的用户认证API项目
- 实现用户注册、登录、JWT令牌认证功能
- 集成Gin、GORM、Viper、Zap等框架
- 添加密码加密、数据库操作、中间件等完整功能
- 配置多环境支持、日志轮转、CORS处理
- 创建完整的项目结构和配置文件体系
2025-12-30 18:00:42 +08:00

827 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 综合实战项目
icon: mdi:shield-account
date: 2025-12-23
category:
- Go
- 后端
- 工程化
- 实战项目
tag:
- API
- 用户认证
- 密码加密
- JWT
- 综合实战
---
整合 Gin、GORM、Viper、Zap 等框架,开发一个完整的用户注册/登录 API。这个项目包含用户认证、密码加密、JWT 令牌和数据库操作,是学习 Go Web 开发的完美案例。
<!-- more -->
---
# 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. **社交登录** - 集成 OAuth2GitHub、Google
6. **单元测试** - 为关键业务逻辑编写测试
7. **Docker 打包** - 容器化部署
8. **CI/CD 流程** - 自动化测试和部署
---
祝你编码愉快!🚀 这个项目整合了 Go Web 开发的所有核心知识,是学习和面试的好材料。