15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)

Day 60: 综合项目展示 - 构建微服务电商平台

1. 课程概述

项目内容 详细说明 预计完成时间
项目介绍 理解整体架构和业务流程 30分钟
用户服务 实现用户注册、登录、认证 2小时
商品服务 实现商品CRUD、库存管理 2小时
订单服务 实现订单创建、支付流程 2小时
网关服务 实现API统一入口、限流 1.5小时
系统测试 完成单元测试和集成测试 1小时
项目部署 使用Docker完成服务部署 1小时

2. 项目架构图

3. 核心代码实现

3.1 项目结构

microshop/
├── api/
│   └── proto/
│       ├── user.proto
│       ├── product.proto
│       └── order.proto
├── cmd/
│   ├── gateway/
│   ├── user/
│   ├── product/
│   └── order/
├── internal/
│   ├── auth/
│   ├── config/
│   ├── database/
│   └── middleware/
├── pkg/
│   ├── errors/
│   ├── logger/
│   └── utils/
├── docker-compose.yml
└── go.mod

3.2 用户服务实现

go 复制代码
// internal/user/service/user.go
package service

import (
    "context"
    "crypto/sha256"
    "encoding/hex"
    "time"
    
    "github.com/go-redis/redis/v8"
    "gorm.io/gorm"
    "github.com/your/microshop/internal/model"
    "github.com/your/microshop/pkg/errors"
)

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

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

type RegisterRequest struct {
    Username string
    Email    string
    Password string
}

func (s *UserService) Register(ctx context.Context, req *RegisterRequest) error {
    // 检查用户是否已存在
    var existingUser model.User
    if err := s.db.Where("email = ?", req.Email).First(&existingUser).Error; err != gorm.ErrRecordNotFound {
        return errors.New("user already exists")
    }

    // 密码加密
    hashedPassword := hashPassword(req.Password)

    // 创建新用户
    user := &model.User{
        Username: req.Username,
        Email:    req.Email,
        Password: hashedPassword,
        Created:  time.Now(),
        Updated:  time.Now(),
    }

    if err := s.db.Create(user).Error; err != nil {
        return errors.Wrap(err, "failed to create user")
    }

    return nil
}

func (s *UserService) Login(ctx context.Context, email, password string) (string, error) {
    var user model.User
    if err := s.db.Where("email = ?", email).First(&user).Error; err != nil {
        return "", errors.New("invalid credentials")
    }

    if hashPassword(password) != user.Password {
        return "", errors.New("invalid credentials")
    }

    // 生成token
    token := generateToken()
    
    // 存储token到Redis,设置过期时间
    err := s.redis.Set(ctx, token, user.ID, 24*time.Hour).Err()
    if err != nil {
        return "", errors.Wrap(err, "failed to store token")
    }

    return token, nil
}

func hashPassword(password string) string {
    hash := sha256.New()
    hash.Write([]byte(password))
    return hex.EncodeToString(hash.Sum(nil))
}

func generateToken() string {
    // 实际项目中应该使用更安全的token生成方式
    return hex.EncodeToString([]byte(time.Now().String()))
}

3.3 商品服务实现

go 复制代码
// internal/product/service/product.go
package service

import (
    "context"
    "encoding/json"
    "time"

    "github.com/go-redis/redis/v8"
    "gorm.io/gorm"
    "github.com/your/microshop/internal/model"
    "github.com/your/microshop/pkg/errors"
)

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

type ProductRequest struct {
    Name        string
    Description string
    Price       float64
    Stock       int
}

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

func (s *ProductService) CreateProduct(ctx context.Context, req *ProductRequest) error {
    product := &model.Product{
        Name:        req.Name,
        Description: req.Description,
        Price:       req.Price,
        Stock:       req.Stock,
        Created:     time.Now(),
        Updated:     time.Now(),
    }

    if err := s.db.Create(product).Error; err != nil {
        return errors.Wrap(err, "failed to create product")
    }

    // 更新缓存
    return s.updateProductCache(ctx, product)
}

