当前位置: 首页 > article >正文

15分钟学 Go 实战项目五 :简单电子商务网站(3W字完整例子)

简单的电子商务网站开发实战

项目概述

目标

  • 实现用户注册登录功能
  • 开发商品浏览和搜索功能
  • 实现购物车管理
  • 完成订单处理流程

技术栈

类别技术选择说明
Web框架Gin高性能HTTP框架
数据库MySQL存储用户和商品信息
缓存Redis购物车和会话管理
ORMGORM数据库操作
认证JWT用户身份验证

一、项目结构

在这里插入图片描述

二、核心代码实现

1. 数据库模型定义

package model

import (
    "time"
    "gorm.io/gorm"
)

// User 用户模型
type User struct {
    gorm.Model
    Username string `gorm:"unique;not null" json:"username"`
    Password string `gorm:"not null" json:"-"`
    Email    string `gorm:"unique;not null" json:"email"`
    Role     string `gorm:"default:'user'" json:"role"`
}

// Product 商品模型
type Product struct {
    gorm.Model
    Name        string  `gorm:"not null" json:"name"`
    Description string  `json:"description"`
    Price       float64 `gorm:"not null" json:"price"`
    Stock       int     `gorm:"not null" json:"stock"`
    CategoryID  uint    `json:"category_id"`
}

// CartItem 购物车项目
type CartItem struct {
    gorm.Model
    UserID    uint    `gorm:"not null" json:"user_id"`
    ProductID uint    `gorm:"not null" json:"product_id"`
    Quantity  int     `gorm:"not null" json:"quantity"`
    Product   Product `gorm:"foreignKey:ProductID" json:"product"`
}

// Order 订单
type Order struct {
    gorm.Model
    UserID      uint       `gorm:"not null" json:"user_id"`
    TotalAmount float64    `gorm:"not null" json:"total_amount"`
    Status      string     `gorm:"default:'pending'" json:"status"`
    Items       []OrderItem `gorm:"foreignKey:OrderID" json:"items"`
}

// OrderItem 订单项目
type OrderItem struct {
    gorm.Model
    OrderID   uint    `gorm:"not null" json:"order_id"`
    ProductID uint    `gorm:"not null" json:"product_id"`
    Quantity  int     `gorm:"not null" json:"quantity"`
    Price     float64 `gorm:"not null" json:"price"`
    Product   Product `gorm:"foreignKey:ProductID" json:"product"`
}

2. 用户认证中间件

package middleware

import (
    "github.com/gin-gonic/gin"
    "github.com/dgrijalva/jwt-go"
    "net/http"
    "strings"
    "time"
)

const (
    secretKey = "your-secret-key"
)

// Claims JWT claims结构
type Claims struct {
    UserID uint   `json:"user_id"`
    Role   string `json:"role"`
    jwt.StandardClaims
}

// GenerateToken 生成JWT token
func GenerateToken(userID uint, role string) (string, error) {
    claims := Claims{
        UserID: userID,
        Role:   role,
        StandardClaims: jwt.StandardClaims{
            ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
            IssuedAt:  time.Now().Unix(),
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString([]byte(secretKey))
}

// AuthMiddleware 认证中间件
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is required"})
            c.Abort()
            return
        }

        tokenString := strings.Replace(authHeader, "Bearer ", "", 1)
        claims := &Claims{}

        token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
            return []byte(secretKey), nil
        })

        if err != nil || !token.Valid {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
            c.Abort()
            return
        }

        // 将用户信息存储到上下文
        c.Set("userID", claims.UserID)
        c.Set("role", claims.Role)
        c.Next()
    }
}

// AdminRequired 管理员权限中间件
func AdminRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        role, exists := c.Get("role")
        if !exists || role != "admin" {
            c.JSON(http.StatusForbidden, gin.H{"error": "Admin privileges required"})
            c.Abort()
            return
        }
        c.Next()
    }
}

3. 购物车服务实现

package service

import (
    "errors"
    "github.com/your/project/internal/model"
    "gorm.io/gorm"
)

type CartService struct {
    db *gorm.DB
}

func NewCartService(db *gorm.DB) *CartService {
    return &CartService{db: db}
}

// AddToCart 添加商品到购物车
func (s *CartService) AddToCart(userID uint, productID uint, quantity int) error {
    // 检查商品是否存在
    var product model.Product
    if err := s.db.First(&product, productID).Error; err != nil {
        return errors.New("product not found")
    }

    // 检查库存
    if product.Stock < quantity {
        return errors.New("insufficient stock")
    }

    // 查找购物车中是否已存在该商品
    var cartItem model.CartItem
    result := s.db.Where("user_id = ? AND product_id = ?", userID, productID).First(&cartItem)

    if result.Error == nil {
        // 更新数量
        cartItem.Quantity += quantity
        return s.db.Save(&cartItem).Error
    }

    // 创建新的购物车项
    cartItem = model.CartItem{
        UserID:    userID,
        ProductID: productID,
        Quantity:  quantity,
    }
    return s.db.Create(&cartItem).Error
}

