Go语言微服务架构实战:基于Gin + gRPC + MySQL的完整项目开发流程

Alice346
Alice346 2026-02-05T18:07:04+08:00
0 0 1

前言

在现代软件开发中,微服务架构已成为构建可扩展、可维护应用的重要模式。Go语言凭借其高性能、并发性强和部署简单的特点,成为微服务开发的热门选择。本文将通过一个完整的实战项目,详细介绍如何使用Go语言结合Gin Web框架、gRPC远程调用和MySQL数据库来构建一个微服务系统。

项目概述

我们将构建一个简单的用户管理系统,包含用户注册、登录、查询等功能。整个系统采用微服务架构,将不同的业务功能拆分为独立的服务模块,通过gRPC进行服务间通信,使用MySQL作为数据存储。

技术栈说明

  • Go语言:核心编程语言
  • Gin框架:Web API开发框架
  • gRPC:服务间通信协议
  • MySQL:关系型数据库
  • Docker:容器化部署
  • Protobuf:接口定义语言

项目初始化与结构设计

项目目录结构

user-service/
├── cmd/
│   └── user-service/
│       └── main.go
├── internal/
│   ├── config/
│   │   └── config.go
│   ├── handler/
│   │   └── user_handler.go
│   ├── model/
│   │   ├── user.go
│   │   └── user.pb.go
│   ├── repository/
│   │   └── user_repository.go
│   ├── service/
│   │   └── user_service.go
│   └── utils/
│       └── database.go
├── proto/
│   └── user.proto
├── docker-compose.yml
├── Dockerfile
└── go.mod

初始化Go模块

mkdir user-service
cd user-service
go mod init user-service

MySQL数据库设计与配置

数据库表结构

首先创建用户表:

CREATE DATABASE IF NOT EXISTS user_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE user_db;

CREATE TABLE IF NOT EXISTS users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

数据库连接配置

// internal/config/config.go
package config

import (
    "fmt"
    "os"
    "time"
)

type DatabaseConfig struct {
    Host     string
    Port     string
    User     string
    Password string
    Name     string
    Charset  string
}

type Config struct {
    Database DatabaseConfig
    Server   ServerConfig
}

type ServerConfig struct {
    Port string
}

func LoadConfig() (*Config, error) {
    config := &Config{
        Database: DatabaseConfig{
            Host:     getEnv("DB_HOST", "localhost"),
            Port:     getEnv("DB_PORT", "3306"),
            User:     getEnv("DB_USER", "root"),
            Password: getEnv("DB_PASSWORD", ""),
            Name:     getEnv("DB_NAME", "user_db"),
            Charset:  getEnv("DB_CHARSET", "utf8mb4"),
        },
        Server: ServerConfig{
            Port: getEnv("SERVER_PORT", "8080"),
        },
    }
    
    return config, nil
}

func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

数据库连接工具

// internal/utils/database.go
package utils

import (
    "database/sql"
    "fmt"
    "log"
    
    _ "github.com/go-sql-driver/mysql"
    "user-service/internal/config"
)

func InitDB(cfg *config.DatabaseConfig) (*sql.DB, error) {
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local",
        cfg.User,
        cfg.Password,
        cfg.Host,
        cfg.Port,
        cfg.Name,
        cfg.Charset,
    )
    
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        return nil, fmt.Errorf("failed to open database: %v", err)
    }
    
    // 设置连接池参数
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(5 * time.Minute)
    
    // 测试连接
    if err := db.Ping(); err != nil {
        return nil, fmt.Errorf("failed to ping database: %v", err)
    }
    
    log.Println("Database connected successfully")
    return db, nil
}

gRPC服务定义与实现

Protobuf接口定义

// proto/user.proto
syntax = "proto3";

package user;

option go_package = "./;user";

service UserService {
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
  rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
}

message User {
  int64 id = 1;
  string username = 2;
  string email = 3;
  string password = 4;
  string created_at = 5;
  string updated_at = 6;
}

message CreateUserRequest {
  string username = 1;
  string email = 2;
  string password = 3;
}

message CreateUserResponse {
  int64 id = 1;
  bool success = 2;
  string message = 3;
}