func (s *ProductService) GetProduct(ctx context.Context, id uint) (*model.Product, error) {
    // 先从缓存获取
    cacheKey := s.getProductCacheKey(id)
    data, err := s.redis.Get(ctx, cacheKey).Bytes()
    if err == nil {
        var product model.Product
        if err := json.Unmarshal(data, &product); err == nil {
            return &product, nil
        }
    }

    // 缓存未命中,从数据库获取
    var product model.Product
    if err := s.db.First(&product, id).Error; err != nil {
        return nil, errors.Wrap(err, "product not found")
    }

    // 更新缓存
    if err := s.updateProductCache(ctx, &product); err != nil {
        return nil, err
    }

    return &product, nil
}

func (s *ProductService) UpdateStock(ctx context.Context, id uint, quantity int) error {
    return s.db.Transaction(func(tx *gorm.DB) error {
        var product model.Product
        if err := tx.First(&product, id).Error; err != nil {
            return errors.Wrap(err, "product not found")
        }

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

        product.Stock -= quantity
        if err := tx.Save(&product).Error; err != nil {
            return errors.Wrap(err, "failed to update stock")
        }

        // 更新缓存
        return s.updateProductCache(ctx, &product)
    })
}

func (s *ProductService) getProductCacheKey(id uint) string {
    return fmt.Sprintf("product:%d", id)
}

func (s *ProductService) updateProductCache(ctx context.Context, product *model.Product) error {
    data, err := json.Marshal(product)
    if err != nil {
        return errors.Wrap(err, "failed to marshal product")
    }

    cacheKey := s.getProductCacheKey(product.ID)
    return s.redis.Set(ctx, cacheKey, data, 24*time.Hour).Err()
}

3.4 订单服务实现

go 复制代码
// internal/order/service/order.go
package service

import (
    "context"
    "time"

    "github.com/streadway/amqp"
    "gorm.io/gorm"
    "github.com/your/microshop/internal/model"
    "github.com/your/microshop/pkg/errors"
)

type OrderService struct {
    db          *gorm.DB
    productSvc  ProductClient
    rabbitmq    *amqp.Channel
}

type CreateOrderRequest struct {
    UserID    uint
    ProductID uint
    Quantity  int
    Address   string
}

func NewOrderService(db *gorm.DB, productSvc ProductClient, rabbitmq *amqp.Channel) *OrderService {
    return &OrderService{
        db:         db,
        productSvc: productSvc,
        rabbitmq:   rabbitmq,
    }
}

func (s *OrderService) CreateOrder(ctx context.Context, req *CreateOrderRequest) error {
    return s.db.Transaction(func(tx *gorm.DB) error {
        // 检查并更新商品库存
        if err := s.productSvc.UpdateStock(ctx, req.ProductID, req.Quantity); err != nil {
            return errors.Wrap(err, "failed to update stock")
        }

        // 创建订单
        order := &model.Order{
            UserID:    req.UserID,
            ProductID: req.ProductID,
            Quantity:  req.Quantity,
            Status:    "pending",
            Address:   req.Address,
            Created:   time.Now(),
            Updated:   time.Now(),
        }

        if err := tx.Create(order).Error; err != nil {
            return errors.Wrap(err, "failed to create order")
        }

        // 发送订单创建消息到消息队列
        if err := s.publishOrderCreatedEvent(order); err != nil {
            return errors.Wrap(err, "failed to publish order created event")
        }

        return nil
    })
}

func (s *OrderService) GetOrder(ctx context.Context, id uint) (*model.Order, error) {
    var order model.Order
    if err := s.db.First(&order, id).Error; err != nil {
        return nil, errors.Wrap(err, "order not found")
    }
    return &order, nil
}

func (s *OrderService) UpdateOrderStatus(ctx context.Context, id uint, status string) error {
    result := s.db.Model(&model.Order{}).
        Where("id = ?", id).
        Updates(map[string]interface{}{
            "status":  status,
            "updated": time.Now(),
        })

    if result.Error != nil {
        return errors.Wrap(result.Error, "failed to update order status")
    }

    if result.RowsAffected == 0 {
        return errors.New("order not found")
    }

    return nil
}

