基于 Laravel 12 的 API 网关设置与使用指南

基于 Laravel 12 的 API 网关设置与使用指南

在微服务架构中,API 网关是系统的"前门",负责处理所有客户端请求并将它们路由到适当的后端服务。下面我将详细介绍如何在 Laravel 12 中设置和使用 API 网关。

API 网关的核心功能

  1. 请求路由 - 将请求转发到适当的微服务
  2. 认证授权 - 集中处理身份验证
  3. 负载均衡 - 在多个服务实例间分配流量
  4. 熔断机制 - 防止级联故障
  5. 日志与监控 - 收集所有请求的日志和指标
  6. API 聚合 - 组合多个服务的响应

设置 API 网关的步骤

1. 创建 API 网关项目

composer create-project laravel/laravel api-gateway
cd api-gateway

2. 安装所需依赖

composer require guzzlehttp/guzzle # 用于请求转发
composer require stechstudio/laravel-consul # Consul 服务发现
composer require firebase/php-jwt # JWT 验证

3. 配置环境变量(.env)

APP_NAME="API Gateway"
APP_ENV=production

# Consul 服务发现
CONSUL_HOST=consul
CONSUL_PORT=8500

# JWT 配置
JWT_SECRET=your_super_secret_key
JWT_ALGORITHM=HS256
JWT_EXPIRY=3600

# 服务配置
AUTH_SERVICE_URL=http://auth-service
ORDER_SERVICE_URL=http://order-service
PRODUCT_SERVICE_URL=http://product-service

4. 创建网关控制器

app/Http/Controllers/GatewayController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use Exception;

class GatewayController extends Controller
{
    // 服务映射配置
    protected $serviceMap = [
        'auth' => 'AUTH_SERVICE_URL',
        'order' => 'ORDER_SERVICE_URL',
        'product' => 'PRODUCT_SERVICE_URL'
    ];

    /**
     * 处理所有传入请求
     */
    public function handle(Request $request)
    {
        // 提取服务名和端点
        $pathParts = explode('/', $request->path());
        $serviceName = $pathParts[0] ?? null;
        $endpoint = implode('/', array_slice($pathParts, 1));

        // 验证服务是否存在
        if (!$serviceName || !array_key_exists($serviceName, $this->serviceMap)) {
            return response()->json(['error' => 'Service not found'], 404);
        }

        // 跳过认证服务的登录和注册请求
        if ($serviceName !== 'auth' || ($endpoint !== 'login' && $endpoint !== 'register')) {
            // 验证 JWT
            try {
                $this->authenticate($request);
            } catch (Exception $e) {
                return response()->json(['error' => $e->getMessage()], 401);
            }
        }

        // 获取目标服务 URL
        $serviceUrl = env($this->serviceMap[$serviceName]);
        $targetUrl = rtrim($serviceUrl, '/') . '/' . $endpoint;

        // 构建请求选项
        $options = $this->buildRequestOptions($request);

        try {
            // 转发请求
            $response = Http::withOptions($options)
                ->send($request->method(), $targetUrl);

            return response($response->body(), $response->status())
                ->withHeaders($response->headers());

        } catch (\Exception $e) {
            Log::error("Gateway forwarding error: {$e->getMessage()}");
            return response()->json(['error' => 'Service unavailable'], 503);
        }
    }

    /**
     * 验证 JWT Token
     */
    private function authenticate(Request $request)
    {
        $token = $request->bearerToken();
        
        if (!$token) {
            throw new Exception('Missing authorization token');
        }

        try {
            $decoded = JWT::decode($token, new Key(env('JWT_SECRET'), env('JWT_ALGORITHM')));
            $request->attributes->add(['user' => (array)$decoded]);
        } catch (ExpiredException $e) {
            throw new Exception('Token expired');
        } catch (SignatureInvalidException $e) {
            throw new Exception('Invalid token signature');
        } catch (Exception $e) {
            throw new Exception('Unauthorized');
        }
    }

    /**
     * 构建请求选项
     */
    private function buildRequestOptions(Request $request)
    {
        return [
            'query' => $request->query(),
            'headers' => $request->headers->all(),
            'json' => $request->json()->all(),
            'form_params' => $request->post(),
            'body' => $request->getContent(),
            'timeout' => 15,
            'verify' => false, // 仅用于开发环境,生产环境应使用有效证书
            'http_errors' => false
        ];
    }
}

5. 创建路由文件

routes/api.php

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\GatewayController;

// 捕获所有路由并转发到网关控制器
Route::any('{any}', [GatewayController::class, 'handle'])
    ->where('any', '.*');

6. 创建熔断器中间件

