基于 Laravel 12 的 API 网关设置与使用指南
在微服务架构中,API 网关是系统的"前门",负责处理所有客户端请求并将它们路由到适当的后端服务。下面我将详细介绍如何在 Laravel 12 中设置和使用 API 网关。
API 网关的核心功能
- 请求路由 - 将请求转发到适当的微服务
- 认证授权 - 集中处理身份验证
- 负载均衡 - 在多个服务实例间分配流量
- 熔断机制 - 防止级联故障
- 日志与监控 - 收集所有请求的日志和指标
- 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 网关,能有效管理微服务架构中的请求路由、认证授权和熔断保护等功能。这个网关设计考虑了扩展性、性能和可靠性,是微服务架构的关键组件。