Go语言通关指南:零基础玩转高并发编程(第Ⅴ部分)(第15章)-项目实战开发(案例1:RESTful API服务开发)

Go语言通关指南:零基础玩转高并发编程(第Ⅴ部分)(第15章)-现代Go编程-项目实战开发(案例1:RESTful API服务开发)



1. 功能点与技术点展开


1.1 用户注册

功能描述:用户通过邮箱和密码注册账号。
关键技术点

  1. 邮箱格式校验:使用正则表达式验证邮箱格式。
  2. 密码强度校验:密码长度至少8位,包含大小写字母和数字。
  3. 密码加密存储:使用Bcrypt算法加密密码。
  4. 数据库操作:将用户信息存储到MySQL数据库。

1.2 用户登录

功能描述:用户通过邮箱和密码登录,返回JWT Token。
关键技术点

  1. 密码校验:使用Bcrypt算法验证密码。
  2. JWT生成:生成包含用户ID的JWT Token,设置有效期。
  3. Token存储:将Token存储到Redis,用于后续鉴权。

1.3 获取用户信息

功能描述:通过用户ID查询用户信息。
关键技术点

  1. JWT鉴权:从请求头中获取Token并验证有效性。
  2. 数据库查询:根据用户ID查询用户信息。
  3. 敏感信息过滤:过滤密码等敏感信息。

1.4 Token刷新

功能描述:用户通过旧Token获取新Token。
关键技术点

  1. JWT验证:验证旧Token的有效性。
  2. 新Token生成:生成新的JWT Token。
  3. Token更新:将新Token存储到Redis。

1.5 用户注销

功能描述:用户注销,将Token加入黑名单。
关键技术点

  1. Token黑名单:将Token存储到Redis黑名单中。
  2. 黑名单校验:在鉴权中间件中校验Token是否在黑名单中。

2. 完整代码实现

2.1 工程结构

.
├── main.go
├── config
│   └── config.go
├── controller
│   └── user_controller.go
├── service
│   └── user_service.go
├── dao
│   └── user_dao.go
├── model
│   └── user.go
├── middleware
│   ├── auth.go
│   └── logging.go
├── utils
│   ├── jwt.go
│   └── response.go
├── tests
│   └── user_test.go
└── docs
    └── swagger.yaml

2.2 文件说明与完整代码


2.2.1 文件:main.go

作用:程序入口,初始化配置、数据库、中间件和路由,启动HTTP服务。

package main

import (
    "github.com/gin-gonic/gin"
    "your_project/config"
    "your_project/controller"
    "your_project/middleware"
    "your_project/utils"
)

func main() {
    // 初始化配置
    config.InitConfig()

    // 初始化数据库和Redis
    utils.InitDB()
    utils.InitRedis()

    // 创建Gin引擎
    r := gin.Default()

    // 注册中间件
    r.Use(middleware.Logging()) // 日志中间件
    r.Use(middleware.Auth())    // JWT鉴权中间件

    // 注册路由
    r.POST("/register", controller.Register)    // 用户注册
    r.POST("/login", controller.Login)         // 用户登录
    r.GET("/user", controller.GetUserInfo)     // 获取用户信息
    r.POST("/refresh", controller.RefreshToken) // 刷新Token
    r.POST("/logout", controller.Logout)       // 用户注销

    // 启动HTTP服务
    r.Run(":8080")
}

2.2.2 文件:config/config.go

作用:加载YAML配置文件,提供全局配置访问接口。

package config

import (
    "github.com/spf13/viper"
)

func InitConfig() {
    viper.SetConfigName("config")   // 配置文件名(无扩展名)
    viper.SetConfigType("yaml")     // 配置文件类型
    viper.AddConfigPath(".")        // 配置文件路径
    viper.AutomaticEnv()            // 自动读取环境变量覆盖配置

    // 读取配置文件
    if err := viper.ReadInConfig(); err != nil {
        panic("Failed to load config: " + err.Error())
    }
}

// GetDatabaseURL 获取数据库连接URL
func GetDatabaseURL() string {
    return viper.GetString("database.url")
}

// GetRedisAddress 获取Redis地址
func GetRedisAddress() string {
    return viper.GetString("redis.address")
}

2.2.3 文件:controller/user_controller.go

作用:处理HTTP请求,调用服务层完成业务逻辑。

package controller

import (
    "github.com/gin-gonic/gin"
    "your_project/model"
    "your_project/service"
    "your_project/utils"
)

// Register 用户注册接口
func Register(c *gin.Context) {
    var user model.User
    if err := c.ShouldBindJSON(&user); err != nil {
        utils.ResponseError(c, "Invalid request format")
        return
    }

    if err := service.RegisterUser(&user); err != nil {
        utils.ResponseError(c, err.Error())
        return
    }

    utils.ResponseSuccess(c, "User registered successfully")
}