// GetCart 获取购物车内容
func (s *CartService) GetCart(userID uint) ([]model.CartItem, error) {
    var cartItems []model.CartItem
    err := s.db.Preload("Product").Where("user_id = ?", userID).Find(&cartItems).Error
    return cartItems, err
}

// UpdateCartItem 更新购物车项目数量
func (s *CartService) UpdateCartItem(userID uint, itemID uint, quantity int) error {
    var cartItem model.CartItem
    if err := s.db.Where("id = ? AND user_id = ?", itemID, userID).First(&cartItem).Error; err != nil {
        return errors.New("cart item not found")
    }

    // 检查库存
    var product model.Product
    if err := s.db.First(&product, cartItem.ProductID).Error; err != nil {
        return errors.New("product not found")
    }

    if product.Stock < quantity {
        return errors.New("insufficient stock")
    }

    cartItem.Quantity = quantity
    return s.db.Save(&cartItem).Error
}

// RemoveFromCart 从购物车移除商品
func (s *CartService) RemoveFromCart(userID uint, itemID uint) error {
    result := s.db.Where("id = ? AND user_id = ?", itemID, userID).Delete(&model.CartItem{})
    if result.RowsAffected == 0 {
        return errors.New("cart item not found")
    }
    return result.Error
}

// ClearCart 清空购物车
func (s *CartService) ClearCart(userID uint) error {
    return s.db.Where("user_id = ?", userID).Delete(&model.CartItem{}).Error
}

// GetCartTotal 计算购物车总金额
func (s *CartService) GetCartTotal(userID uint) (float64, error) {
    var total float64
    var cartItems []model.CartItem
    err := s.db.Preload("Product").Where("user_id = ?", userID).Find(&cartItems).Error
    if err != nil {
        return 0, err
    }

    for _, item := range cartItems {
        total += item.Product.Price * float64(item.Quantity)
    }
    return total, nil
}

4. API路由定义

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/your/project/internal/handler"
    "github.com/your/project/internal/middleware"
)

func setupRouter(h *handler.Handler) *gin.Engine {
    r := gin.Default()

    // 公开路由
    public := r.Group("/api/v1")
    {
        // 用户认证
        public.POST("/register", h.Register)
        public.POST("/login", h.Login)
        
        // 商品
        public.GET("/products", h.ListProducts)
        public.GET("/products/:id", h.GetProduct)
        public.GET("/products/search", h.SearchProducts)
    }

    // 需要认证的路由
    authorized := r.Group("/api/v1")
    authorized.Use(middleware.AuthMiddleware())
    {
        // 用户相关
        authorized.GET("/profile", h.GetProfile)
        authorized.PUT("/profile", h.UpdateProfile)
        
        // 购物车
        authorized.GET("/cart", h.GetCart)
        authorized.POST("/cart", h.AddToCart)
        authorized.PUT("/cart/:id", h.UpdateCartItem)
        authorized.DELETE("/cart/:id", h.RemoveFromCart)
        authorized.DELETE("/cart", h.ClearCart)
        
        // 订单
        authorized.POST("/orders", h.CreateOrder)
        authorized.GET("/orders", h.ListOrders)
        authorized.GET("/orders/:id", h.GetOrder)
    }

    // 管理员路由
    admin := r.Group("/api/v1/admin")
    admin.Use(middleware.AuthMiddleware(), middleware.AdminRequired())
    {
        // 商品管理
        admin.POST("/products", h.CreateProduct)
        admin.PUT("/products/:id", h.UpdateProduct)
        admin.DELETE("/products/:id", h.DeleteProduct)
        
        // 订单管理
        admin.GET("/orders", h.ListAllOrders)
        admin.PUT("/orders/:id/status", h.UpdateOrderStatus)
    }

    return r
}

三、流程图示

1. 用户注册登录流程

在这里插入图片描述

2. 购物车操作流程

在这里插入图片描述

四、处理器实现

1. 用户处理器

package handler

import (
    "github.com/gin-gonic/gin"
    "github.com/your/project/internal/model"
    "github.com/your/project/internal/service"
    "net/http"
)

type UserHandler struct {
    userService *service.UserService
}

func NewUserHandler(userService *service.UserService) *UserHandler {
    return &UserHandler{userService: userService}
}

// Register 用户注册
func (h *UserHandler) Register(c *gin.Context) {
    var input struct {
        Username string `json:"username" binding:"required,min=3,max=32"`
        Password string `json:"password" binding:"required,min=6"`
        Email    string `json:"email" binding:"required,email"`
    }

    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user, err := h.userService.CreateUser(input.Username, input.Password, input.Email)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    token, err := service.GenerateToken(user.ID, user.Role)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error generating token"})
        return
    }

    c.JSON(http.StatusCreated, gin.H{
        "token": token,
        "user":  user,
    })
}

