Go语言通关指南:零基础玩转高并发编程(第Ⅴ部分)(第15章)-现代Go编程-项目实战开发(案例1:RESTful API服务开发)
文章目录
- Go语言通关指南:零基础玩转高并发编程(第Ⅴ部分)(第15章)-现代Go编程-项目实战开发(案例1:RESTful API服务开发)
- 1. 功能点与技术点展开
- 2. 完整代码实现
- 2.1 工程结构
- 2.2 文件说明与完整代码
- 2.2.1 文件:`main.go`
- 2.2.2 文件:`config/config.go`
- 2.2.3 文件:`controller/user_controller.go`
- 2.2.4 文件:`service/user_service.go`
- 2.2.5 文件:`dao/user_dao.go`
- 2.2.6 文件:`model/user.go`
- 2.2.7 文件:`middleware/auth.go`
- 2.2.8 文件:middleware/logging.go
- 2.2.9 文件:`utils/response.go`
- 2.2.10 文件:utils/jwt.go
- 2.2.11 文件:`tests/user_test.go`
- 2.2.12 文件:`docs/swagger.yaml`
- 3. 生产级实践总结
- 4. 注意事项
1. 功能点与技术点展开
1.1 用户注册
功能描述:用户通过邮箱和密码注册账号。
关键技术点:
- 邮箱格式校验:使用正则表达式验证邮箱格式。
- 密码强度校验:密码长度至少8位,包含大小写字母和数字。
- 密码加密存储:使用Bcrypt算法加密密码。
- 数据库操作:将用户信息存储到MySQL数据库。
1.2 用户登录
功能描述:用户通过邮箱和密码登录,返回JWT Token。
关键技术点:
- 密码校验:使用Bcrypt算法验证密码。
- JWT生成:生成包含用户ID的JWT Token,设置有效期。
- Token存储:将Token存储到Redis,用于后续鉴权。
1.3 获取用户信息
功能描述:通过用户ID查询用户信息。
关键技术点:
- JWT鉴权:从请求头中获取Token并验证有效性。
- 数据库查询:根据用户ID查询用户信息。
- 敏感信息过滤:过滤密码等敏感信息。
1.4 Token刷新
功能描述:用户通过旧Token获取新Token。
关键技术点:
- JWT验证:验证旧Token的有效性。
- 新Token生成:生成新的JWT Token。
- Token更新:将新Token存储到Redis。
1.5 用户注销
功能描述:用户注销,将Token加入黑名单。
关键技术点:
- Token黑名单:将Token存储到Redis黑名单中。
- 黑名单校验:在鉴权中间件中校验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文档使用说明
- 在线调试:
- 启动服务后,访问
http://localhost:8080/swagger/index.html,即可查看和调试API。
- 启动服务后,访问
- 生成文档:
- 使用
swag init命令生成Swagger文档,集成到项目中。
- 使用
- 安全性:
- 使用
BearerAuth验证JWT Token,确保API访问安全。
- 使用
3. 生产级实践总结
- 分层架构:严格分离Controller/Service/DAO,提高代码可维护性。
- 安全性:密码加密存储(Bcrypt)、JWT短期Token、Redis黑名单机制。
- 可观测性:集成Zap日志和Prometheus监控。
- 单元测试:覆盖核心业务逻辑,确保代码质量。
4. 注意事项
-
安全性:
- 密码必须加密存储,推荐使用Bcrypt算法。
- JWT Token应设置短期有效期,并通过Redis黑名单实现主动注销。
-
性能优化:
- 使用数据库连接池,避免频繁创建和关闭连接。
- Redis缓存热点数据(如用户信息),减少数据库查询压力。
-
可维护性:
- 采用分层架构(Controller/Service/DAO),确保代码清晰易维护。
- 统一响应格式和错误处理,提高代码一致性。
-
可观测性:
- 集成日志记录(如Zap)和性能监控(如Prometheus),便于问题排查和性能优化。

被折叠的 条评论
为什么被折叠?