app/Http/Middleware/CircuitBreaker.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class CircuitBreaker
{
    // 熔断器配置
    protected $config = [
        'failure_threshold' => 5,    // 连续失败多少次触发熔断
        'success_threshold' => 3,    // 熔断后需要多少次成功才能恢复
        'timeout' => 60,             // 熔断持续时间(秒)
    ];

    public function handle($request, Closure $next)
    {
        $service = $request->route('any') ? explode('/', $request->route('any'))[0] : '';
        $cacheKey = "circuit:{$service}";
        
        // 检查服务是否在熔断状态
        if ($this->isCircuitOpen($cacheKey)) {
            Log::warning("Circuit breaker open for service: {$service}");
            return response()->json(['error' => 'Service unavailable'], 503);
        }

        // 处理请求
        $response = $next($request);
        
        // 检查响应状态
        if ($response->getStatusCode() >= 500) {
            $this->recordFailure($cacheKey);
        } else {
            $this->recordSuccess($cacheKey);
        }

        return $response;
    }

    /**
     * 检查熔断器是否打开
     */
    private function isCircuitOpen($cacheKey)
    {
        $state = Cache::get($cacheKey);
        
        if ($state && $state['status'] === 'open') {
            // 检查是否应该尝试半开状态
            if (now()->diffInSeconds($state['last_open']) > $this->config['timeout']) {
                Cache::put($cacheKey, [
                    'status' => 'half-open',
                    'success_count' => 0,
                    'last_open' => now()
                ], now()->addMinutes(10));
                
                return false;
            }
            
            return true;
        }
        
        return false;
    }

    /**
     * 记录失败
     */
    private function recordFailure($cacheKey)
    {
        $state = Cache::get($cacheKey, [
            'status' => 'closed',
            'failure_count' => 0
        ]);

        $state['failure_count']++;
        
        // 达到失败阈值,打开熔断器
        if ($state['failure_count'] >= $this->config['failure_threshold']) {
            $state['status'] = 'open';
            $state['last_open'] = now();
            Log::warning("Opening circuit breaker for {$cacheKey}");
        }
        
        Cache::put($cacheKey, $state, now()->addMinutes(10));
    }

    /**
     * 记录成功
     */
    private function recordSuccess($cacheKey)
    {
        $state = Cache::get($cacheKey);
        
        if (!$state) {
            return;
        }
        
        // 半开状态下计数成功
        if ($state['status'] === 'half-open') {
            $state['success_count']++;
            
            // 达到成功阈值,关闭熔断器
            if ($state['success_count'] >= $this->config['success_threshold']) {
                $state = [
                    'status' => 'closed',
                    'failure_count' => 0
                ];
                Log::info("Closing circuit breaker for {$cacheKey}");
            }
        } else {
            // 重置失败计数
            $state['failure_count'] = 0;
        }
        
        Cache::put($cacheKey, $state, now()->addMinutes(10));
    }
}

7. 注册中间件

app/Http/Kernel.php

protected $middlewareGroups = [
    'api' => [
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \App\Http\Middleware\CircuitBreaker::class, // 添加熔断器中间件
    ],
];

8. 运行网关服务器

php artisan serve --port=8000

使用 API 网关

1. 通过网关访问服务

所有请求都应发送到网关,网关会根据路径将请求路由到正确的服务:

http://gateway:8000/auth/login
http://gateway:8000/order/123
http://gateway:8000/product/search?q=laptop

2. 客户端请求示例

// 登录请求
fetch('http://gateway:8000/auth/login', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({
    email: 'user@example.com',
    password: 'password'
  })
})
.then(response => response.json())
.then(data => {
  // 保存 token 用于后续请求
  const token = data.token;
  
  // 获取用户订单
  fetch('http://gateway:8000/order/user', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  })
  .then(response => response.json())
  .then(orders => {
    console.log('User orders:', orders);
  });
});

3. 健康检查端点

在网关中添加健康检查端点:

routes/api.php

Route::get('/health', function () {
    return response()->json([
        'status' => 'up',
        'services' => [
            'auth' => Http::get(env('AUTH_SERVICE_URL') . '/health')->successful(),
            'order' => Http::get(env('ORDER_SERVICE_URL') . '/health')->successful(),
            'product' => Http::get(env('PRODUCT_SERVICE_URL') . '/health')->successful()
        ]
    ]);
});

生产环境优化

1. 性能优化

  • 使用 Swoole 或 RoadRunner 替代 PHP-FPM
  • 启用 OPCache
  • 缓存服务发现结果

2. 安全性增强

  • 启用 HTTPS
  • 设置速率限制
  • 添加 WAF 规则
  • 过滤敏感头信息

3. 高可用性

  • 部署多个网关实例
  • 使用负载均衡器(如 Nginx)
  • 配置自动伸缩策略

4. 监控与日志

  • 集成 Prometheus 监控
  • 发送日志到 ELK 或 Loki
  • 设置警报规则

常见问题解决

问题:服务间通信延迟高

  • 解决方案:实现本地缓存,减少服务发现调用次数

问题:认证服务不可用

  • 解决方案:实现 JWT 本地验证,避免每次请求都调用认证服务

问题:网关成为性能瓶颈

  • 解决方案:优化中间件链,移除不必要的中间件

通过以上设置,创建了一个功能完备的 API 网关,能有效管理微服务架构中的请求路由、认证授权和熔断保护等功能。这个网关设计考虑了扩展性、性能和可靠性,是微服务架构的关键组件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值