// Login 用户登录
func (h *UserHandler) Login(c *gin.Context) {
    var input struct {
        Username string `json:"username" binding:"required"`
        Password string `json:"password" binding:"required"`
    }

    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user, err := h.userService.ValidateUser(input.Username, input.Password)
    if err != nil {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
        return
    }

    token, err := service.GenerateToken(user.ID, user.Role)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error generating token"})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "token": token,
        "user":  user,
    })
}

// GetProfile 获取用户信息
func (h *UserHandler) GetProfile(c *gin.Context) {
    userID, _ := c.Get("userID")
    user, err := h.userService.GetUserByID(userID.(uint))
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"user": user})
}

// UpdateProfile 更新用户信息
func (h *UserHandler) UpdateProfile(c *gin.Context) {
    userID, _ := c.Get("userID")
    
    var input struct {
        Email    string `json:"email" binding:"omitempty,email"`
        Password string `json:"password" binding:"omitempty,min=6"`
    }

    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user, err := h.userService.UpdateUser(userID.(uint), input.Email, input.Password)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"user": user})
}

2. 购物车处理器

package handler

import (
    "github.com/gin-gonic/gin"
    "github.com/your/project/internal/service"
    "net/http"
    "strconv"
)

type CartHandler struct {
    cartService *service.CartService
}

func NewCartHandler(cartService *service.CartService) *CartHandler {
    return &CartHandler{cartService: cartService}
}

// GetCart 获取购物车内容
func (h *CartHandler) GetCart(c *gin.Context) {
    userID := c.GetUint("userID")
    
    cartItems, err := h.cartService.GetCart(userID)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    total, err := h.cartService.GetCartTotal(userID)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "items": cartItems,
        "total": total,
    })
}

// AddToCart 添加商品到购物车
func (h *CartHandler) AddToCart(c *gin.Context) {
    userID := c.GetUint("userID")
    
    var input struct {
        ProductID uint `json:"product_id" binding:"required"`
        Quantity  int  `json:"quantity" binding:"required,min=1"`
    }

    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    err := h.cartService.AddToCart(userID, input.ProductID, input.Quantity)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "Item added to cart"})
}

// UpdateCartItem 更新购物车商品数量
func (h *CartHandler) UpdateCartItem(c *gin.Context) {
    userID := c.GetUint("userID")
    itemID, err := strconv.ParseUint(c.Param("id"), 10, 64)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid item ID"})
        return
    }

    var input struct {
        Quantity int `json:"quantity" binding:"required,min=1"`
    }

    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    err = h.cartService.UpdateCartItem(userID, uint(itemID), input.Quantity)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "Cart item updated"})
}

// RemoveFromCart 从购物车移除商品
func (h *CartHandler) RemoveFromCart(c *gin.Context) {
    userID := c.GetUint("userID")
    itemID, err := strconv.ParseUint(c.Param("id"), 10, 64)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid item ID"})
        return
    }

    err = h.cartService.RemoveFromCart(userID, uint(itemID))
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "Item removed from cart"})
}

// ClearCart 清空购物车
func (h *CartHandler) ClearCart(c *gin.Context) {
    userID := c.GetUint("userID")

    err := h.cartService.ClearCart(userID)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"message": "Cart cleared"})
}

五、API接口说明

用户相关接口

接口方法描述请求参数返回数据
/api/v1/registerPOST用户注册username, password, emailtoken, user
/api/v1/loginPOST用户登录username, passwordtoken, user
/api/v1/profileGET获取用户信息-user
/api/v1/profilePUT更新用户信息email, passworduser

购物车相关接口

接口方法描述请求参数返回数据
/api/v1/cartGET获取购物车-items, total
/api/v1/cartPOST添加商品product_id, quantitymessage
/api/v1/cart/:idPUT更新商品数量quantitymessage
/api/v1/cart/:idDELETE删除商品-message
/api/v1/cartDELETE清空购物车-message

六、测试案例

1. 用户服务测试

package service_test

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/your/project/internal/service"
    "github.com/your/project/internal/model"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

func setupTestDB(t *testing.T) *gorm.DB {
    db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
    assert.NoError(t, err)
    
    err = db.AutoMigrate(&model.User{})
    assert.NoError(t, err)
    
    return db
}

func TestUserService_CreateUser(t *testing.T) {
    db := setupTestDB(t)
    userService := service.NewUserService(db)

    tests := []struct {
        name     string
        username string
        password string
        email    string
        wantErr  bool
    }{
        {
            name:     "Valid user creation",
            username: "testuser",
            password: "password123",
            email:    "test@example.com",
            wantErr:  false,
        },
        {
            name:     "Duplicate username",
            username: "testuser",
            password: "password123",
            email:    "test2@example.com",
            wantErr:  true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            user, err := userService.CreateUser(tt.username, tt.password, tt.email)
            if tt.wantErr {
                assert.Error(t, err)
                return
            }
            
            assert.NoError(t, err)
            assert.NotNil(t, user)
            assert.Equal(t, tt.username, user.Username)
            assert.Equal(t, tt.email, user.Email)
            assert.NotEqual(t, tt.password, user.Password) // 密码应该已加密
        })
    }
}