func (s *OrderService) publishOrderCreatedEvent(order *model.Order) error {
    body, err := json.Marshal(map[string]interface{}{
        "order_id":   order.ID,
        "user_id":    order.UserID,
        "product_id": order.ProductID,
        "quantity":   order.Quantity,
        "status":     order.Status,
        "created_at": order.Created,
    })
    if err != nil {
        return err
    }

    return s.rabbitmq.Publish(
        "orders",    // exchange
        "created",   // routing key
        false,      // mandatory
        false,      // immediate
        amqp.Publishing{
            ContentType: "application/json",
            Body:       body,
        },
    )
}

3.5 API网关实现

go 复制代码
// cmd/gateway/main.go
package main

import (
    "context"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/go-redis/redis/v8"
    "golang.org/x/time/rate"
    "github.com/your/microshop/internal/middleware"
    "github.com/your/microshop/pkg/errors"
)

type Gateway struct {
    userClient    UserClient
    productClient ProductClient
    orderClient   OrderClient
    redis         *redis.Client
    limiter      *rate.Limiter
}

func NewGateway(userClient UserClient, productClient ProductClient, 
    orderClient OrderClient, redis *redis.Client) *Gateway {
    return &Gateway{
        userClient:    userClient,
        productClient: productClient,
        orderClient:   orderClient,
        redis:         redis,
        limiter:       rate.NewLimiter(rate.Every(time.Second), 100), // 限制每秒100个请求
    }
}

func (g *Gateway) SetupRouter() *gin.Engine {
    router := gin.Default()

    // 中间件
    router.Use(middleware.Cors())
    router.Use(g.RateLimit())
    router.Use(g.RequestLogger())

    // 用户相关路由
    user := router.Group("/api/v1/users")
    {
        user.POST("/register", g.HandleUserRegister)
        user.POST("/login", g.HandleUserLogin)
        user.GET("/profile", g.AuthMiddleware(), g.HandleGetUserProfile)
    }

    // 商品相关路由
    product := router.Group("/api/v1/products")
    {
        product.GET("", g.HandleListProducts)
        product.GET("/:id", g.HandleGetProduct)
        product.POST("", g.AuthMiddleware(), g.AdminRequired(), g.HandleCreateProduct)
        product.PUT("/:id", g.AuthMiddleware(), g.AdminRequired(), g.HandleUpdateProduct)
    }

    // 订单相关路由
    order := router.Group("/api/v1/orders")
    {
        order.POST("", g.AuthMiddleware(), g.HandleCreateOrder)
        order.GET("", g.AuthMiddleware(), g.HandleListOrders)
        order.GET("/:id", g.AuthMiddleware(), g.HandleGetOrder)
    }

    return router
}

// 限流中间件
func (g *Gateway) RateLimit() gin.HandlerFunc {
    return func(c *gin.Context) {
        if !g.limiter.Allow() {
            c.JSON(http.StatusTooManyRequests, gin.H{"error": "too many requests"})
            c.Abort()
            return
        }
        c.Next()
    }
}

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

        // 从Redis验证token
        userID, err := g.redis.Get(c, token).Uint64()
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
            c.Abort()
            return
        }

        c.Set("userID", uint(userID))
        c.Next()
    }
}

// 处理用户注册
func (g *Gateway) HandleUserRegister(c *gin.Context) {
    var req struct {
        Username string `json:"username" binding:"required"`
        Email    string `json:"email" binding:"required,email"`
        Password string `json:"password" binding:"required,min=6"`
    }

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

    err := g.userClient.Register(c.Request.Context(), &RegisterRequest{
        Username: req.Username,
        Email:    req.Email,
        Password: req.Password,
    })

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

    c.JSON(http.StatusCreated, gin.H{"message": "user registered successfully"})
}

// 处理创建订单
func (g *Gateway) HandleCreateOrder(c *gin.Context) {
    var req struct {
        ProductID uint `json:"product_id" binding:"required"`
        Quantity  int  `json:"quantity" binding:"required,min=1"`
        Address   string `json:"address" binding:"required"`
    }

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

    userID := c.MustGet("userID").(uint)
    err := g.orderClient.CreateOrder(c.Request.Context(), &CreateOrderRequest{
        UserID:    userID,
        ProductID: req.ProductID,
        Quantity:  req.Quantity,
        Address:   req.Address,
    })

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

    c.JSON(http.StatusCreated, gin.H{"message": "order created successfully"})
}

