Laravel 13 第三方 API 集成完全指南
现代应用经常需要与各种第三方 API 进行集成。本文将深入探讨 Laravel 13 中集成第三方 API 的最佳实践和高级技巧。
HTTP 客户端基础
基础请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| use Illuminate\Support\Facades\Http;
$response = Http::get('https://api.example.com/users');
$response = Http::post('https://api.example.com/users', [ 'name' => 'John Doe', 'email' => 'john@example.com', ]);
$response = Http::put('https://api.example.com/users/1', [ 'name' => 'Jane Doe', ]);
$response = Http::delete('https://api.example.com/users/1');
|
请求选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| $response = Http::withHeaders([ 'Accept' => 'application/json', 'X-Custom-Header' => 'value', ])->get('https://api.example.com/users');
$response = Http::withToken('your-api-token') ->get('https://api.example.com/users');
$response = Http::withBasicAuth('username', 'password') ->get('https://api.example.com/users');
$response = Http::withDigestAuth('username', 'password') ->get('https://api.example.com/users');
$response = Http::withQueryParameters([ 'page' => 1, 'per_page' => 15, 'sort' => 'name', ])->get('https://api.example.com/users');
|
处理响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| $response = Http::get('https://api.example.com/users');
$statusCode = $response->status(); $ok = $response->ok(); $successful = $response->successful(); $failed = $response->failed(); $clientError = $response->clientError(); $serverError = $response->serverError();
$body = $response->body(); $json = $response->json(); $data = $response->object();
$name = $response->json('data.user.name'); $users = $response->json('data.*.name');
$headers = $response->headers(); $contentType = $response->header('Content-Type');
|
封装 API 服务
创建 API 服务类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| <?php
namespace App\Services\Api;
use Illuminate\Support\Facades\Http; use Illuminate\Http\Client\PendingRequest;
abstract class BaseApiService { protected string $baseUrl; protected int $timeout = 30; protected int $retryTimes = 3; protected int $retrySleep = 100;
protected function client(): PendingRequest { return Http::baseUrl($this->baseUrl) ->timeout($this->timeout) ->retry($this->retryTimes, $this->retrySleep) ->withHeaders($this->getDefaultHeaders()); }
protected function getDefaultHeaders(): array { return [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', ]; }
protected function get(string $uri, array $query = []): array { return $this->client() ->get($uri, $query) ->throw() ->json(); }
protected function post(string $uri, array $data = []): array { return $this->client() ->post($uri, $data) ->throw() ->json(); }
protected function put(string $uri, array $data = []): array { return $this->client() ->put($uri, $data) ->throw() ->json(); }
protected function delete(string $uri): array { return $this->client() ->delete($uri) ->throw() ->json(); } }
|
具体服务实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <?php
namespace App\Services\Api;
class GitHubApiService extends BaseApiService { protected string $baseUrl = 'https://api.github.com';
public function __construct( protected string $token ) {}
protected function getDefaultHeaders(): array { return array_merge(parent::getDefaultHeaders(), [ 'Authorization' => "Bearer {$this->token}", 'X-GitHub-Api-Version' => '2022-11-28', ]); }
public function getUser(string $username): array { return $this->get("users/{$username}"); }
public function getRepositories(string $username, array $options = []): array { return $this->get("users/{$username}/repos", $options); }
public function getRepository(string $owner, string $repo): array { return $this->get("repos/{$owner}/{$repo}"); }
public function getIssues(string $owner, string $repo, array $filters = []): array { return $this->get("repos/{$owner}/{$repo}/issues", $filters); }
public function createIssue(string $owner, string $repo, array $data): array { return $this->post("repos/{$owner}/{$repo}/issues", $data); } }
|
OAuth 集成
OAuth 服务类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <?php
namespace App\Services\OAuth;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Str;
class OAuthService { protected string $authorizeUrl; protected string $tokenUrl; protected string $clientId; protected string $clientSecret; protected string $redirectUri;
public function getAuthorizationUrl(string $state = null): string { $params = http_build_query([ 'client_id' => $this->clientId, 'redirect_uri' => $this->redirectUri, 'response_type' => 'code', 'scope' => 'read write', 'state' => $state ?? Str::random(40), ]);
return "{$this->authorizeUrl}?{$params}"; }
public function getAccessToken(string $code): array { $response = Http::asForm()->post($this->tokenUrl, [ 'grant_type' => 'authorization_code', 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'redirect_uri' => $this->redirectUri, 'code' => $code, ]);
return $response->json(); }
public function refreshAccessToken(string $refreshToken): array { $response = Http::asForm()->post($this->tokenUrl, [ 'grant_type' => 'refresh_token', 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'refresh_token' => $refreshToken, ]);
return $response->json(); } }
|
Google OAuth 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php
namespace App\Services\OAuth;
class GoogleOAuthService extends OAuthService { protected string $authorizeUrl = 'https://accounts.google.com/o/oauth2/v2/auth'; protected string $tokenUrl = 'https://oauth2.googleapis.com/token';
public function __construct() { $this->clientId = config('services.google.client_id'); $this->clientSecret = config('services.google.client_secret'); $this->redirectUri = config('services.google.redirect'); }
public function getUserInfo(string $accessToken): array { return Http::withToken($accessToken) ->get('https://www.googleapis.com/oauth2/v2/userinfo') ->json(); } }
|
支付网关集成
Stripe 集成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| <?php
namespace App\Services\Payment;
use Stripe\Stripe; use Stripe\PaymentIntent; use Stripe\Customer; use Stripe\Refund;
class StripeService { public function __construct() { Stripe::setApiKey(config('services.stripe.secret')); Stripe::setApiVersion('2023-10-16'); }
public function createCustomer(array $data): Customer { return Customer::create([ 'email' => $data['email'], 'name' => $data['name'], 'metadata' => $data['metadata'] ?? [], ]); }
public function createPaymentIntent(int $amount, string $currency = 'usd', array $options = []): PaymentIntent { return PaymentIntent::create([ 'amount' => $amount, 'currency' => $currency, 'automatic_payment_methods' => [ 'enabled' => true, ], ...$options, ]); }
public function retrievePaymentIntent(string $id): PaymentIntent { return PaymentIntent::retrieve($id); }
public function refund(string $paymentIntentId, ?int $amount = null): Refund { return Refund::create([ 'payment_intent' => $paymentIntentId, 'amount' => $amount, ]); }
public function handleWebhook(string $payload, string $signature): array { $event = \Stripe\Webhook::constructEvent( $payload, $signature, config('services.stripe.webhook_secret') );
return match ($event->type) { 'payment_intent.succeeded' => $this->handleSuccessfulPayment($event->data->object), 'payment_intent.payment_failed' => $this->handleFailedPayment($event->data->object), default => ['status' => 'ignored'], }; }
protected function handleSuccessfulPayment($paymentIntent): array { return ['status' => 'processed']; }
protected function handleFailedPayment($paymentIntent): array { return ['status' => 'failed']; } }
|
PayPal 集成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| <?php
namespace App\Services\Payment;
use Illuminate\Support\Facades\Http;
class PayPalService { protected string $baseUrl; protected string $clientId; protected string $clientSecret; protected ?string $accessToken = null;
public function __construct() { $this->clientId = config('services.paypal.client_id'); $this->clientSecret = config('services.paypal.secret'); $this->baseUrl = config('services.paypal.sandbox') ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; }
protected function getAccessToken(): string { if ($this->accessToken) { return $this->accessToken; }
$response = Http::withBasicAuth($this->clientId, $this->clientSecret) ->asForm() ->post("{$this->baseUrl}/v1/oauth2/token", [ 'grant_type' => 'client_credentials', ]);
$this->accessToken = $response->json('access_token');
return $this->accessToken; }
protected function client(): PendingRequest { return Http::withToken($this->getAccessToken()) ->baseUrl($this->baseUrl) ->withHeaders([ 'Content-Type' => 'application/json', ]); }
public function createOrder(float $amount, string $currency = 'USD'): array { return $this->client()->post('/v2/checkout/orders', [ 'intent' => 'CAPTURE', 'purchase_units' => [ [ 'amount' => [ 'currency_code' => $currency, 'value' => number_format($amount, 2, '.', ''), ], ], ], ])->json(); }
public function captureOrder(string $orderId): array { return $this->client() ->post("/v2/checkout/orders/{$orderId}/capture") ->json(); }
public function getOrder(string $orderId): array { return $this->client() ->get("/v2/checkout/orders/{$orderId}") ->json(); }
public function refund(string $captureId, float $amount, string $currency = 'USD'): array { return $this->client()->post("/v2/payments/captures/{$captureId}/refund", [ 'amount' => [ 'value' => number_format($amount, 2, '.', ''), 'currency_code' => $currency, ], ])->json(); } }
|
云存储集成
AWS S3 集成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| <?php
namespace App\Services\Storage;
use Aws\S3\S3Client; use Aws\Exception\AwsException;
class S3Service { protected S3Client $client; protected string $bucket;
public function __construct() { $this->client = new S3Client([ 'region' => config('filesystems.disks.s3.region'), 'version' => 'latest', 'credentials' => [ 'key' => config('filesystems.disks.s3.key'), 'secret' => config('filesystems.disks.s3.secret'), ], ]); $this->bucket = config('filesystems.disks.s3.bucket'); }
public function upload(string $key, $content, array $options = []): string { $this->client->putObject([ 'Bucket' => $this->bucket, 'Key' => $key, 'Body' => $content, 'ContentType' => $options['content_type'] ?? 'application/octet-stream', 'Metadata' => $options['metadata'] ?? [], ]);
return $key; }
public function download(string $key): string { $result = $this->client->getObject([ 'Bucket' => $this->bucket, 'Key' => $key, ]);
return $result['Body']->getContents(); }
public function delete(string $key): bool { try { $this->client->deleteObject([ 'Bucket' => $this->bucket, 'Key' => $key, ]); return true; } catch (AwsException $e) { return false; } }
public function getPresignedUrl(string $key, int $expires = 3600): string { $command = $this->client->getCommand('GetObject', [ 'Bucket' => $this->bucket, 'Key' => $key, ]);
return (string) $this->client->createPresignedRequest($command, "+{$expires} seconds")->getUri(); }
public function listObjects(string $prefix = ''): array { $result = $this->client->listObjectsV2([ 'Bucket' => $this->bucket, 'Prefix' => $prefix, ]);
return $result->get('Contents') ?? []; } }
|
消息推送集成
Firebase Cloud Messaging
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| <?php
namespace App\Services\Push;
use Illuminate\Support\Facades\Http;
class FirebaseService { protected string $projectId; protected string $accessToken;
public function __construct() { $this->projectId = config('services.firebase.project_id'); $this->accessToken = $this->getAccessToken(); }
protected function getAccessToken(): string { $credentials = json_decode( file_get_contents(config('services.firebase.credentials_file')), true );
$response = Http::asForm()->post('https://oauth2.googleapis.com/token', [ 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion' => $this->createJwt($credentials), ]);
return $response->json('access_token'); }
public function sendNotification(string $token, array $notification, array $data = []): array { return Http::withToken($this->accessToken) ->post("https://fcm.googleapis.com/v1/projects/{$this->projectId}/messages:send", [ 'message' => [ 'token' => $token, 'notification' => $notification, 'data' => $data, ], ]) ->json(); }
public function sendToTopic(string $topic, array $notification, array $data = []): array { return Http::withToken($this->accessToken) ->post("https://fcm.googleapis.com/v1/projects/{$this->projectId}/messages:send", [ 'message' => [ 'topic' => $topic, 'notification' => $notification, 'data' => $data, ], ]) ->json(); }
public function subscribeToTopic(string $token, string $topic): array { return Http::withToken($this->accessToken) ->post("https://iid.googleapis.com/iid/v1/{$token}/rel/topics/{$topic}") ->json(); } }
|
错误处理
API 异常类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?php
namespace App\Exceptions;
use Exception;
class ApiException extends Exception { protected array $context;
public function __construct(string $message, int $code = 0, array $context = []) { parent::__construct($message, $code); $this->context = $context; }
public function getContext(): array { return $this->context; }
public function report(): void { \Log::error('API Error', [ 'message' => $this->getMessage(), 'code' => $this->getCode(), 'context' => $this->context, ]); } }
|
错误处理中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
namespace App\Http\Middleware;
use Closure; use Illuminate\Http\Client\ConnectionException; use App\Exceptions\ApiException;
class HandleApiErrors { public function handle($request, Closure $next) { try { return $next($request); } catch (ConnectionException $e) { throw new ApiException('API 连接失败', 503, [ 'original_message' => $e->getMessage(), ]); } catch (\Illuminate\Http\Client\RequestException $e) { throw new ApiException( $e->response->json('message', 'API 请求失败'), $e->response->status(), [ 'response' => $e->response->json(), ] ); } } }
|
缓存与限流
API 响应缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
namespace App\Services\Api;
use Illuminate\Support\Facades\Cache;
trait CachesResponses { protected int $cacheTtl = 3600;
protected function cachedGet(string $uri, array $query = [], ?int $ttl = null): array { $cacheKey = $this->generateCacheKey($uri, $query);
return Cache::remember($cacheKey, $ttl ?? $this->cacheTtl, function () use ($uri, $query) { return $this->get($uri, $query); }); }
protected function generateCacheKey(string $uri, array $query = []): string { return 'api:' . md5($uri . serialize($query)); }
protected function clearCache(string $uri, array $query = []): void { Cache::forget($this->generateCacheKey($uri, $query)); } }
|
总结
Laravel 13 的第三方 API 集成提供了:
- 强大的 HTTP 客户端
- 灵活的服务封装模式
- OAuth 认证支持
- 支付网关集成
- 云存储集成
- 消息推送集成
- 完善的错误处理
合理封装第三方 API 可以提高代码的可维护性和可测试性。