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

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 用户服务实现

// 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 商品服务实现

// 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 订单服务实现

// 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网关实现

// 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 系统配置和数据库模型

// 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部署配置

# 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 单元测试示例

// 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 集成测试示例

// 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 性能测试工具

# 使用 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监控配置

# 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 服务监控指标实现

// 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 告警规则配置

# 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大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!


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

相关文章:

  • 前端-react(class组件和Hooks)
  • 在 Ubuntu 上安装 Yarn 环境
  • css iframe标签使用
  • GRU (门控循环单元 - 基于RNN - 简化LSTM又快又好 - 体现注意力的思想) + 代码实现 —— 笔记3.5《动手学深度学习》
  • 网络安全,文明上网(6)网安相关法律
  • el-table最大高度无法滚动
  • 使用Faiss构建音频特征索引并计算余弦相似度
  • 基于机器视觉的表面缺陷检测
  • MySQL慢查询怎么解决
  • 动态规划-用集合的角度推导状态转移方程 — 最长上升子序列(LIS)
  • MCU通过APB总线与FPGA 数据交互(实现JATG 模块的控制)
  • Matlab|计及调峰主动性的风光水火储多能系统互补协调优化调度
  • C#里演示使用路径类Path
  • 2022 年中高职组“网络安全”赛项-海南省省竞赛任务书-1-B模块B-1-Windows操作系统渗透测试
  • Matlab函数中的隐马尔可夫模型
  • Java安全—JNDI注入RMI服务LDAP服务JDK绕过
  • AP+AC组网——STA接入
  • 大数据治理:构建数据驱动决策的核心基石
  • 十四:HTTP消息在服务器端的路由
  • 根据实验试要求,打通隧道连接服务器上的数据库,前端进行数据调用。
  • 云服务器部署WebSocket项目
  • 【Android】Service使用方法:本地服务 / 可通信服务 / 前台服务 / 远程服务(AIDL)
  • react中Fragment的使用场景
  • docker-compose快速编排docker容器
  • uniapp+vue2全局监听退出小程序清除缓存
  • 全面解析 Android 系统架构:从内核到应用层的分层设计