3.6 系统配置和数据库模型

go 复制代码
// internal/config/config.go
package config

type Config struct {
    Server struct {
        Port int    `yaml:"port"`
        Mode string `yaml:"mode"`
    } `yaml:"server"`

    Database struct {
        Host     string `yaml:"host"`
        Port     int    `yaml:"port"`
        User     string `yaml:"user"`
        Password string `yaml:"password"`
        DBName   string `yaml:"dbname"`
    } `yaml:"database"`

    Redis struct {
        Host     string `yaml:"host"`
        Port     int    `yaml:"port"`
        Password string `yaml:"password"`
        DB       int    `yaml:"db"`
    } `yaml:"redis"`

    RabbitMQ struct {
        URL string `yaml:"url"`
    } `yaml:"rabbitmq"`
}

// internal/model/models.go
package model

import (
    "time"
)

type User struct {
    ID        uint      `gorm:"primaryKey"`
    Username  string    `gorm:"size:255;not null"`
    Email     string    `gorm:"size:255;not null;unique"`
    Password  string    `gorm:"size:255;not null"`
    Role      string    `gorm:"size:20;default:'user'"`
    Created   time.Time `gorm:"not null"`
    Updated   time.Time `gorm:"not null"`
    Orders    []Order   `gorm:"foreignKey:UserID"`
}

type Product struct {
    ID          uint      `gorm:"primaryKey"`
    Name        string    `gorm:"size:255;not null"`
    Description string    `gorm:"type:text"`
    Price       float64   `gorm:"not null"`
    Stock       int       `gorm:"not null"`
    Created     time.Time `gorm:"not null"`
    Updated     time.Time `gorm:"not null"`
    Orders      []Order   `gorm:"foreignKey:ProductID"`
}

type Order struct {
    ID        uint      `gorm:"primaryKey"`
    UserID    uint      `gorm:"not null"`
    ProductID uint      `gorm:"not null"`
    Quantity  int       `gorm:"not null"`
    Status    string    `gorm:"size:20;not null"`
    Address   string    `gorm:"type:text;not null"`
    Created   time.Time `gorm:"not null"`
    Updated   time.Time `gorm:"not null"`
    User      User      `gorm:"foreignKey:UserID"`
    Product   Product   `gorm:"foreignKey:ProductID"`
}

3.7 Docker部署配置

go 复制代码
# docker-compose.yml
version: '3.8'

services:
  gateway:
    build:
      context: .
      dockerfile: cmd/gateway/Dockerfile
    ports:
      - "8080:8080"
    depends_on:
      - user-service
      - product-service
      - order-service
    environment:
      - SERVER_PORT=8080
      - REDIS_HOST=redis
      - USER_SERVICE_URL=user-service:9001
      - PRODUCT_SERVICE_URL=product-service:9002
      - ORDER_SERVICE_URL=order-service:9003

  user-service:
    build:
      context: .
      dockerfile: cmd/user/Dockerfile
    depends_on:
      - mysql
      - redis
    environment:
      - SERVER_PORT=9001
      - DB_HOST=mysql
      - REDIS_HOST=redis

  product-service:
    build:
      context: .
      dockerfile: cmd/product/Dockerfile
    depends_on:
      - mysql
      - redis
    environment:
      - SERVER_PORT=9002
      - DB_HOST=mysql
      - REDIS_HOST=redis

  order-service:
    build:
      context: .
      dockerfile: cmd/order/Dockerfile
    depends_on:
      - mysql
      - rabbitmq
    environment:
      - SERVER_PORT=9003
      - DB_HOST=mysql
      - RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672/

  mysql:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=microshop
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - "3306:3306"

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

  rabbitmq:
    image: rabbitmq:3.9-management
    ports:
      - "5672:5672"
      - "15672:15672"
    volumes:
      - rabbitmq-data:/var/lib/rabbitmq

volumes:
  mysql-data:
  redis-data:
  rabbitmq-data:

# cmd/gateway/Dockerfile
FROM golang:1.19-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o gateway cmd/gateway/main.go

