| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- <?php
- namespace App\Module\ThirdParty\Services;
- use App\Module\ThirdParty\Models\ThirdPartyService as ServiceModel;
- use App\Module\ThirdParty\Models\ThirdPartyCredential;
- use App\Module\ThirdParty\Models\ThirdPartyLog;
- use App\Module\ThirdParty\Models\ThirdPartyQuota;
- use App\Module\ThirdParty\Enums\SERVICE_STATUS;
- use App\Module\ThirdParty\Enums\LOG_LEVEL;
- use Illuminate\Support\Facades\Http;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Http\Client\Response;
- /**
- * 第三方服务核心服务类
- */
- class ThirdPartyService
- {
- /**
- * 注册新的第三方服务
- *
- * @param array $data
- * @return ServiceModel
- */
- public static function registerService(array $data): ServiceModel
- {
- // 生成服务代码
- if (empty($data['code'])) {
- $data['code'] = ServiceModel::generateCode($data['name'], $data['provider']);
- }
-
- // 设置默认值
- $data = array_merge([
- 'version' => 'v1',
- 'status' => config('thirdparty.defaults.service_status', 'INACTIVE'),
- 'auth_type' => config('thirdparty.defaults.auth_type', 'API_KEY'),
- 'timeout' => config('thirdparty.defaults.timeout', 30),
- 'retry_times' => config('thirdparty.defaults.retry_times', 3),
- 'retry_delay' => config('thirdparty.defaults.retry_delay', 1000),
- 'health_check_interval' => config('thirdparty.defaults.health_check_interval', 300),
- 'priority' => 0,
- ], $data);
-
- return ServiceModel::create($data);
- }
- /**
- * 调用第三方API
- *
- * @param string $serviceCode 服务代码
- * @param string $endpoint API端点
- * @param array $data 请求数据
- * @param string $method HTTP方法
- * @param array $options 额外选项
- * @return array
- * @throws \Exception
- */
- public static function callApi(
- string $serviceCode,
- string $endpoint,
- array $data = [],
- string $method = 'POST',
- array $options = []
- ): array {
- $service = static::getServiceByCode($serviceCode);
-
- if (!$service) {
- throw new \Exception("服务 {$serviceCode} 不存在");
- }
-
- if (!$service->canCallApi()) {
- throw new \Exception("服务 {$serviceCode} 当前不可用,状态:{$service->getStatusLabel()}");
- }
-
- // 检查配额
- if (!static::checkQuota($service)) {
- throw new \Exception("服务 {$serviceCode} 配额已用完");
- }
-
- // 获取凭证
- $credential = $service->getActiveCredential($options['environment'] ?? 'production');
- if (!$credential) {
- throw new \Exception("服务 {$serviceCode} 没有可用的认证凭证");
- }
-
- $requestId = ThirdPartyLog::generateRequestId();
- $startTime = microtime(true);
-
- try {
- // 构建请求
- $url = $service->getApiUrl($endpoint);
- $headers = array_merge($service->getDefaultHeaders(), $credential->generateAuthHeaders());
- $params = array_merge($service->getDefaultParams(), $data);
-
- // 发送请求
- $response = static::sendRequest($service, $url, $method, $params, $headers, $options);
-
- $responseTime = (int)((microtime(true) - $startTime) * 1000);
-
- // 记录日志
- static::logApiCall($service, $credential, $requestId, $method, $url, $headers, $params, $response, $responseTime);
-
- // 更新配额
- static::updateQuota($service);
-
- // 更新凭证使用统计
- $credential->updateUsageStats();
-
- return [
- 'success' => true,
- 'data' => $response->json(),
- 'status_code' => $response->status(),
- 'response_time' => $responseTime,
- 'request_id' => $requestId,
- ];
-
- } catch (\Exception $e) {
- $responseTime = (int)((microtime(true) - $startTime) * 1000);
-
- // 记录错误日志
- static::logApiError($service, $credential, $requestId, $method, $url, $headers, $params, $e, $responseTime);
-
- throw $e;
- }
- }
- /**
- * 获取服务列表
- *
- * @param array $filters
- * @return \Illuminate\Database\Eloquent\Collection
- */
- public static function getServices(array $filters = [])
- {
- $query = ServiceModel::query();
-
- if (isset($filters['type'])) {
- $query->where('type', $filters['type']);
- }
-
- if (isset($filters['status'])) {
- $query->where('status', $filters['status']);
- }
-
- if (isset($filters['provider'])) {
- $query->where('provider', $filters['provider']);
- }
-
- return $query->orderBy('priority')->orderBy('name')->get();
- }
- /**
- * 根据代码获取服务
- *
- * @param string $code
- * @return ServiceModel|null
- */
- public static function getServiceByCode(string $code): ?ServiceModel
- {
- $cacheKey = "thirdparty:service:{$code}";
-
- return Cache::remember($cacheKey, config('thirdparty.cache.ttl', 3600), function () use ($code) {
- return ServiceModel::where('code', $code)->first();
- });
- }
- /**
- * 更新服务状态
- *
- * @param string $serviceCode
- * @param string $status
- * @return bool
- */
- public static function updateServiceStatus(string $serviceCode, string $status): bool
- {
- $service = static::getServiceByCode($serviceCode);
-
- if (!$service) {
- return false;
- }
-
- $currentStatus = $service->getServiceStatusEnum();
- $newStatus = SERVICE_STATUS::from($status);
-
- // 检查状态转换是否允许
- if (!$currentStatus->canTransitionTo($newStatus)) {
- throw new \Exception("不能从状态 {$currentStatus->getLabel()} 转换到 {$newStatus->getLabel()}");
- }
-
- $result = $service->update(['status' => $status]);
-
- // 清除缓存
- Cache::forget("thirdparty:service:{$serviceCode}");
-
- return $result;
- }
- /**
- * 检查服务配额
- *
- * @param ServiceModel $service
- * @param int $amount
- * @return bool
- */
- protected static function checkQuota(ServiceModel $service, int $amount = 1): bool
- {
- if (!config('thirdparty.quota.enabled', true)) {
- return true;
- }
-
- $quotas = $service->quotas()->active()->get();
-
- foreach ($quotas as $quota) {
- if (!$quota->canUse($amount)) {
- return false;
- }
- }
-
- return true;
- }
- /**
- * 更新服务配额
- *
- * @param ServiceModel $service
- * @param int $amount
- * @return void
- */
- protected static function updateQuota(ServiceModel $service, int $amount = 1): void
- {
- if (!config('thirdparty.quota.enabled', true)) {
- return;
- }
-
- $quotas = $service->quotas()->active()->get();
-
- foreach ($quotas as $quota) {
- $quota->incrementUsage($amount);
- }
- }
- /**
- * 发送HTTP请求
- *
- * @param ServiceModel $service
- * @param string $url
- * @param string $method
- * @param array $data
- * @param array $headers
- * @param array $options
- * @return Response
- */
- protected static function sendRequest(
- ServiceModel $service,
- string $url,
- string $method,
- array $data,
- array $headers,
- array $options
- ): Response {
- $httpOptions = [
- 'timeout' => $options['timeout'] ?? $service->timeout,
- 'headers' => $headers,
- ];
-
- // 配置重试
- $retryTimes = $options['retry_times'] ?? $service->retry_times;
- $retryDelay = $options['retry_delay'] ?? $service->retry_delay;
-
- $http = Http::withOptions($httpOptions);
-
- if ($retryTimes > 0) {
- $http = $http->retry($retryTimes, $retryDelay, function ($exception, $request) {
- // 只在特定错误时重试
- if ($exception instanceof \Illuminate\Http\Client\ConnectionException) {
- return true;
- }
-
- if ($exception instanceof \Illuminate\Http\Client\RequestException) {
- $status = $exception->response->status();
- return in_array($status, config('thirdparty.retry.retry_on_status', [429, 500, 502, 503, 504]));
- }
-
- return false;
- });
- }
-
- // 发送请求
- return match (strtoupper($method)) {
- 'GET' => $http->get($url, $data),
- 'POST' => $http->post($url, $data),
- 'PUT' => $http->put($url, $data),
- 'PATCH' => $http->patch($url, $data),
- 'DELETE' => $http->delete($url, $data),
- default => throw new \Exception("不支持的HTTP方法: {$method}"),
- };
- }
- /**
- * 记录API调用日志
- *
- * @param ServiceModel $service
- * @param ThirdPartyCredential $credential
- * @param string $requestId
- * @param string $method
- * @param string $url
- * @param array $headers
- * @param array $params
- * @param Response $response
- * @param int $responseTime
- * @return void
- */
- protected static function logApiCall(
- ServiceModel $service,
- ThirdPartyCredential $credential,
- string $requestId,
- string $method,
- string $url,
- array $headers,
- array $params,
- Response $response,
- int $responseTime
- ): void {
- if (!config('thirdparty.logging.enabled', true)) {
- return;
- }
-
- // 过滤敏感信息
- $filteredHeaders = static::filterSensitiveData($headers);
- $filteredParams = static::filterSensitiveData($params);
-
- ThirdPartyLog::createLog([
- 'service_id' => $service->id,
- 'credential_id' => $credential->id,
- 'request_id' => $requestId,
- 'method' => $method,
- 'url' => $url,
- 'headers' => $filteredHeaders,
- 'params' => $filteredParams,
- 'body' => json_encode($filteredParams),
- 'response_status' => $response->status(),
- 'response_headers' => $response->headers(),
- 'response_body' => static::truncateResponseBody($response->body()),
- 'response_time' => $responseTime,
- 'level' => $response->successful() ? LOG_LEVEL::INFO->value : LOG_LEVEL::ERROR->value,
- 'user_id' => auth()->id(),
- 'ip_address' => request()->ip(),
- 'user_agent' => request()->userAgent(),
- ]);
- }
- /**
- * 记录API错误日志
- *
- * @param ServiceModel $service
- * @param ThirdPartyCredential $credential
- * @param string $requestId
- * @param string $method
- * @param string $url
- * @param array $headers
- * @param array $params
- * @param \Exception $exception
- * @param int $responseTime
- * @return void
- */
- protected static function logApiError(
- ServiceModel $service,
- ThirdPartyCredential $credential,
- string $requestId,
- string $method,
- string $url,
- array $headers,
- array $params,
- \Exception $exception,
- int $responseTime
- ): void {
- if (!config('thirdparty.logging.enabled', true)) {
- return;
- }
-
- $filteredHeaders = static::filterSensitiveData($headers);
- $filteredParams = static::filterSensitiveData($params);
-
- ThirdPartyLog::createLog([
- 'service_id' => $service->id,
- 'credential_id' => $credential->id,
- 'request_id' => $requestId,
- 'method' => $method,
- 'url' => $url,
- 'headers' => $filteredHeaders,
- 'params' => $filteredParams,
- 'body' => json_encode($filteredParams),
- 'response_time' => $responseTime,
- 'error_message' => $exception->getMessage(),
- 'level' => LOG_LEVEL::ERROR->value,
- 'user_id' => auth()->id(),
- 'ip_address' => request()->ip(),
- 'user_agent' => request()->userAgent(),
- ]);
- }
- /**
- * 过滤敏感数据
- *
- * @param array $data
- * @return array
- */
- protected static function filterSensitiveData(array $data): array
- {
- $sensitiveFields = config('thirdparty.logging.sensitive_fields', []);
-
- foreach ($sensitiveFields as $field) {
- if (isset($data[$field])) {
- $data[$field] = '***';
- }
- }
-
- return $data;
- }
- /**
- * 截断响应体
- *
- * @param string $body
- * @return string
- */
- protected static function truncateResponseBody(string $body): string
- {
- $maxSize = config('thirdparty.logging.max_body_size', 10240);
-
- if (strlen($body) > $maxSize) {
- return substr($body, 0, $maxSize) . '... [truncated]';
- }
-
- return $body;
- }
- }
|