func TestUserService_ValidateUser(t *testing.T) {
    db := setupTestDB(t)
    userService := service.NewUserService(db)

    // 创建测试用户
    testUser, err := userService.CreateUser("testuser", "password123", "test@example.com")
    assert.NoError(t, err)

    tests := []struct {
        name     string
        username string
        password string
        wantErr  bool
    }{
        {
            name:     "Valid credentials",
            username: "testuser",
            password: "password123",
            wantErr:  false,
        },
        {
            name:     "Invalid password",
            username: "testuser",
            password: "wrongpassword",
            wantErr:  true,
        },
        {
            name:     "Non-existent user",
            username: "nonexistent",
            password: "password123",
            wantErr:  true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            user, err := userService.ValidateUser(tt.username, tt.password)
            if tt.wantErr {
                assert.Error(t, err)
                return
            }
            
            assert.NoError(t, err)
            assert.NotNil(t, user)
            assert.Equal(t, testUser.ID, user.ID)
        })
    }
}

2. 购物车服务测试

package service_test

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/your/project/internal/service"
    "github.com/your/project/internal/model"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

func setupCartTestDB(t *testing.T) *gorm.DB {
    db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
    assert.NoError(t, err)
    
    err = db.AutoMigrate(&model.User{}, &model.Product{}, &model.CartItem{})
    assert.NoError(t, err)
    
    return db
}

func createTestProduct(db *gorm.DB) *model.Product {
    product := &model.Product{
        Name:        "Test Product",
        Description: "Test Description",
        Price:       99.99,
        Stock:       100,
    }
    db.Create(product)
    return product
}

func TestCartService_AddToCart(t *testing.T) {
    db := setupCartTestDB(t)
    cartService := service.NewCartService(db)
    product := createTestProduct(db)

    tests := []struct {
        name      string
        userID    uint
        productID uint
        quantity  int
        wantErr   bool
    }{
        {
            name:      "Valid addition to cart",
            userID:    1,
            productID: product.ID,
            quantity:  5,
            wantErr:   false,
        },
        {
            name:      "Exceed stock quantity",
            userID:    1,
            productID: product.ID,
            quantity:  101,
            wantErr:   true,
        },
        {
            name:      "Non-existent product",
            userID:    1,
            productID: 999,
            quantity:  1,
            wantErr:   true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := cartService.AddToCart(tt.userID, tt.productID, tt.quantity)
            if tt.wantErr {
                assert.Error(t, err)
                return
            }
            
            assert.NoError(t, err)
            
            // 验证购物车项目
            var cartItem model.CartItem
            err = db.Where("user_id = ? AND product_id = ?", tt.userID, tt.productID).First(&cartItem).Error
            assert.NoError(t, err)
            assert.Equal(t, tt.quantity, cartItem.Quantity)
        })
    }
}

func TestCartService_GetCartTotal(t *testing.T) {
    db := setupCartTestDB(t)
    cartService := service.NewCartService(db)
    
    // 创建测试产品
    product1 := &model.Product{Name: "Product 1", Price: 10.00, Stock: 100}
    product2 := &model.Product{Name: "Product 2", Price: 20.00, Stock: 100}
    db.Create(product1)
    db.Create(product2)
    
    // 添加商品到购物车
    cartService.AddToCart(1, product1.ID, 2) // 2 * 10.00 = 20.00
    cartService.AddToCart(1, product2.ID, 1) // 1 * 20.00 = 20.00
    
    // 计算总价
    total, err := cartService.GetCartTotal(1)
    assert.NoError(t, err)
    assert.Equal(t, 40.00, total) // 20.00 + 20.00 = 40.00
}

func TestCartService_RemoveFromCart(t *testing.T) {
    db := setupCartTestDB(t)
    cartService := service.NewCartService(db)
    product := createTestProduct(db)
    
    // 先添加商品到购物车
    err := cartService.AddToCart(1, product.ID, 1)
    assert.NoError(t, err)
    
    // 获取购物车项目ID
    var cartItem model.CartItem
    err = db.Where("user_id = ? AND product_id = ?", 1, product.ID).First(&cartItem).Error
    assert.NoError(t, err)
    
    // 测试移除
    err = cartService.RemoveFromCart(1, cartItem.ID)
    assert.NoError(t, err)
    
    // 验证商品已被移除
    var count int64
    db.Model(&model.CartItem{}).Where("id = ?", cartItem.ID).Count(&count)
    assert.Equal(t, int64(0), count)
}

七、性能优化建议

1. 数据库优化

  1. 索引优化

    • 为常用查询字段添加索引
    • 用户表:username, email
    • 商品表:category_id, price
    • 订单表:user_id, status
  2. 查询优化

    • 使用预加载避免N+1问题
    • 分页加载大量数据
    • 适当使用缓存

2. 缓存策略

在这里插入图片描述

3. 购物车优化实现

package service

import (
    "context"
    "encoding/json"
    "fmt"
    "github.com/go-redis/redis/v8"
    "github.com/your/project/internal/model"
    "gorm.io/gorm"
    "time"
)