FROM alpine:3.14
WORKDIR /app
COPY --from=builder /app/gateway .
COPY configs/ configs/
EXPOSE 8080
CMD ["./gateway"]

4. 系统测试流程

4.1 测试流程图

4.2 单元测试示例

go 复制代码
// internal/product/service/product_test.go
package service

import (
    "context"
    "testing"
    "time"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
    "github.com/go-redis/redis/v8"
    "gorm.io/gorm"
    "github.com/your/microshop/internal/model"
)

type MockDB struct {
    mock.Mock
}

func (m *MockDB) First(out interface{}, conditions ...interface{}) *gorm.DB {
    args := m.Called(out, conditions)
    return args.Get(0).(*gorm.DB)
}

func (m *MockDB) Create(value interface{}) *gorm.DB {
    args := m.Called(value)
    return args.Get(0).(*gorm.DB)
}

func TestProductService_CreateProduct(t *testing.T) {
    // 初始化mock对象
    mockDB := new(MockDB)
    mockRedis := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    service := NewProductService(mockDB, mockRedis)

    // 测试用例
    tests := []struct {
        name    string
        req     *ProductRequest
        mock    func()
        wantErr bool
    }{
        {
            name: "successful creation",
            req: &ProductRequest{
                Name:        "Test Product",
                Description: "Test Description",
                Price:      99.99,
                Stock:      100,
            },
            mock: func() {
                mockDB.On("Create", mock.AnythingOfType("*model.Product")).
                    Return(&gorm.DB{Error: nil})
            },
            wantErr: false,
        },
        {
            name: "database error",
            req: &ProductRequest{
                Name:        "Test Product",
                Description: "Test Description",
                Price:      99.99,
                Stock:      100,
            },
            mock: func() {
                mockDB.On("Create", mock.AnythingOfType("*model.Product")).
                    Return(&gorm.DB{Error: gorm.ErrInvalidTransaction})
            },
            wantErr: true,
        },
    }

    // 执行测试用例
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            tt.mock()
            err := service.CreateProduct(context.Background(), tt.req)
            if tt.wantErr {
                assert.Error(t, err)
            } else {
                assert.NoError(t, err)
            }
        })
    }
}

// internal/order/service/order_test.go
package service

import (
    "context"
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
)

type MockProductClient struct {
    mock.Mock
}

func (m *MockProductClient) UpdateStock(ctx context.Context, productID uint, quantity int) error {
    args := m.Called(ctx, productID, quantity)
    return args.Error(0)
}

func TestOrderService_CreateOrder(t *testing.T) {
    // 初始化mock对象
    mockDB := new(MockDB)
    mockProductClient := new(MockProductClient)

    service := NewOrderService(mockDB, mockProductClient, nil)

    // 测试用例
    tests := []struct {
        name    string
        req     *CreateOrderRequest
        mock    func()
        wantErr bool
    }{
        {
            name: "successful order creation",
            req: &CreateOrderRequest{
                UserID:    1,
                ProductID: 1,
                Quantity:  2,
                Address:   "Test Address",
            },
            mock: func() {
                mockProductClient.On("UpdateStock", mock.Anything, uint(1), 2).
                    Return(nil)
                mockDB.On("Create", mock.AnythingOfType("*model.Order")).
                    Return(&gorm.DB{Error: nil})
            },
            wantErr: false,
        },
        {
            name: "insufficient stock",
            req: &CreateOrderRequest{
                UserID:    1,
                ProductID: 1,
                Quantity:  100,
                Address:   "Test Address",
            },
            mock: func() {
                mockProductClient.On("UpdateStock", mock.Anything, uint(1), 100).
                    Return(errors.New("insufficient stock"))
            },
            wantErr: true,
        },
    }

    // 执行测试用例
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            tt.mock()
            err := service.CreateOrder(context.Background(), tt.req)
            if tt.wantErr {
                assert.Error(t, err)
            } else {
                assert.NoError(t, err)
            }
        })
    }
}

4.3 集成测试示例

go 复制代码
// tests/integration/api_test.go
package integration

import (
    "bytes"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
    "github.com/your/microshop/internal/gateway"
)

