API 网关实现
本章节将详细介绍如何实现 API 网关,包括路由转发、协议转换(HTTP 到 gRPC)、服务发现集成和统一入口管理。
📋 学习目标
完成本章节后,你将能够:
- 理解 API 网关的作用和架构
- 实现 HTTP 路由和转发
- 实现 HTTP 到 gRPC 的协议转换
- 集成服务发现(Consul)
- 实现请求日志和监控
- 实现认证和授权(可选)
🎯 网关功能
API 网关提供以下功能:
- ✅ 统一 API 入口
- ✅ 路由转发到各个微服务
- ✅ HTTP 到 gRPC 协议转换
- ✅ 服务发现集成
- ✅ 请求日志记录
- ✅ CORS 支持
- ✅ 健康检查端点
🏗️ 网关架构
客户端 (HTTP)
↓
API 网关 (Gin)
↓
服务发现 (Consul)
↓
gRPC 客户端
↓
微服务 (gRPC)💻 网关实现
1. 创建网关结构
go
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
consulapi "github.com/hashicorp/consul/api"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
orderpb "go-study/examples/microservices/06-ecommerce-microservices/proto/order"
productpb "go-study/examples/microservices/06-ecommerce-microservices/proto/product"
userpb "go-study/examples/microservices/06-ecommerce-microservices/proto/user"
)
var (
port = flag.Int("port", 8080, "网关端口")
consulAddr = flag.String("consul", "localhost:8500", "Consul 地址")
)
func main() {
flag.Parse()
// 创建网关
gateway := NewGateway(*consulAddr)
// 创建 Gin 路由
router := gin.Default()
// 中间件
router.Use(gin.Logger())
router.Use(gin.Recovery())
router.Use(CORSMiddleware())
// 健康检查
router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "healthy"})
})
// API 路由
api := router.Group("/api")
{
// 用户服务路由
users := api.Group("/users")
{
users.POST("", gateway.CreateUser)
users.GET("/:id", gateway.GetUser)
users.PUT("/:id", gateway.UpdateUser)
users.DELETE("/:id", gateway.DeleteUser)
users.POST("/login", gateway.Login)
}
// 订单服务路由
orders := api.Group("/orders")
{
orders.POST("", gateway.CreateOrder)
orders.GET("/:id", gateway.GetOrder)
orders.GET("/user/:user_id", gateway.GetUserOrders)
orders.PUT("/:id/status", gateway.UpdateOrderStatus)
orders.DELETE("/:id", gateway.CancelOrder)
}
// 商品服务路由
products := api.Group("/products")
{
products.POST("", gateway.CreateProduct)
products.GET("/:id", gateway.GetProduct)
products.PUT("/:id", gateway.UpdateProduct)
products.DELETE("/:id", gateway.DeleteProduct)
products.GET("", gateway.ListProducts)
}
}
// 启动服务器
server := &http.Server{
Addr: fmt.Sprintf(":%d", *port),
Handler: router,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Printf("API 网关启动在端口 %d", *port)
log.Fatal(server.ListenAndServe())
}2. 网关核心结构
go
// Gateway API 网关
type Gateway struct {
consulAddr string
clients map[string]*grpc.ClientConn
}
// NewGateway 创建网关
func NewGateway(consulAddr string) *Gateway {
return &Gateway{
consulAddr: consulAddr,
clients: make(map[string]*grpc.ClientConn),
}
}
// getServiceClient 获取服务客户端
func (g *Gateway) getServiceClient(serviceName string) (interface{}, error) {
// 从 Consul 发现服务
config := consulapi.DefaultConfig()
config.Address = g.consulAddr
client, err := consulapi.NewClient(config)
if err != nil {
return nil, err
}
services, _, err := client.Health().Service(serviceName, "", true, nil)
if err != nil || len(services) == 0 {
return nil, fmt.Errorf("未找到服务: %s", serviceName)
}
service := services[0].Service
address := fmt.Sprintf("%s:%d", service.Address, service.Port)
// 检查是否已有连接
if conn, ok := g.clients[address]; ok {
return g.createClient(serviceName, conn)
}
// 创建新连接
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, err
}
g.clients[address] = conn
return g.createClient(serviceName, conn)
}
// createClient 创建服务客户端
func (g *Gateway) createClient(serviceName string, conn *grpc.ClientConn) (interface{}, error) {
switch serviceName {
case "user-service":
return userpb.NewUserServiceClient(conn), nil
case "order-service":
return orderpb.NewOrderServiceClient(conn), nil
case "product-service":
return productpb.NewProductServiceClient(conn), nil
default:
return nil, fmt.Errorf("未知服务: %s", serviceName)
}
}3. 用户服务路由处理
go
// CreateUser 创建用户
func (g *Gateway) CreateUser(c *gin.Context) {
var req userpb.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
client, err := g.getServiceClient("user-service")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
userClient := client.(userpb.UserServiceClient)
resp, err := userClient.CreateUser(context.Background(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, resp)
}
// GetUser 获取用户
func (g *Gateway) GetUser(c *gin.Context) {
userID := c.Param("id")
req := &userpb.GetUserRequest{UserId: parseID(userID)}
client, err := g.getServiceClient("user-service")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
userClient := client.(userpb.UserServiceClient)
resp, err := userClient.GetUser(context.Background(), req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, resp)
}4. CORS 中间件
go
// CORSMiddleware CORS 中间件
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
c.Writer.Header().Set("Access-Control-Max-Age", "3600")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}5. 辅助函数
go
// parseID 解析 ID
func parseID(s string) int64 {
var id int64
fmt.Sscanf(s, "%d", &id)
return id
}
// parseInt 解析整数
func parseInt(s string, defaultValue int) int {
if s == "" {
return defaultValue
}
var val int
fmt.Sscanf(s, "%d", &val)
if val <= 0 {
return defaultValue
}
return val
}🔑 关键功能
1. 服务发现
网关通过 Consul 动态发现服务:
- 自动发现服务实例
- 支持服务健康检查
- 支持负载均衡
2. 协议转换
将 HTTP 请求转换为 gRPC 调用:
- HTTP JSON → gRPC Protobuf
- HTTP 响应 ← gRPC 响应
3. 连接管理
- 复用 gRPC 连接
- 连接池管理
- 自动重连
🚀 运行网关
bash
# 确保所有服务已启动
# 1. 启动 Consul
consul agent -dev
# 2. 启动用户服务
cd user-service && go run main.go
# 3. 启动商品服务
cd product-service && go run main.go
# 4. 启动订单服务
cd order-service && go run main.go
# 5. 启动网关
cd gateway
go mod tidy
go run main.go🧪 测试 API
bash
# 创建用户
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"email": "test@example.com",
"password": "password123",
"full_name": "Test User"
}'
# 获取用户
curl http://localhost:8080/api/users/1
# 创建商品
curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{
"name": "Test Product",
"description": "Test Description",
"price": 99.99,
"stock": 100,
"category": "electronics"
}'
# 创建订单
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{
"user_id": 1,
"items": [
{
"product_id": 1,
"quantity": 2,
"price": 99.99
}
],
"shipping_address": "123 Main St"
}'📚 下一步
API 网关实现完成后,可以继续:
- 部署运维 - 部署和运维实践
✅ API 网关实现完成! 现在可以部署整个系统了。