type CachedCartService struct {
    db    *gorm.DB
    redis *redis.Client
}

func NewCachedCartService(db *gorm.DB, redis *redis.Client) *CachedCartService {
    return &CachedCartService{
        db:    db,
        redis: redis,
    }
}

// 生成购物车缓存key
func cartKey(userID uint) string {
    return fmt.Sprintf("cart:user:%d", userID)
}

// AddToCart 添加商品到购物车(带缓存)
func (s *CachedCartService) AddToCart(userID uint, productID uint, quantity int) error {
    ctx := context.Background()

    // 检查商品并锁定库存
    var product model.Product
    err := s.db.Transaction(func(tx *gorm.DB) error {
        if err := tx.Lock().First(&product, productID).Error; err != nil {
            return err
        }
        
        if product.Stock < quantity {
            return fmt.Errorf("insufficient stock")
        }
        
        return nil
    })
    if err != nil {
        return err
    }

    // 更新数据库
    cartItem := model.CartItem{
        UserID:    userID,
        ProductID: productID,
        Quantity:  quantity,
        Product:   product,
    }
    
    if err := s.db.Create(&cartItem).Error; err != nil {
        return err
    }

    // 更新缓存
    cartItemJSON, err := json.Marshal(cartItem)
    if err != nil {
        return err
    }

    key := cartKey(userID)
    pipe := s.redis.Pipeline()
    pipe.HSet(ctx, key, fmt.Sprintf("item:%d", cartItem.ID), cartItemJSON)
    pipe.Expire(ctx, key, 24*time.Hour) // 设置过期时间
    _, err = pipe.Exec(ctx)

    return err
}

// GetCart 获取购物车内容(带缓存)
func (s *CachedCartService) GetCart(userID uint) ([]model.CartItem, error) {
    ctx := context.Background()
    key := cartKey(userID)

    // 尝试从缓存获取
    cartData, err := s.redis.HGetAll(ctx, key).Result()
    if err == nil && len(cartData) > 0 {
        items := make([]model.CartItem, 0, len(cartData))
        for _, itemJSON := range cartData {
            var item model.CartItem
            if err := json.Unmarshal([]byte(itemJSON), &item); err != nil {
                continue
            }
            items = append(items, item)
        }
        return items, nil
    }

    // 缓存未命中,从数据库获取
    var items []model.CartItem
    if err := s.db.Preload("Product").Where("user_id = ?", userID).Find(&items).Error; err != nil {
        return nil, err
    }

    // 更新缓存
    pipe := s.redis.Pipeline()
    for _, item := range items {
        itemJSON, err := json.Marshal(item)
        if err != nil {
            continue
        }
        pipe.HSet(ctx, key, fmt.Sprintf("item:%d", item.ID), itemJSON)
    }
    pipe.Expire(ctx, key, 24*time.Hour)
    pipe.Exec(ctx)

    return items, nil
}

// RemoveFromCart 从购物车移除商品(带缓存)
func (s *CachedCartService) RemoveFromCart(userID uint, itemID uint) error {
    ctx := context.Background()

    // 删除数据库记录
    if err := s.db.Where("id = ? AND user_id = ?", itemID, userID).Delete(&model.CartItem{}).Error; err != nil {
        return err
    }

    // 删除缓存
    key := cartKey(userID)
    s.redis.HDel(ctx, key, fmt.Sprintf("item:%d", itemID))

    return nil
}

// UpdateCartItem 更新购物车商品数量(带缓存)
func (s *CachedCartService) UpdateCartItem(userID uint, itemID uint, quantity int) error {
    ctx := context.Background()

    // 更新数据库
    var cartItem model.CartItem
    err := s.db.Transaction(func(tx *gorm.DB) error {
        if err := tx.Where("id = ? AND user_id = ?", itemID, userID).First(&cartItem).Error; err != nil {
            return err
        }

        // 检查库存
        var product model.Product
        if err := tx.First(&product, cartItem.ProductID).Error; err != nil {
            return err
        }

        if product.Stock < quantity {
            return fmt.Errorf("insufficient stock")
        }

        cartItem.Quantity = quantity
        return tx.Save(&cartItem).Error
    })
    if err != nil {
        return err
    }

    // 更新缓存
    itemJSON, err := json.Marshal(cartItem)
    if err != nil {
        return err
    }

    key := cartKey(userID)
    return s.redis.HSet(ctx, key, fmt.Sprintf("item:%d", itemID), itemJSON).Err()
}

八、错误处理和日志记录

1. 自定义错误类型

package errors

import "fmt"

// ErrorType 错误类型
type ErrorType string

const (
    ErrorTypeValidation   ErrorType = "VALIDATION_ERROR"
    ErrorTypeNotFound     ErrorType = "NOT_FOUND"
    ErrorTypeUnauthorized ErrorType = "UNAUTHORIZED"
    ErrorTypeDatabase     ErrorType = "DATABASE_ERROR"
    ErrorTypeInternal     ErrorType = "INTERNAL_ERROR"
)