func TestAPIIntegration(t *testing.T) {
    // 设置测试环境
    gin.SetMode(gin.TestMode)
    router := setupTestRouter()

    // 用户注册测试
    t.Run("user registration", func(t *testing.T) {
        reqBody := map[string]interface{}{
            "username": "testuser",
            "email":    "test@example.com",
            "password": "password123",
        }
        jsonBody, _ := json.Marshal(reqBody)

        req := httptest.NewRequest("POST", "/api/v1/users/register", bytes.NewBuffer(jsonBody))
        req.Header.Set("Content-Type", "application/json")
        resp := httptest.NewRecorder()

        router.ServeHTTP(resp, req)
        assert.Equal(t, http.StatusCreated, resp.Code)

        var response map[string]interface{}
        err := json.Unmarshal(resp.Body.Bytes(), &response)
        assert.NoError(t, err)
        assert.Contains(t, response, "message")
    })

    // 用户登录测试
    var token string
    t.Run("user login", func(t *testing.T) {
        reqBody := map[string]interface{}{
            "email":    "test@example.com",
            "password": "password123",
        }
        jsonBody, _ := json.Marshal(reqBody)

        req := httptest.NewRequest("POST", "/api/v1/users/login", bytes.NewBuffer(jsonBody))
        req.Header.Set("Content-Type", "application/json")
        resp := httptest.NewRecorder()

        router.ServeHTTP(resp, req)
        assert.Equal(t, http.StatusOK, resp.Code)

        var response map[string]interface{}
        err := json.Unmarshal(resp.Body.Bytes(), &response)
        assert.NoError(t, err)
        assert.Contains(t, response, "token")
        token = response["token"].(string)
    })

    // 创建商品测试
    var productID uint
    t.Run("create product", func(t *testing.T) {
        reqBody := map[string]interface{}{
            "name":        "Test Product",
            "description": "Test Description",
            "price":      99.99,
            "stock":      100,
        }
        jsonBody, _ := json.Marshal(reqBody)

        req := httptest.NewRequest("POST", "/api/v1/products", bytes.NewBuffer(jsonBody))
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Authorization", token)
        resp := httptest.NewRecorder()

        router.ServeHTTP(resp, req)
        assert.Equal(t, http.StatusCreated, resp.Code)

        var response map[string]interface{}
        err := json.Unmarshal(resp.Body.Bytes(), &response)
        assert.NoError(t, err)
        assert.Contains(t, response, "product_id")
        productID = uint(response["product_id"].(float64))
    })

    // 创建订单测试
    t.Run("create order", func(t *testing.T) {
        reqBody := map[string]interface{}{
            "product_id": productID,
            "quantity":   2,
            "address":    "Test Address",
        }
        jsonBody, _ := json.Marshal(reqBody)

        req := httptest.NewRequest("POST", "/api/v1/orders", bytes.NewBuffer(jsonBody))
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Authorization", token)
        resp := httptest.NewRecorder()

        router.ServeHTTP(resp, req)
        assert.Equal(t, http.StatusCreated, resp.Code)

        var response map[string]interface{}
        err := json.Unmarshal(resp.Body.Bytes(), &response)
        assert.NoError(t, err)
        assert.Contains(t, response, "message")
    })
}

func setupTestRouter() *gin.Engine {
    // 初始化测试环境的路由和依赖
    router := gin.New()
    router.Use(gin.Recovery())
    
    // 配置测试环境的服务依赖
    gateway := gateway.NewGateway(
        newMockUserClient(),
        newMockProductClient(),
        newMockOrderClient(),
        setupTestRedis(),
    )
    
    gateway.SetupRouter(router)
    return router
}

4.4 性能测试工具

bash 复制代码
# 使用 Apache Benchmark 进行基本性能测试
ab -n 1000 -c 100 http://localhost:8080/api/v1/products

# 使用 hey 进行并发测试
hey -n 10000 -c 100 http://localhost:8080/api/v1/products

# 使用 wrk 进行压力测试
wrk -t12 -c400 -d30s http://localhost:8080/api/v1/products

5. 项目部署流程

5.1 部署流程图

5.2 部署检查清单

