| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- <?php
- namespace App\Module\OpenAPI\Services;
- use App\Module\OpenAPI\Models\OpenApiApp;
- use App\Module\OpenAPI\Enums\APP_STATUS;
- use App\Module\OpenAPI\Enums\AUTH_TYPE;
- use App\Module\OpenAPI\Enums\SCOPE_TYPE;
- use App\Module\OpenAPI\Events\AppCreatedEvent;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Support\Facades\Event;
- use Illuminate\Support\Facades\Hash;
- /**
- * OpenAPI核心服务
- */
- class OpenApiService
- {
- /**
- * 创建新应用
- *
- * @param array $data
- * @return OpenApiApp
- */
- public function createApp(array $data): OpenApiApp
- {
- // 验证必填字段
- $this->validateRequiredFields($data);
- // 生成应用ID和密钥
- $data['app_id'] = OpenApiApp::generateAppId();
- $data['app_secret'] = OpenApiApp::generateAppSecret();
- // 设置默认值
- $data['status'] = $data['status'] ?? config('openapi.app.default_status', APP_STATUS::PENDING->value);
- $data['auth_type'] = $data['auth_type'] ?? config('openapi.auth.default_type', AUTH_TYPE::API_KEY->value);
- $data['scopes'] = $data['scopes'] ?? config('openapi.scopes.default', []);
- // 设置过期时间
- if (!isset($data['expires_at'])) {
- $expireDays = config('openapi.app.expire_days', 365);
- $data['expires_at'] = now()->addDays($expireDays);
- }
- // 加密应用密钥
- if (config('openapi.security.encrypt_secrets', true)) {
- $data['app_secret'] = encrypt($data['app_secret']);
- }
- // 创建应用
- $app = OpenApiApp::create($data);
- // 触发应用创建事件
- Event::dispatch(new AppCreatedEvent($app));
- return $app;
- }
- /**
- * 验证必填字段
- *
- * @param array $data
- * @throws \InvalidArgumentException
- */
- protected function validateRequiredFields(array $data): void
- {
- $requiredFields = config('openapi.app.required_fields', ['name', 'description', 'callback_url']);
- foreach ($requiredFields as $field) {
- if (empty($data[$field])) {
- throw new \InvalidArgumentException("字段 {$field} 是必填的");
- }
- }
- }
- /**
- * 审核应用
- *
- * @param int $appId
- * @param bool $approved
- * @param string $note
- * @param int $approvedBy
- * @return OpenApiApp
- */
- public function approveApp(int $appId, bool $approved, string $note = '', int $approvedBy = null): OpenApiApp
- {
- $app = OpenApiApp::findOrFail($appId);
- if ($app->status !== APP_STATUS::PENDING->value) {
- throw new \InvalidArgumentException('只能审核待审核状态的应用');
- }
- $app->update([
- 'status' => $approved ? APP_STATUS::APPROVED->value : APP_STATUS::REJECTED->value,
- 'approved_at' => now(),
- 'approved_by' => $approvedBy,
- 'approved_note' => $note,
- ]);
- // 清除缓存
- $this->clearAppCache($app->app_id);
- return $app;
- }
- /**
- * 激活应用
- *
- * @param int $appId
- * @return OpenApiApp
- */
- public function activateApp(int $appId): OpenApiApp
- {
- $app = OpenApiApp::findOrFail($appId);
- if (!in_array($app->status, [APP_STATUS::APPROVED->value, APP_STATUS::SUSPENDED->value])) {
- throw new \InvalidArgumentException('只能激活已审核或已暂停的应用');
- }
- $app->update(['status' => APP_STATUS::ACTIVE->value]);
- // 清除缓存
- $this->clearAppCache($app->app_id);
- return $app;
- }
- /**
- * 暂停应用
- *
- * @param int $appId
- * @param string $reason
- * @return OpenApiApp
- */
- public function suspendApp(int $appId, string $reason = ''): OpenApiApp
- {
- $app = OpenApiApp::findOrFail($appId);
- if ($app->status !== APP_STATUS::ACTIVE->value) {
- throw new \InvalidArgumentException('只能暂停激活状态的应用');
- }
- $app->update([
- 'status' => APP_STATUS::SUSPENDED->value,
- 'approved_note' => $reason,
- ]);
- // 清除缓存
- $this->clearAppCache($app->app_id);
- return $app;
- }
- /**
- * 禁用应用
- *
- * @param int $appId
- * @param string $reason
- * @return OpenApiApp
- */
- public function disableApp(int $appId, string $reason = ''): OpenApiApp
- {
- $app = OpenApiApp::findOrFail($appId);
- $app->update([
- 'status' => APP_STATUS::DISABLED->value,
- 'approved_note' => $reason,
- ]);
- // 清除缓存
- $this->clearAppCache($app->app_id);
- return $app;
- }
- /**
- * 重新生成应用密钥
- *
- * @param int $appId
- * @return OpenApiApp
- */
- public function regenerateSecret(int $appId): OpenApiApp
- {
- $app = OpenApiApp::findOrFail($appId);
- if (!config('openapi.api_key.allow_regenerate', true)) {
- throw new \InvalidArgumentException('不允许重新生成密钥');
- }
- $newSecret = OpenApiApp::generateAppSecret();
- // 加密应用密钥
- if (config('openapi.security.encrypt_secrets', true)) {
- $newSecret = encrypt($newSecret);
- }
- $app->update(['app_secret' => $newSecret]);
- // 清除缓存
- $this->clearAppCache($app->app_id);
- return $app;
- }
- /**
- * 更新应用权限
- *
- * @param int $appId
- * @param array $scopes
- * @return OpenApiApp
- */
- public function updateScopes(int $appId, array $scopes): OpenApiApp
- {
- $app = OpenApiApp::findOrFail($appId);
- // 验证权限范围
- $this->validateScopes($scopes);
- $app->update(['scopes' => $scopes]);
- // 清除缓存
- $this->clearAppCache($app->app_id);
- return $app;
- }
- /**
- * 验证权限范围
- *
- * @param array $scopes
- * @throws \InvalidArgumentException
- */
- protected function validateScopes(array $scopes): void
- {
- $validScopes = array_column(SCOPE_TYPE::cases(), 'value');
- $validScopes[] = '*'; // 允许通配符
- foreach ($scopes as $scope) {
- if (!in_array($scope, $validScopes)) {
- throw new \InvalidArgumentException("无效的权限范围: {$scope}");
- }
- }
- }
- /**
- * 通过应用ID获取应用信息
- *
- * @param string $appId
- * @return OpenApiApp|null
- */
- public function getAppByAppId(string $appId): ?OpenApiApp
- {
- $cacheKey = config('openapi.cache.keys.app_info');
- $cacheKey = str_replace('{app_id}', $appId, $cacheKey);
- return Cache::remember($cacheKey, config('openapi.cache.default_ttl', 300), function () use ($appId) {
- return OpenApiApp::where('app_id', $appId)->first();
- });
- }
- /**
- * 验证应用认证
- *
- * @param string $appId
- * @param string $appSecret
- * @return OpenApiApp|null
- */
- public function validateApp(string $appId, string $appSecret): ?OpenApiApp
- {
- $app = $this->getAppByAppId($appId);
- if (!$app) {
- return null;
- }
- // 检查应用状态
- if (!$app->can_call_api) {
- return null;
- }
- // 检查应用是否过期
- if ($app->is_expired) {
- return null;
- }
- // 验证密钥
- $storedSecret = $app->app_secret;
- if (config('openapi.security.encrypt_secrets', true)) {
- $storedSecret = decrypt($storedSecret);
- }
- if (!hash_equals($storedSecret, $appSecret)) {
- return null;
- }
- return $app;
- }
- /**
- * 检查应用权限
- *
- * @param OpenApiApp $app
- * @param string $scope
- * @return bool
- */
- public function checkScope(OpenApiApp $app, string $scope): bool
- {
- return $app->hasScope($scope);
- }
- /**
- * 检查IP白名单
- *
- * @param OpenApiApp $app
- * @param string $ip
- * @return bool
- */
- public function checkIpWhitelist(OpenApiApp $app, string $ip): bool
- {
- return $app->isIpAllowed($ip);
- }
- /**
- * 更新应用最后使用时间
- *
- * @param OpenApiApp $app
- * @return void
- */
- public function updateLastUsed(OpenApiApp $app): void
- {
- $app->updateLastUsed();
- }
- /**
- * 获取应用统计信息
- *
- * @param string $appId
- * @param string $period
- * @return array
- */
- public function getAppStats(string $appId, string $period = 'day'): array
- {
- $startTime = $this->getStatsPeriodStart($period);
- // 获取基础统计
- $stats = \App\Module\OpenAPI\Models\OpenApiStats::where('app_id', $appId)
- ->where('created_at', '>=', $startTime)
- ->selectRaw('
- SUM(request_count) as total_requests,
- SUM(success_count) as total_success,
- SUM(error_count) as total_errors,
- AVG(avg_response_time) as avg_response_time,
- MAX(max_response_time) as max_response_time,
- MIN(min_response_time) as min_response_time,
- SUM(rate_limit_hits) as total_rate_limit_hits,
- SUM(unique_ips) as total_unique_ips
- ')
- ->first();
- $successRate = $stats->total_requests > 0
- ? round(($stats->total_success / $stats->total_requests) * 100, 2)
- : 0;
- $errorRate = $stats->total_requests > 0
- ? round(($stats->total_errors / $stats->total_requests) * 100, 2)
- : 0;
- return [
- 'period' => $period,
- 'start_time' => $startTime,
- 'total_requests' => $stats->total_requests ?? 0,
- 'total_success' => $stats->total_success ?? 0,
- 'total_errors' => $stats->total_errors ?? 0,
- 'success_rate' => $successRate,
- 'error_rate' => $errorRate,
- 'avg_response_time' => round($stats->avg_response_time ?? 0, 2),
- 'max_response_time' => $stats->max_response_time ?? 0,
- 'min_response_time' => $stats->min_response_time ?? 0,
- 'total_rate_limit_hits' => $stats->total_rate_limit_hits ?? 0,
- 'total_unique_ips' => $stats->total_unique_ips ?? 0,
- ];
- }
- /**
- * 清除应用缓存
- *
- * @param string $appId
- * @return void
- */
- protected function clearAppCache(string $appId): void
- {
- $cacheKey = config('openapi.cache.keys.app_info');
- $cacheKey = str_replace('{app_id}', $appId, $cacheKey);
- Cache::forget($cacheKey);
- }
- /**
- * 批量处理过期应用
- *
- * @return int
- */
- public function handleExpiredApps(): int
- {
- $expiredApps = OpenApiApp::where('expires_at', '<', now())
- ->where('status', '!=', APP_STATUS::EXPIRED->value)
- ->get();
- $count = 0;
- foreach ($expiredApps as $app) {
- $app->update(['status' => APP_STATUS::EXPIRED->value]);
- $this->clearAppCache($app->app_id);
- $count++;
- }
- return $count;
- }
- /**
- * 获取用户的应用列表
- *
- * @param int $userId
- * @return \Illuminate\Database\Eloquent\Collection
- */
- public function getUserApps(int $userId)
- {
- return OpenApiApp::byUser($userId)->orderBy('created_at', 'desc')->get();
- }
- /**
- * 检查用户是否可以创建更多应用
- *
- * @param int $userId
- * @return bool
- */
- public function canCreateMoreApps(int $userId): bool
- {
- $maxApps = config('openapi.app.max_apps_per_user', 10);
- $currentCount = OpenApiApp::byUser($userId)->count();
- return $currentCount < $maxApps;
- }
- /**
- * 更新应用信息
- *
- * @param int $appId
- * @param array $data
- * @return OpenApiApp
- */
- public function updateApp(int $appId, array $data): OpenApiApp
- {
- $app = OpenApiApp::findOrFail($appId);
- // 过滤允许更新的字段
- $allowedFields = ['name', 'description', 'website', 'logo', 'callback_url', 'contact_email'];
- $updateData = array_intersect_key($data, array_flip($allowedFields));
- $app->update($updateData);
- // 清除缓存
- $this->clearAppCache($app->app_id);
- return $app->fresh();
- }
- /**
- * 重新生成应用密钥
- *
- * @param int $appId
- * @return string
- */
- public function regenerateAppSecret(int $appId): string
- {
- $app = OpenApiApp::findOrFail($appId);
- $newSecret = OpenApiApp::generateAppSecret();
- $app->update(['app_secret' => $newSecret]);
- // 清除缓存
- $this->clearAppCache($app->app_id);
- return $newSecret;
- }
- /**
- * 获取应用配额信息
- *
- * @param string $appId
- * @return array
- */
- public function getAppQuota(string $appId): array
- {
- $app = OpenApiApp::where('app_id', $appId)->firstOrFail();
- // 获取今日使用量
- $todayUsage = \App\Module\OpenAPI\Models\OpenApiStats::where('app_id', $appId)
- ->where('date', now()->toDateString())
- ->sum('request_count');
- // 获取本月使用量
- $monthUsage = \App\Module\OpenAPI\Models\OpenApiStats::where('app_id', $appId)
- ->whereBetween('date', [
- now()->startOfMonth()->toDateString(),
- now()->endOfMonth()->toDateString()
- ])
- ->sum('request_count');
- // 获取配额限制
- $rateLimits = $app->rate_limits ?? [];
- $dailyLimit = $rateLimits['requests_per_day'] ?? 0;
- $monthlyLimit = $rateLimits['requests_per_month'] ?? 0;
- return [
- 'today' => [
- 'used' => $todayUsage,
- 'limit' => $dailyLimit,
- 'remaining' => max(0, $dailyLimit - $todayUsage),
- 'percentage' => $dailyLimit > 0 ? round(($todayUsage / $dailyLimit) * 100, 2) : 0,
- ],
- 'month' => [
- 'used' => $monthUsage,
- 'limit' => $monthlyLimit,
- 'remaining' => max(0, $monthlyLimit - $monthUsage),
- 'percentage' => $monthlyLimit > 0 ? round(($monthUsage / $monthlyLimit) * 100, 2) : 0,
- ],
- ];
- }
- /**
- * 获取统计周期的开始时间
- *
- * @param string $period
- * @return \Carbon\Carbon
- */
- protected function getStatsPeriodStart(string $period): \Carbon\Carbon
- {
- return match ($period) {
- 'hour' => now()->subHour(),
- 'day' => now()->subDay(),
- 'week' => now()->subWeek(),
- 'month' => now()->subMonth(),
- default => now()->subDay(),
- };
- }
- }
|