// AppError 应用错误
type AppError struct {
    Type    ErrorType `json:"type"`
    Message string    `json:"message"`
    Details string    `json:"details,omitempty"`
}

func (e *AppError) Error() string {
    if e.Details != "" {
        return fmt.Sprintf("%s: %s (%s)", e.Type, e.Message, e.Details)
    }
    return fmt.Sprintf("%s: %s", e.Type, e.Message)
}

// 创建各类错误的工厂函数
func NewValidationError(message string, details string) *AppError {
    return &AppError{
        Type:    ErrorTypeValidation,
        Message: message,
        Details: details,
    }
}

func NewNotFoundError(message string) *AppError {
    return &AppError{
        Type:    ErrorTypeNotFound,
        Message: message,
    }
}

func NewUnauthorizedError(message string) *AppError {
    return &AppError{
        Type:    ErrorTypeUnauthorized,
        Message: message,
    }
}

func NewDatabaseError(err error) *AppError {
    return &AppError{
        Type:    ErrorTypeDatabase,
        Message: "Database operation failed",
        Details: err.Error(),
    }
}

func NewInternalError(err error) *AppError {
    return &AppError{
        Type:    ErrorTypeInternal,
        Message: "Internal server error",
        Details: err.Error(),
    }
}

2. 日志中间件

package middleware

import (
    "bytes"
    "github.com/gin-gonic/gin"
    "github.com/sirupsen/logrus"
    "io/ioutil"
    "time"
)

// responseWriter 自定义响应写入器
type responseWriter struct {
    gin.ResponseWriter
    body *bytes.Buffer
}

func (w responseWriter) Write(b []byte) (int, error) {
    w.body.Write(b)
    return w.ResponseWriter.Write(b)
}

// LoggingMiddleware 日志中间件
func LoggingMiddleware(logger *logrus.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        // 开始时间
        start := time.Now()

        // 读取请求体
        var requestBody []byte
        if c.Request.Body != nil {
            requestBody, _ = ioutil.ReadAll(c.Request.Body)
            c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(requestBody))
        }

        // 包装响应写入器
        w := &responseWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer}
        c.Writer = w

        // 处理请求
        c.Next()

        // 请求处理时间
        duration := time.Since(start)

        // 获取用户ID(如果有)
        userID, exists := c.Get("userID")
        if !exists {
            userID = "anonymous"
        }

        // 构建日志字段
        fields := logrus.Fields{
            "client_ip":  c.ClientIP(),
            "duration":   duration,
            "method":     c.Request.Method,
            "path":       c.Request.URL.Path,
            "status":     c.Writer.Status(),
            "user_id":    userID,
            "referrer":   c.Request.Referer(),
            "user_agent": c.Request.UserAgent(),
        }

        // 添加请求体(如果不是太大)
        if len(requestBody) > 0 && len(requestBody) < 1024 {
            fields["request_body"] = string(requestBody)
        }

        // 添加响应体(如果不是太大)
        responseBody := w.body.String()
        if len(responseBody) > 0 && len(responseBody) < 1024 {
            fields["response_body"] = responseBody
        }

        // 根据状态码选择日志级别
        if c.Writer.Status() >= 500 {
            logger.WithFields(fields).Error("Server error")
        } else if c.Writer.Status() >= 400 {
            logger.WithFields(fields).Warn("Client error")
        } else {
            logger.WithFields(fields).Info("Request processed")
        }
    }
}

九、项目部署和监控

1. Docker部署配置

# Dockerfile
FROM golang:1.21-alpine AS builder

WORKDIR /app

# 安装依赖
COPY go.mod go.sum ./
RUN go mod download

# 复制源代码
COPY . .

# 编译
RUN CGO_ENABLED=0 GOOS=linux go build -o main cmd/server/main.go

# 最终镜像
FROM alpine:latest

WORKDIR /app

# 从builder阶段复制编译后的程序
COPY --from=builder /app/main .
COPY --from=builder /app/configs ./configs

# 暴露端口
EXPOSE 8080

# 启动程序
CMD ["./main"]

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=mysql
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=secret
      - DB_NAME=ecommerce
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - JWT_SECRET=your-secret-key
    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: ecommerce
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - "3306:3306"

  redis:
    image: redis:alpine
    volumes:
      - redis-data:/data
    ports:
      - "6379:6379"

volumes:
  mysql-data:
  redis-data:

2. 性能监控实现

package middleware

import (
    "github.com/gin-gonic/gin"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "time"
)

var (
    httpRequestsTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "path", "status"},
    )

    httpRequestDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10},
        },
        []string{"method", "path"},
    )

    activeRequests = promauto.NewGauge(
        prometheus.GaugeOpts{
            Name: "http_requests_active",
            Help: "Number of active HTTP requests",
        },
    )

    responseSize = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_response_size_bytes",
            Help:    "Size of HTTP responses in bytes",
            Buckets: []float64{100, 1000, 10000, 100000, 1000000},
        },
        []string{"method", "path"},
    )
)