检查项 说明 状态确认方法
Docker环境 确保Docker和Docker Compose已安装 docker -v && docker-compose -v
配置文件 检查所有服务的配置文件是否正确 检查config目录下的配置文件
数据库初始化 确保数据库表结构已创建 执行go run cmd/migrate/main.go
网络配置 检查服务间网络连接是否正常 docker network ls
服务状态 验证所有服务是否正常运行 docker-compose ps
日志检查 检查服务日志是否有错误 docker-compose logs
性能监控 配置Prometheus和Grafana 访问监控面板

6. 项目总结

6.1 项目特点

  1. 微服务架构设计

    • 服务解耦
    • 独立部署
    • 技术栈灵活
  2. 高可用性设计

    • 服务注册与发现
    • 负载均衡
    • 熔断降级
  3. 性能优化

    • 缓存策略
    • 消息队列
    • 数据库优化
  4. 安全性考虑

    • 身份认证
    • 访问控制
    • 数据加密

6.2 可扩展性设计

  1. 水平扩展

    • 服务实例动态扩展
    • 数据库读写分离
    • 缓存集群
  2. 垂直扩展

    • 业务模块独立
    • 功能模块可插拔
    • 接口版本控制

6.3 后续优化方向

  1. 技术架构

    • 服务网格整合
    • 容器编排优化
    • 监控告警完善
  2. 业务功能

    • 支付系统集成
    • 库存管理优化
    • 订单流程完善
  3. 运维支持

    • CI/CD流程优化
    • 日志中心建设
    • 监控体系完善

7. 系统监控和维护

7.1 监控系统架构

7.2 Prometheus监控配置

go 复制代码
# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - alertmanager:9093

rule_files:
  - "rules/*.yml"

scrape_configs:
  - job_name: 'microshop-gateway'
    static_configs:
      - targets: ['gateway:8080']
        labels:
          service: 'gateway'

  - job_name: 'microshop-user'
    static_configs:
      - targets: ['user-service:9001']
        labels:
          service: 'user'

  - job_name: 'microshop-product'
    static_configs:
      - targets: ['product-service:9002']
        labels:
          service: 'product'

  - job_name: 'microshop-order'
    static_configs:
      - targets: ['order-service:9003']
        labels:
          service: 'order'

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'mysql-exporter'
    static_configs:
      - targets: ['mysql-exporter:9104']

  - job_name: 'redis-exporter'
    static_configs:
      - targets: ['redis-exporter:9121']

  - job_name: 'rabbitmq-exporter'
    static_configs:
      - targets: ['rabbitmq-exporter:9419']

7.3 服务监控指标实现

go 复制代码
// internal/metrics/metrics.go
package metrics

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
)

var (
    // HTTP请求总数
    HttpRequestsTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "endpoint", "status"},
    )

    // HTTP请求处理时间
    HttpRequestDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: prometheus.DefBuckets,
        },
        []string{"method", "endpoint"},
    )

    // 活跃用户数
    ActiveUsers = promauto.NewGauge(
        prometheus.GaugeOpts{
            Name: "active_users",
            Help: "Number of active users",
        },
    )

    // 订单处理总数
    OrderProcessedTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "orders_processed_total",
            Help: "Total number of processed orders",
        },
        []string{"status"},
    )

    // 商品库存水平
    ProductStock = promauto.NewGaugeVec(
        prometheus.GaugeOpts{
            Name: "product_stock_level",
            Help: "Current stock level of products",
        },
        []string{"product_id"},
    )

    // 系统错误总数
    ErrorsTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "system_errors_total",
            Help: "Total number of system errors",
        },
        []string{"service", "type"},
    )
)

// 中间件:记录HTTP请求指标
func MetricsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path
        method := c.Request.Method

        c.Next()

        status := strconv.Itoa(c.Writer.Status())
        duration := time.Since(start).Seconds()

        HttpRequestsTotal.WithLabelValues(method, path, status).Inc()
        HttpRequestDuration.WithLabelValues(method, path).Observe(duration)
    }
}

// 更新商品库存指标
func UpdateProductStock(productID string, stock float64) {
    ProductStock.WithLabelValues(productID).Set(stock)
}

// 记录订单处理
func RecordOrderProcessed(status string) {
    OrderProcessedTotal.WithLabelValues(status).Inc()
}