// Login 用户登录接口
func Login(c *gin.Context) {
    var req model.LoginRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        utils.ResponseError(c, "Invalid request format")
        return
    }

    token, err := service.LoginUser(req.Email, req.Password)
    if err != nil {
        utils.ResponseError(c, err.Error())
        return
    }

    utils.ResponseSuccess(c, gin.H{"token": token})
}

// GetUserInfo 获取用户信息接口
func GetUserInfo(c *gin.Context) {
    userID := c.MustGet("userID").(uint)
    user, err := service.GetUserByID(userID)
    if err != nil {
        utils.ResponseError(c, err.Error())
        return
    }
    utils.ResponseSuccess(c, user)
}

// RefreshToken 刷新Token接口
func RefreshToken(c *gin.Context) {
    userID := c.MustGet("userID").(uint)
    newToken, err := service.RefreshUserToken(userID)
    if err != nil {
        utils.ResponseError(c, err.Error())
        return
    }
    utils.ResponseSuccess(c, gin.H{"token": newToken})
}

// Logout 用户注销接口
func Logout(c *gin.Context) {
    token := c.GetHeader("Authorization")
    userID := c.MustGet("userID").(uint)
    if err := service.LogoutUser(userID, token); err != nil {
        utils.ResponseError(c, err.Error())
        return
    }
    utils.ResponseSuccess(c, "Logged out successfully")
}

2.2.4 文件:service/user_service.go

作用:实现用户注册、登录、注销等核心业务逻辑。

package service

import (
    "errors"
    "golang.org/x/crypto/bcrypt"
    "your_project/dao"
    "your_project/model"
    "your_project/utils"
)

// RegisterUser 用户注册逻辑
func RegisterUser(user *model.User) error {
    // 校验邮箱格式
    if !utils.ValidateEmail(user.Email) {
        return errors.New("invalid email format")
    }

    // 校验密码强度
    if !utils.ValidatePassword(user.Password) {
        return errors.New("password must be at least 8 characters")
    }

    // 密码加密
    hashedPassword, err := bcrypt.GenerateFromPassword(
        []byte(user.Password), bcrypt.DefaultCost,
    )
    if err != nil {
        return errors.New("failed to encrypt password")
    }
    user.Password = string(hashedPassword)

    // 保存用户
    return dao.CreateUser(user)
}

// LoginUser 用户登录逻辑
func LoginUser(email, password string) (string, error) {
    user, err := dao.GetUserByEmail(email)
    if err != nil {
        return "", errors.New("user not found")
    }

    // 校验密码
    if err := bcrypt.CompareHashAndPassword(
        []byte(user.Password), []byte(password),
    ); err != nil {
        return "", errors.New("invalid password")
    }

    // 生成JWT Token
    token, err := utils.GenerateJWT(user.ID)
    if err != nil {
        return "", errors.New("failed to generate token")
    }

    // 存储Token到Redis
    if err := dao.StoreToken(user.ID, token); err != nil {
        return "", errors.New("failed to store token")
    }

    return token, nil
}

// GetUserByID 获取用户信息
func GetUserByID(userID uint) (*model.User, error) {
    user, err := dao.GetUserByID(userID)
    if err != nil {
        return nil, err
    }
    user.Password = "" // 过滤敏感信息
    return user, nil
}

// RefreshUserToken 刷新Token
func RefreshUserToken(userID uint) (string, error) {
    token, err := utils.GenerateJWT(userID)
    if err != nil {
        return "", err
    }
    if err := dao.StoreToken(userID, token); err != nil {
        return "", err
    }
    return token, nil
}

// LogoutUser 用户注销
func LogoutUser(userID uint, token string) error {
    return dao.AddTokenToBlacklist(userID, token)
}

2.2.5 文件:dao/user_dao.go

作用:封装数据库和Redis操作,提供数据访问接口。

package dao

import (
    "context"
    "gorm.io/gorm"
    "your_project/model"
    "your_project/utils"
)

// CreateUser 创建用户
func CreateUser(user *model.User) error {
    return utils.DB.Create(user).Error
}

// GetUserByEmail 根据邮箱查询用户
func GetUserByEmail(email string) (*model.User, error) {
    var user model.User
    if err := utils.DB.Where("email = ?", email).First(&user).Error; err != nil {
        return nil, err
    }
    return &user, nil
}

// GetUserByID 根据用户ID查询用户
func GetUserByID(userID uint) (*model.User, error) {
    var user model.User
    if err := utils.DB.First(&user, userID).Error; err != nil {
        return nil, err
    }
    return &user, nil
}