// MetricsMiddleware 性能监控中间件
func MetricsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        
        // 增加活跃请求计数
        activeRequests.Inc()
        
        // 请求处理完成后执行
        defer func() {
            // 减少活跃请求计数
            activeRequests.Dec()
            
            // 记录请求总数
            status := c.Writer.Status()
            httpRequestsTotal.WithLabelValues(
                c.Request.Method,
                c.FullPath(),
                string(rune(status)),
            ).Inc()
            
            // 记录请求处理时间
            duration := time.Since(start).Seconds()
            httpRequestDuration.WithLabelValues(
                c.Request.Method,
                c.FullPath(),
            ).Observe(duration)
            
            // 记录响应大小
            responseSize.WithLabelValues(
                c.Request.Method,
                c.FullPath(),
            ).Observe(float64(c.Writer.Size()))
        }()
        
        c.Next()
    }
}

// PerformanceMonitor 系统性能监控
type PerformanceMonitor struct {
    requestCount    int64
    errorCount     int64
    responseTimes  []float64
    slowThreshold  float64 // 慢请求阈值(秒)
}

func NewPerformanceMonitor() *PerformanceMonitor {
    return &PerformanceMonitor{
        slowThreshold: 1.0, // 默认1秒为慢请求
        responseTimes: make([]float64, 0),
    }
}

// RecordMetrics 记录性能指标
func (pm *PerformanceMonitor) RecordMetrics(c *gin.Context, start time.Time) {
    duration := time.Since(start).Seconds()
    
    // 记录响应时间
    pm.responseTimes = append(pm.responseTimes, duration)
    
    // 记录请求计数
    pm.requestCount++
    
    // 记录错误计数
    if c.Writer.Status() >= 400 {
        pm.errorCount++
    }
    
    // 记录慢请求
    if duration > pm.slowThreshold {
        logrus.WithFields(logrus.Fields{
            "path":     c.Request.URL.Path,
            "method":   c.Request.Method,
            "duration": duration,
        }).Warn("Slow request detected")
    }
}

// GetMetrics 获取性能指标统计
func (pm *PerformanceMonitor) GetMetrics() map[string]interface{} {
    var avgResponseTime float64
    if len(pm.responseTimes) > 0 {
        total := 0.0
        for _, t := range pm.responseTimes {
            total += t
        }
        avgResponseTime = total / float64(len(pm.responseTimes))
    }

    return map[string]interface{}{
        "total_requests":      pm.requestCount,
        "error_rate":         float64(pm.errorCount) / float64(pm.requestCount),
        "avg_response_time":  avgResponseTime,
    }
}

3. 健康检查接口

package handler

import (
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"
    "github.com/go-redis/redis/v8"
    "net/http"
    "context"
    "time"
)

type HealthHandler struct {
    db    *gorm.DB
    redis *redis.Client
}

func NewHealthHandler(db *gorm.DB, redis *redis.Client) *HealthHandler {
    return &HealthHandler{
        db:    db,
        redis: redis,
    }
}

// 健康检查响应结构
type HealthCheck struct {
    Status      string            `json:"status"`
    Version     string            `json:"version"`
    Services    map[string]string `json:"services"`
    Timestamp   time.Time         `json:"timestamp"`
    Environment string            `json:"environment"`
}

// Check 健康检查处理函数
func (h *HealthHandler) Check(c *gin.Context) {
    ctx := context.Background()
    services := make(map[string]string)

    // 检查数据库连接
    sqlDB, err := h.db.DB()
    if err != nil {
        services["database"] = "error: " + err.Error()
    } else if err := sqlDB.Ping(); err != nil {
        services["database"] = "error: " + err.Error()
    } else {
        services["database"] = "healthy"
    }

    // 检查Redis连接
    if err := h.redis.Ping(ctx).Err(); err != nil {
        services["redis"] = "error: " + err.Error()
    } else {
        services["redis"] = "healthy"
    }

    // 确定整体状态
    status := "healthy"
    for _, s := range services {
        if s != "healthy" {
            status = "degraded"
            break
        }
    }

    response := HealthCheck{
        Status:      status,
        Version:     "1.0.0", // 从配置中获取
        Services:    services,
        Timestamp:   time.Now(),
        Environment: "production", // 从环境变量获取
    }

    c.JSON(http.StatusOK, response)
}

// DetailedHealth 详细的健康检查
func (h *HealthHandler) DetailedHealth(c *gin.Context) {
    ctx := context.Background()
    details := make(map[string]interface{})

    // 数据库详细信息
    if sqlDB, err := h.db.DB(); err == nil {
        dbStats := make(map[string]interface{})
        dbStats["max_open_connections"] = sqlDB.Stats().MaxOpenConnections
        dbStats["open_connections"] = sqlDB.Stats().OpenConnections
        dbStats["in_use"] = sqlDB.Stats().InUse
        dbStats["idle"] = sqlDB.Stats().Idle
        details["database"] = dbStats
    }

    // Redis详细信息
    if info, err := h.redis.Info(ctx).Result(); err == nil {
        details["redis"] = map[string]interface{}{
            "info": info,
        }
    }

    // 系统信息
    details["system"] = map[string]interface{}{
        "goroutines": runtime.NumGoroutine(),
        "cgocalls":   runtime.NumCgoCall(),
        "cpus":       runtime.NumCPU(),
    }

    c.JSON(http.StatusOK, gin.H{
        "status":   "healthy",
        "details":  details,
        "timestamp": time.Now(),
    })
}