// 记录系统错误
func RecordError(service, errorType string) {
    ErrorsTotal.WithLabelValues(service, errorType).Inc()
}

7.4 告警规则配置

go 复制代码
# rules/alert_rules.yml
groups:
  - name: microshop_alerts
    rules:
      # API响应时间告警
      - alert: HighResponseTime
        expr: rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m]) > 0.5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High response time detected"
          description: "API response time is above 500ms for 5 minutes"

      # 错误率告警
      - alert: HighErrorRate
        expr: sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected"
          description: "Error rate is above 5% for 5 minutes"

      # 商品库存不足告警
      - alert: LowProductStock
        expr: product_stock_level < 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Low product stock"
          description: "Product {{ $labels.product_id }} stock is below 10 units"

      # 系统错误数量告警
      - alert: HighSystemErrors
        expr: rate(system_errors_total[5m]) > 10
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High system error rate"
          description: "Service {{ $labels.service }} is experiencing high error rate"

      # 服务实例存活告警
      - alert: ServiceDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Service is down"
          description: "Service {{ $labels.instance }} has been down for more than 1 minute"

      # CPU使用率告警
      - alert: HighCPUUsage
        expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High CPU usage"
          description: "CPU usage is above 80% for 5 minutes"

      # 内存使用率告警
      - alert: HighMemoryUsage
        expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100 > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High memory usage"
          description: "Memory usage is above 85% for 5 minutes"

      # 磁盘使用率告警
      - alert: HighDiskUsage
        expr: 100 - ((node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100) > 85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High disk usage"
          description: "Disk usage is above 85% for 5 minutes"

7.5 系统维护最佳实践

  1. 日常维护清单

    • 日志检查和分析
    • 数据库备份和优化
    • 系统性能监控
    • 安全漏洞扫描
    • 配置文件版本管理
  2. 故障处理流程

    • 故障发现和报告
    • 影响评估
    • 紧急响应
    • 问题定位和解决
    • 事后总结和改进
  3. 性能优化建议

    • 定期进行性能测试
    • 优化数据库查询
    • 调整缓存策略
    • 监控系统资源使用
    • 代码优化和重构
  4. 安全维护措施

    • 定期安全审计
    • 更新安全补丁
    • 访问权限管理
    • 数据备份和恢复
    • 安全事件响应

8. 项目扩展建议

  1. 功能扩展

    • 用户评价系统
    • 推荐系统
    • 积分系统
    • 优惠券系统
    • 售后服务系统
  2. 技术升级

    • 服务网格整合
    • GraphQL API支持
    • 实时数据分析
    • AI辅助决策
    • 区块链集成
  3. 运维优化

    • 自动化部署
    • 容灾备份
    • 多区域部署
    • 自动扩缩容
    • 智能运维

这个综合项目展示了一个完整的微服务电商系统的设计、实现和维护过程。通过合理的架构设计、可靠的代码实现、完善的测试和监控体系,以及良好的运维实践,可以构建一个高可用、可扩展、易维护的现代化微服务系统。在实际项目中,可以根据具体需求和场景进行调整和优化。


怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

相关推荐
海威的技术博客4 分钟前
JS中的原型与原型链
开发语言·javascript·原型模式
WPG大大通12 分钟前
基于DIODES AP43781+PI3USB31531+PI3DPX1207C的USB-C PD& Video 之全功能显示器连接端口方案
c语言·开发语言·计算机外设·开发板·电源·大大通
从以前26 分钟前
【算法题解】Bindian 山丘信号问题(E. Bindian Signaling)
开发语言·python·算法
kirito学长-Java32 分钟前
springboot/ssm网上宠物店系统Java代码编写web宠物用品商城项目
java·spring boot·后端
海绵波波10739 分钟前
flask后端开发(9):ORM模型外键+迁移ORM模型
后端·python·flask
余生H43 分钟前
前端Python应用指南(二)深入Flask:理解Flask的应用结构与模块化设计
前端·后端·python·flask·全栈
high20111 小时前
【Java 基础】-- ArrayList 和 Linkedlist
java·开发语言
1nullptr1 小时前
lua和C API库一些记录
开发语言·lua
Jerry Nan1 小时前
Lua元表
开发语言·lua