// StoreToken 存储Token到Redis
func StoreToken(userID uint, token string) error {
    return utils.Redis.Set(
        context.Background(),
        token,
        userID,
        utils.TokenExpiration,
    ).Err()
}

// AddTokenToBlacklist 将Token加入黑名单
func AddTokenToBlacklist(userID uint, token string) error {
    return utils.Redis.Set(
        context.Background(),
        "blacklist:"+token,
        userID,
        utils.TokenExpiration,
    ).Err()
}

2.2.6 文件:model/user.go

作用:定义数据模型结构体。

package model

import "time"

// User 用户模型
type User struct {
    ID        uint      `gorm:"primaryKey" json:"id"`
    Email     string    `gorm:"unique;not null" json:"email"`
    Password  string    `gorm:"not null" json:"-"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// LoginRequest 登录请求模型
type LoginRequest struct {
    Email    string `json:"email" binding:"required"`
    Password string `json:"password" binding:"required"`
}

2.2.7 文件:middleware/auth.go

作用:JWT鉴权中间件,验证请求Token有效性。

package middleware

import (
    "github.com/gin-gonic/gin"
    "your_project/utils"
)

// Auth JWT鉴权中间件
func Auth() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 从Header中获取Token
        token := c.GetHeader("Authorization")
        if token == "" {
            utils.ResponseError(c, "Authorization header is required", 401)
            c.Abort()
            return
        }

        // 验证Token
        userID, err := utils.ValidateJWT(token)
        if err != nil {
            utils.ResponseError(c, "Invalid or expired token", 401)
            c.Abort()
            return
        }

        // 将用户ID存入上下文
        c.Set("userID", userID)
        c.Next()
    }
}

2.2.8 文件:middleware/logging.go

作用:日志记录中间件,记录请求和响应信息。

package middleware

import (
    "github.com/gin-gonic/gin"
    "go.uber.org/zap"
    "time"
    "your_project/utils"
)

func Logging() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next() // 处理请求

        // 记录日志
        utils.Logger.Info("HTTP Request",
            zap.String("method", c.Request.Method),
            zap.String("path", c.Request.URL.Path),
            zap.Int("status", c.Writer.Status()),
            zap.Duration("duration", time.Since(start)),
        )
    }
}

2.2.9 文件:utils/response.go

作用:统一HTTP响应格式。

package utils

import "github.com/gin-gonic/gin"

// 统一成功响应
func ResponseSuccess(c *gin.Context, data interface{}) {
    c.JSON(200, gin.H{
        "code": 0,
        "msg":  "success",
        "data": data,
    })
}

// 统一错误响应
func ResponseError(c *gin.Context, message string, statusCode ...int) {
    code := 400
    if len(statusCode) > 0 {
        code = statusCode[0]
    }
    c.JSON(code, gin.H{
        "code": code,
        "msg":  message,
        "data": nil,
    })
}

2.2.10 文件:utils/jwt.go

作用:JWT工具类,生成和验证Token。

package utils

import (
    "github.com/golang-jwt/jwt/v5"
    "time"
)

var JWTSecret = []byte("your_jwt_secret_key")

// 生成JWT Token
func GenerateJWT(userID uint) (string, error) {
    claims := jwt.MapClaims{
        "userID": userID,
        "exp":    time.Now().Add(24 * time.Hour).Unix(),
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(JWTSecret)
}

// 验证JWT Token
func ValidateJWT(tokenString string) (uint, error) {
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        return JWTSecret, nil
    })
    if err != nil {
        return 0, err
    }
    claims := token.Claims.(jwt.MapClaims)
    return uint(claims["userID"].(float64)), nil
}

2.2.11 文件:tests/user_test.go

作用:单元测试,覆盖核心业务逻辑。

package tests

import (
    "testing"
    "your_project/model"
    "your_project/service"
)

// TestRegisterUser 测试用户注册
func TestRegisterUser(t *testing.T) {
    user := &model.User{
        Email:    "test@example.com",
        Password: "StrongPassword123!",
    }

    if err := service.RegisterUser(user); err != nil {
        t.Fatalf("RegisterUser failed: %v", err)
    }
}

// TestLoginUser 测试用户登录
func TestLoginUser(t *testing.T) {
    token, err := service.LoginUser("test@example.com", "StrongPassword123!")
    if err != nil {
        t.Fatalf("LoginUser failed: %v", err)
    }
    if token == "" {
        t.Fatal("LoginUser returned empty token")
    }
}

// TestGetUserByID 测试获取用户信息
func TestGetUserByID(t *testing.T) {
    user, err := service.GetUserByID(1)
    if err != nil {
        t.Fatalf("GetUserByID failed: %v", err)
    }
    if user.Email != "test@example.com" {
        t.Fatal("GetUserByID returned incorrect user")
    }
}

2.2.12 文件:docs/swagger.yaml

作用:使用Swagger定义API文档,支持在线调试和文档生成。

openapi: 3.0.0
info:
  title: RESTful API服务开发
  description: 提供用户注册、登录、信息查询等功能的API文档。
  version: 1.0.0
servers:
  - url: http://localhost:8080
    description: 本地开发环境
paths:
  /register:
    post:
      summary: 用户注册
      description: 用户通过邮箱和密码注册账号。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
      responses:
        '200':
          description: 注册成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 0
                  msg:
                    type: string
                    example: "注册成功"
                  data:
                    type: object
                    nullable: true
        '400':
          description: 请求格式错误或邮箱已存在
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 400
                  msg:
                    type: string
                    example: "邮箱格式错误"
                  data:
                    type: object
                    nullable: true
  /login:
    post:
      summary: 用户登录
      description: 用户通过邮箱和密码登录,返回JWT Token。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: 登录成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 0
                  msg:
                    type: string
                    example: "登录成功"
                  data:
                    type: object
                    properties:
                      token:
                        type: string
                        example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
        '401':
          description: 邮箱或密码错误
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 401
                  msg:
                    type: string
                    example: "邮箱或密码错误"
                  data:
                    type: object
                    nullable: true
  /user:
    get:
      summary: 获取用户信息
      description: 通过JWT Token获取当前用户信息。
      security:
        - BearerAuth: []
      responses:
        '200':
          description: 获取成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 0
                  msg:
                    type: string
                    example: "获取成功"
                  data:
                    $ref: '#/components/schemas/User'
        '401':
          description: Token无效或已过期
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 401
                  msg:
                    type: string
                    example: "Token无效或已过期"
                  data:
                    type: object
                    nullable: true
  /refresh:
    post:
      summary: 刷新Token
      description: 通过旧Token获取新Token。
      security:
        - BearerAuth: []
      responses:
        '200':
          description: 刷新成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 0
                  msg:
                    type: string
                    example: "刷新成功"
                  data:
                    type: object
                    properties:
                      token:
                        type: string
                        example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
        '401':
          description: Token无效或已过期
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 401
                  msg:
                    type: string
                    example: "Token无效或已过期"
                  data:
                    type: object
                    nullable: true
  /logout:
    post:
      summary: 用户注销
      description: 用户注销,将Token加入黑名单。
      security:
        - BearerAuth: []
      responses:
        '200':
          description: 注销成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 0
                  msg:
                    type: string
                    example: "注销成功"
                  data:
                    type: object
                    nullable: true
        '401':
          description: Token无效或已过期
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 401
                  msg:
                    type: string
                    example: "Token无效或已过期"
                  data:
                    type: object
                    nullable: true
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    User:
      type: object
      properties:
        email:
          type: string
          example: "user@example.com"
        password:
          type: string
          example: "password123"
      required:
        - email
        - password
    LoginRequest:
      type: object
      properties:
        email:
          type: string
          example: "user@example.com"
        password:
          type: string
          example: "password123"
      required:
        - email
        - password

Swagger文档使用说明

  1. 在线调试
    • 启动服务后,访问http://localhost:8080/swagger/index.html,即可查看和调试API。
  2. 生成文档
    • 使用swag init命令生成Swagger文档,集成到项目中。
  3. 安全性
    • 使用BearerAuth验证JWT Token,确保API访问安全。

3. 生产级实践总结

  1. 分层架构:严格分离Controller/Service/DAO,提高代码可维护性。
  2. 安全性:密码加密存储(Bcrypt)、JWT短期Token、Redis黑名单机制。
  3. 可观测性:集成Zap日志和Prometheus监控。
  4. 单元测试:覆盖核心业务逻辑,确保代码质量。

4. 注意事项

  1. 安全性

    • 密码必须加密存储,推荐使用Bcrypt算法。
    • JWT Token应设置短期有效期,并通过Redis黑名单实现主动注销。
  2. 性能优化

    • 使用数据库连接池,避免频繁创建和关闭连接。
    • Redis缓存热点数据(如用户信息),减少数据库查询压力。
  3. 可维护性

    • 采用分层架构(Controller/Service/DAO),确保代码清晰易维护。
    • 统一响应格式和错误处理,提高代码一致性。
  4. 可观测性

    • 集成日志记录(如Zap)和性能监控(如Prometheus),便于问题排查和性能优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

双囍菜菜

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值