// MetricsHandler 指标收集处理器
func (h *HealthHandler) MetricsHandler(c *gin.Context) {
    metrics := make(map[string]interface{})
    
    // 收集数据库指标
    if sqlDB, err := h.db.DB(); err == nil {
        stats := sqlDB.Stats()
        metrics["database"] = map[string]interface{}{
            "connections": {
                "max_open": stats.MaxOpenConnections,
                "open":     stats.OpenConnections,
                "in_use":   stats.InUse,
                "idle":     stats.Idle,
            },
            "queries": {
                "wait_count":         stats.WaitCount,
                "wait_duration":      stats.WaitDuration,
                "max_idle_closed":    stats.MaxIdleClosed,
                "max_lifetime_closed": stats.MaxLifetimeClosed,
            },
        }
    }

    // 收集系统指标
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    metrics["system"] = map[string]interface{}{
        "memory": {
            "alloc":      m.Alloc,
            "total_alloc": m.TotalAlloc,
            "sys":        m.Sys,
            "num_gc":     m.NumGC,
        },
        "goroutines": runtime.NumGoroutine(),
    }

    c.JSON(http.StatusOK, metrics)
}

十、项目总结与最佳实践

1. 项目架构总览

在这里插入图片描述

2. 性能优化总结

优化点实现方式效果
数据库查询索引优化、连接池提高查询速度
缓存策略Redis缓存热点数据减少数据库压力
并发处理Go协程池提高并发性能
API响应GZIP压缩、分页优化响应时间
代码结构模块化、依赖注入提高可维护性

3. 安全措施

  1. 认证和授权

    • JWT Token验证
    • 角色权限控制
    • 请求频率限制
  2. 数据安全

    • 密码加密存储
    • SQL注入防护
    • XSS防护
  3. 传输安全

    • HTTPS
    • 数据加密
    • 安全Headers

4. 开发建议

  1. 代码规范

    - 使用 gofmt 格式化代码
    - 遵循 Go 官方代码规范
    - 编写完整的单元测试
    - 使用 golangci-lint 进行代码检查
    
  2. 错误处理

    - 自定义错误类型
    - 统一错误处理中间件
    - 详细的错误日志记录
    
  3. 文档维护

    - API文档使用Swagger
    - 代码注释完整
    - README文档及时更新
    

5. 部署注意事项

  1. 环境配置

    • 使用环境变量
    • 配置文件分环境
    • 敏感信息加密
  2. 监控告警

    • 设置合理的监控指标
    • 配置告警阈值
    • 建立告警机制
  3. 备份策略

    • 定期数据备份
    • 多副本存储
    • 灾难恢复预案

通过以上内容,我们完成了一个功能完整、性能优化、安全可靠的电子商务网站后端系统。项目涵盖了用户管理、商品管理、购物车功能、订单处理等核心功能,并实现了必要的性能优化和安全措施。在实际开发中,可以根据具体需求进行适当的调整和扩展。


http://www.kler.cn/a/399409.html

相关文章:

  • 「Mac玩转仓颉内测版14」PTA刷题篇5 - L1-005 考试座位号
  • Java爬虫(Jsoup)详解
  • gitlab 服务器集群配置及 存储扩展配置
  • Educational Codeforces Round 164 (Rated for Div. 2)(A~E)
  • Android加载pdf
  • 计算机网络WebSocket——针对实习面试
  • 足球青训俱乐部管理后台系统(程序+数据库+报告)
  • Spring Boot 2.x 和 Druid 多数据源整合 dm
  • 【C语言】科技要闻。
  • 构建SSH僵尸网络
  • 什么是 C++ 内联函数?它的作用是什么?内联函数与普通函数有什么区别?如何定义和使用内联函数?
  • QSS 设置bug
  • 【资料】网络安全风险评估报告,风险管理报告,网络安全风险管理计划,网络安全网络安全能力验证报(Word原件)
  • 当API遇上“交通堵塞”:处理API限制的艺术
  • C++ 编程基础(7)内存模型 | 7.1、内存类型
  • Go语言24小时极速学习教程(五)Go语言中的SpringMVC框架——Gin
  • 已有账号,重装系统激活office后发现没有ppt,word,excel等
  • 使用 Vue 和 Create-Vue 构建工程化前端项目
  • androidstudio入门到放弃配置
  • 安装一键式重置密码插件(Linux)-CloudResetPwdAgent
  • java ssm 健康医馆管理系统 中医馆管理 健康平台 药店 源码jsp
  • 网络百问百答(一)
  • 在MATLAB中实现自适应滤波算法
  • Prometheus面试内容整理-实践经验
  • ssh.service could not be found“
  • 【C++】字符串相乘