message GetUserRequest {
  int64 id = 1;
}

message GetUserResponse {
  User user = 1;
  bool success = 2;
  string message = 3;
}

message UpdateUserRequest {
  int64 id = 1;
  string username = 2;
  string email = 3;
  string password = 4;
}

message UpdateUserResponse {
  bool success = 1;
  string message = 2;
}

message DeleteUserRequest {
  int64 id = 1;
}

message DeleteUserResponse {
  bool success = 1;
  string message = 2;
}

gRPC服务实现

// internal/service/user_service.go
package service

import (
    "context"
    "database/sql"
    "fmt"
    "log"
    "time"
    
    "user-service/internal/model"
    "user-service/internal/repository"
    
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

type UserService struct {
    userRepo *repository.UserRepository
    model.UnimplementedUserServiceServer
}

func NewUserService(db *sql.DB) *UserService {
    return &UserService{
        userRepo: repository.NewUserRepository(db),
    }
}

func (s *UserService) CreateUser(ctx context.Context, req *model.CreateUserRequest) (*model.CreateUserResponse, error) {
    log.Printf("Creating user: %s", req.Username)
    
    // 验证输入
    if req.Username == "" || req.Email == "" || req.Password == "" {
        return &model.CreateUserResponse{
            Success: false,
            Message: "Username, email and password are required",
        }, status.Error(codes.InvalidArgument, "Username, email and password are required")
    }
    
    // 检查用户是否已存在
    existingUser, err := s.userRepo.FindByUsername(ctx, req.Username)
    if err != nil && err != sql.ErrNoRows {
        return &model.CreateUserResponse{
            Success: false,
            Message: "Database error",
        }, status.Error(codes.Internal, "Database error")
    }
    
    if existingUser != nil {
        return &model.CreateUserResponse{
            Success: false,
            Message: "Username already exists",
        }, status.Error(codes.AlreadyExists, "Username already exists")
    }
    
    // 创建用户
    user, err := s.userRepo.Create(ctx, req.Username, req.Email, req.Password)
    if err != nil {
        return &model.CreateUserResponse{
            Success: false,
            Message: "Failed to create user",
        }, status.Error(codes.Internal, "Failed to create user")
    }
    
    log.Printf("User created successfully with ID: %d", user.ID)
    
    return &model.CreateUserResponse{
        Id:      user.ID,
        Success: true,
        Message: "User created successfully",
    }, nil
}

func (s *UserService) GetUser(ctx context.Context, req *model.GetUserRequest) (*model.GetUserResponse, error) {
    log.Printf("Getting user by ID: %d", req.Id)
    
    if req.Id <= 0 {
        return &model.GetUserResponse{
            Success: false,
            Message: "Invalid user ID",
        }, status.Error(codes.InvalidArgument, "Invalid user ID")
    }
    
    user, err := s.userRepo.FindByID(ctx, req.Id)
    if err != nil {
        if err == sql.ErrNoRows {
            return &model.GetUserResponse{
                Success: false,
                Message: "User not found",
            }, status.Error(codes.NotFound, "User not found")
        }
        return &model.GetUserResponse{
            Success: false,
            Message: "Database error",
        }, status.Error(codes.Internal, "Database error")
    }
    
    return &model.GetUserResponse{
        User: &model.User{
            Id:        user.ID,
            Username:  user.Username,
            Email:     user.Email,
            Password:  user.Password,
            CreatedAt: user.CreatedAt.Format(time.RFC3339),
            UpdatedAt: user.UpdatedAt.Format(time.RFC3339),
        },
        Success: true,
        Message: "User retrieved successfully",
    }, nil
}

func (s *UserService) UpdateUser(ctx context.Context, req *model.UpdateUserRequest) (*model.UpdateUserResponse, error) {
    log.Printf("Updating user ID: %d", req.Id)
    
    if req.Id <= 0 {
        return &model.UpdateUserResponse{
            Success: false,
            Message: "Invalid user ID",
        }, status.Error(codes.InvalidArgument, "Invalid user ID")
    }
    
    // 检查用户是否存在
    existingUser, err := s.userRepo.FindByID(ctx, req.Id)
    if err != nil {
        if err == sql.ErrNoRows {
            return &model.UpdateUserResponse{
                Success: false,
                Message: "User not found",
            }, status.Error(codes.NotFound, "User not found")
        }
        return &model.UpdateUserResponse{
            Success: false,
            Message: "Database error",
        }, status.Error(codes.Internal, "Database error")
    }
    
    // 更新用户信息
    updatedUser, err := s.userRepo.Update(ctx, req.Id, req.Username, req.Email, req.Password)
    if err != nil {
        return &model.UpdateUserResponse{
            Success: false,
            Message: "Failed to update user",
        }, status.Error(codes.Internal, "Failed to update user")
    }
    
    log.Printf("User updated successfully: %s", updatedUser.Username)
    
    return &model.UpdateUserResponse{
        Success: true,
        Message: "User updated successfully",
    }, nil
}

func (s *UserService) DeleteUser(ctx context.Context, req *model.DeleteUserRequest) (*model.DeleteUserResponse, error) {
    log.Printf("Deleting user ID: %d", req.Id)
    
    if req.Id <= 0 {
        return &model.DeleteUserResponse{
            Success: false,
            Message: "Invalid user ID",
        }, status.Error(codes.InvalidArgument, "Invalid user ID")
    }
    
    err := s.userRepo.Delete(ctx, req.Id)
    if err != nil {
        if err == sql.ErrNoRows {
            return &model.DeleteUserResponse{
                Success: false,
                Message: "User not found",
            }, status.Error(codes.NotFound, "User not found")
        }
        return &model.DeleteUserResponse{
            Success: false,
            Message: "Database error",
        }, status.Error(codes.Internal, "Database error")
    }
    
    log.Printf("User deleted successfully: %d", req.Id)
    
    return &model.DeleteUserResponse{
        Success: true,
        Message: "User deleted successfully",
    }, nil
}

MySQL数据访问层实现

用户数据模型

// internal/model/user.go
package model

import (
    "time"
)

type User struct {
    ID        int64     `json:"id"`
    Username  string    `json:"username"`
    Email     string    `json:"email"`
    Password  string    `json:"password"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

数据访问层实现

// internal/repository/user_repository.go
package repository

import (
    "context"
    "database/sql"
    "fmt"
    "time"
    
    "user-service/internal/model"
)

type UserRepository struct {
    db *sql.DB
}

func NewUserRepository(db *sql.DB) *UserRepository {
    return &UserRepository{
        db: db,
    }
}

func (r *UserRepository) Create(ctx context.Context, username, email, password string) (*model.User, error) {
    query := `INSERT INTO users (username, email, password, created_at, updated_at) 
              VALUES (?, ?, ?, ?, ?)`
    
    now := time.Now()
    _, err := r.db.ExecContext(ctx, query, username, email, password, now, now)
    if err != nil {
        return nil, fmt.Errorf("failed to create user: %v", err)
    }
    
    // 获取新创建的用户
    user, err := r.FindByUsername(ctx, username)
    if err != nil {
        return nil, fmt.Errorf("failed to get created user: %v", err)
    }
    
    return user, nil
}

func (r *UserRepository) FindByID(ctx context.Context, id int64) (*model.User, error) {
    query := `SELECT id, username, email, password, created_at, updated_at 
              FROM users WHERE id = ?`
    
    var user model.User
    err := r.db.QueryRowContext(ctx, query, id).Scan(
        &user.ID,
        &user.Username,
        &user.Email,
        &user.Password,
        &user.CreatedAt,
        &user.UpdatedAt,
    )
    
    if err != nil {
        return nil, err
    }
    
    return &user, nil
}

func (r *UserRepository) FindByUsername(ctx context.Context, username string) (*model.User, error) {
    query := `SELECT id, username, email, password, created_at, updated_at 
              FROM users WHERE username = ?`
    
    var user model.User
    err := r.db.QueryRowContext(ctx, query, username).Scan(
        &user.ID,
        &user.Username,
        &user.Email,
        &user.Password,
        &user.CreatedAt,
        &user.UpdatedAt,
    )
    
    if err != nil {
        return nil, err
    }
    
    return &user, nil
}

func (r *UserRepository) Update(ctx context.Context, id int64, username, email, password string) (*model.User, error) {
    query := `UPDATE users SET username = ?, email = ?, password = ?, updated_at = ? 
              WHERE id = ?`
    
    now := time.Now()
    _, err := r.db.ExecContext(ctx, query, username, email, password, now, id)
    if err != nil {
        return nil, fmt.Errorf("failed to update user: %v", err)
    }
    
    // 获取更新后的用户
    user, err := r.FindByID(ctx, id)
    if err != nil {
        return nil, fmt.Errorf("failed to get updated user: %v", err)
    }
    
    return user, nil
}

func (r *UserRepository) Delete(ctx context.Context, id int64) error {
    query := `DELETE FROM users WHERE id = ?`
    _, err := r.db.ExecContext(ctx, query, id)
    if err != nil {
        return fmt.Errorf("failed to delete user: %v", err)
    }
    
    return nil
}

Gin Web API实现

HTTP路由和处理器

// internal/handler/user_handler.go
package handler

import (
    "context"
    "encoding/json"
    "net/http"
    "time"
    
    "user-service/internal/model"
    "user-service/internal/service"
    
    "github.com/gin-gonic/gin"
    "google.golang.org/grpc"
)

type UserHandler struct {
    userService *service.UserService
    grpcConn    *grpc.ClientConn
}

func NewUserHandler(userService *service.UserService, grpcConn *grpc.ClientConn) *UserHandler {
    return &UserHandler{
        userService: userService,
        grpcConn:    grpcConn,
    }
}

// CreateUser 创建用户
func (h *UserHandler) CreateUser(c *gin.Context) {
    var req model.CreateUserRequest
    
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": false,
            "message": "Invalid request body",
            "error":   err.Error(),
        })
        return
    }
    
    // 调用gRPC服务
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    client := model.NewUserServiceClient(h.grpcConn)
    response, err := client.CreateUser(ctx, &req)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "message": "Failed to create user",
            "error":   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, gin.H{
        "success": response.Success,
        "message": response.Message,
        "data": gin.H{
            "id": response.Id,
        },
    })
}

// GetUser 获取用户信息
func (h *UserHandler) GetUser(c *gin.Context) {
    id := c.Param("id")
    
    // 验证ID格式
    if id == "" {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": false,
            "message": "User ID is required",
        })
        return
    }
    
    // 调用gRPC服务
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    client := model.NewUserServiceClient(h.grpcConn)
    response, err := client.GetUser(ctx, &model.GetUserRequest{
        Id: parseInt64(id),
    })
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "message": "Failed to get user",
            "error":   err.Error(),
        })
        return
    }
    
    if !response.Success {
        c.JSON(http.StatusNotFound, gin.H{
            "success": false,
            "message": response.Message,
        })
        return
    }
    
    c.JSON(http.StatusOK, gin.H{
        "success": response.Success,
        "message": response.Message,
        "data":    response.User,
    })
}

// UpdateUser 更新用户信息
func (h *UserHandler) UpdateUser(c *gin.Context) {
    id := c.Param("id")
    
    var req model.UpdateUserRequest
    
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": false,
            "message": "Invalid request body",
            "error":   err.Error(),
        })
        return
    }
    
    // 设置ID
    req.Id = parseInt64(id)
    
    // 调用gRPC服务
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    client := model.NewUserServiceClient(h.grpcConn)
    response, err := client.UpdateUser(ctx, &req)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "message": "Failed to update user",
            "error":   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, gin.H{
        "success": response.Success,
        "message": response.Message,
    })
}

// DeleteUser 删除用户
func (h *UserHandler) DeleteUser(c *gin.Context) {
    id := c.Param("id")
    
    // 调用gRPC服务
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    client := model.NewUserServiceClient(h.grpcConn)
    response, err := client.DeleteUser(ctx, &model.DeleteUserRequest{
        Id: parseInt64(id),
    })
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "message": "Failed to delete user",
            "error":   err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, gin.H{
        "success": response.Success,
        "message": response.Message,
    })
}

// ListUsers 获取用户列表
func (h *UserHandler) ListUsers(c *gin.Context) {
    // 这里可以实现分页查询逻辑
    c.JSON(http.StatusOK, gin.H{
        "success": true,
        "message": "List users endpoint",
        "data":    []interface{}{},
    })
}

// 辅助函数:字符串转int64
func parseInt64(s string) int64 {
    var result int64
    json.Unmarshal([]byte(s), &result)
    return result
}

Gin应用启动

// cmd/user-service/main.go
package main

import (
    "context"
    "log"
    "net"
    "os"
    "os/signal"
    "time"
    
    "user-service/internal/config"
    "user-service/internal/handler"
    "user-service/internal/service"
    "user-service/internal/utils"
    
    "github.com/gin-gonic/gin"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
)

func main() {
    // 加载配置
    cfg, err := config.LoadConfig()
    if err != nil {
        log.Fatal("Failed to load config:", err)
    }
    
    // 初始化数据库
    db, err := utils.InitDB(&cfg.Database)
    if err != nil {
        log.Fatal("Failed to initialize database:", err)
    }
    defer db.Close()
    
    // 创建gRPC服务
    grpcServer := grpc.NewServer()
    
    // 创建用户服务并注册到gRPC服务器
    userService := service.NewUserService(db)
    // 这里应该使用实际的gRPC服务注册代码
    
    // 启动gRPC服务器
    go func() {
        log.Println("Starting gRPC server on :50051")
        lis, err := net.Listen("tcp", ":50051")
        if err != nil {
            log.Fatalf("Failed to listen: %v", err)
        }
        
        // 注册服务
        // model.RegisterUserServiceServer(grpcServer, userService)
        reflection.Register(grpcServer)
        
        if err := grpcServer.Serve(lis); err != nil {
            log.Fatalf("Failed to serve: %v", err)
        }
    }()
    
    // 创建Gin路由
    r := gin.Default()
    
    // 初始化用户处理器
    // userHandler := handler.NewUserHandler(userService, grpcConn)
    
    // 定义API路由
    api := r.Group("/api/v1")
    {
        // 用户相关路由
        users := api.Group("/users")
        {
            users.POST("/", func(c *gin.Context) {
                c.JSON(200, gin.H{
                    "message": "Create user endpoint",
                })
            })
            users.GET("/:id", func(c *gin.Context) {
                c.JSON(200, gin.H{
                    "message": "Get user endpoint",
                })
            })
            users.PUT("/:id", func(c *gin.Context) {
                c.JSON(200, gin.H{
                    "message": "Update user endpoint",
                })
            })
            users.DELETE("/:id", func(c *gin.Context) {
                c.JSON(200, gin.H{
                    "message": "Delete user endpoint",
                })
            })
        }
    }
    
    // 启动HTTP服务器
    go func() {
        log.Printf("Starting HTTP server on :%s", cfg.Server.Port)
        if err := r.Run(":" + cfg.Server.Port); err != nil {
            log.Fatalf("Failed to start HTTP server: %v", err)
        }
    }()
    
    // 等待中断信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit
    
    log.Println("Shutting down server...")
    
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    // 停止gRPC服务器
    grpcServer.GracefulStop()
    
    log.Println("Server exited")
}

项目构建与部署

Dockerfile配置

# 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 -a -installsuffix cgo -o user-service cmd/user-service/main.go

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/user-service .
EXPOSE 8080 50051
CMD ["./user-service"]

Docker Compose配置

# docker-compose.yml
version: '3.8'

services:
  mysql:
    image: mysql:8.0
    container_name: user-mysql
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: user_db
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - user-network

  user-service:
    build: .
    container_name: user-service
    ports:
      - "8080:8080"
      - "50051:50051"
    depends_on:
      - mysql
    environment:
      DB_HOST: mysql
      DB_PORT: 3306
      DB_USER: user
      DB_PASSWORD: password
      DB_NAME: user_db
      SERVER_PORT: 8080
    networks:
      - user-network

volumes:
  mysql_data:

networks:
  user-network:
    driver: bridge

初始化SQL脚本

-- init.sql
CREATE DATABASE IF NOT EXISTS user_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE user_db;

CREATE TABLE IF NOT EXISTS users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 插入测试数据
INSERT INTO users (username, email, password) VALUES 
('admin
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000