OpenApiService.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. <?php
  2. namespace App\Module\OpenAPI\Services;
  3. use App\Module\OpenAPI\Models\OpenApiApp;
  4. use App\Module\OpenAPI\Enums\APP_STATUS;
  5. use App\Module\OpenAPI\Enums\AUTH_TYPE;
  6. use App\Module\OpenAPI\Enums\SCOPE_TYPE;
  7. use App\Module\OpenAPI\Events\AppCreatedEvent;
  8. use Illuminate\Support\Facades\Cache;
  9. use Illuminate\Support\Facades\Event;
  10. use Illuminate\Support\Facades\Hash;
  11. /**
  12. * OpenAPI核心服务
  13. */
  14. class OpenApiService
  15. {
  16. /**
  17. * 创建新应用
  18. *
  19. * @param array $data
  20. * @return OpenApiApp
  21. */
  22. public function createApp(array $data): OpenApiApp
  23. {
  24. // 验证必填字段
  25. $this->validateRequiredFields($data);
  26. // 生成应用ID和密钥
  27. $data['app_id'] = OpenApiApp::generateAppId();
  28. $data['app_secret'] = OpenApiApp::generateAppSecret();
  29. // 设置默认值
  30. $data['status'] = $data['status'] ?? config('openapi.app.default_status', APP_STATUS::PENDING->value);
  31. $data['auth_type'] = $data['auth_type'] ?? config('openapi.auth.default_type', AUTH_TYPE::API_KEY->value);
  32. $data['scopes'] = $data['scopes'] ?? config('openapi.scopes.default', []);
  33. // 设置过期时间
  34. if (!isset($data['expires_at'])) {
  35. $expireDays = config('openapi.app.expire_days', 365);
  36. $data['expires_at'] = now()->addDays($expireDays);
  37. }
  38. // 加密应用密钥
  39. if (config('openapi.security.encrypt_secrets', true)) {
  40. $data['app_secret'] = encrypt($data['app_secret']);
  41. }
  42. // 创建应用
  43. $app = OpenApiApp::create($data);
  44. // 触发应用创建事件
  45. Event::dispatch(new AppCreatedEvent($app));
  46. return $app;
  47. }
  48. /**
  49. * 验证必填字段
  50. *
  51. * @param array $data
  52. * @throws \InvalidArgumentException
  53. */
  54. protected function validateRequiredFields(array $data): void
  55. {
  56. $requiredFields = config('openapi.app.required_fields', ['name', 'description', 'callback_url']);
  57. foreach ($requiredFields as $field) {
  58. if (empty($data[$field])) {
  59. throw new \InvalidArgumentException("字段 {$field} 是必填的");
  60. }
  61. }
  62. }
  63. /**
  64. * 审核应用
  65. *
  66. * @param int $appId
  67. * @param bool $approved
  68. * @param string $note
  69. * @param int $approvedBy
  70. * @return OpenApiApp
  71. */
  72. public function approveApp(int $appId, bool $approved, string $note = '', int $approvedBy = null): OpenApiApp
  73. {
  74. $app = OpenApiApp::findOrFail($appId);
  75. if ($app->status !== APP_STATUS::PENDING->value) {
  76. throw new \InvalidArgumentException('只能审核待审核状态的应用');
  77. }
  78. $app->update([
  79. 'status' => $approved ? APP_STATUS::APPROVED->value : APP_STATUS::REJECTED->value,
  80. 'approved_at' => now(),
  81. 'approved_by' => $approvedBy,
  82. 'approved_note' => $note,
  83. ]);
  84. // 清除缓存
  85. $this->clearAppCache($app->app_id);
  86. return $app;
  87. }
  88. /**
  89. * 激活应用
  90. *
  91. * @param int $appId
  92. * @return OpenApiApp
  93. */
  94. public function activateApp(int $appId): OpenApiApp
  95. {
  96. $app = OpenApiApp::findOrFail($appId);
  97. if (!in_array($app->status, [APP_STATUS::APPROVED->value, APP_STATUS::SUSPENDED->value])) {
  98. throw new \InvalidArgumentException('只能激活已审核或已暂停的应用');
  99. }
  100. $app->update(['status' => APP_STATUS::ACTIVE->value]);
  101. // 清除缓存
  102. $this->clearAppCache($app->app_id);
  103. return $app;
  104. }
  105. /**
  106. * 暂停应用
  107. *
  108. * @param int $appId
  109. * @param string $reason
  110. * @return OpenApiApp
  111. */
  112. public function suspendApp(int $appId, string $reason = ''): OpenApiApp
  113. {
  114. $app = OpenApiApp::findOrFail($appId);
  115. if ($app->status !== APP_STATUS::ACTIVE->value) {
  116. throw new \InvalidArgumentException('只能暂停激活状态的应用');
  117. }
  118. $app->update([
  119. 'status' => APP_STATUS::SUSPENDED->value,
  120. 'approved_note' => $reason,
  121. ]);
  122. // 清除缓存
  123. $this->clearAppCache($app->app_id);
  124. return $app;
  125. }
  126. /**
  127. * 禁用应用
  128. *
  129. * @param int $appId
  130. * @param string $reason
  131. * @return OpenApiApp
  132. */
  133. public function disableApp(int $appId, string $reason = ''): OpenApiApp
  134. {
  135. $app = OpenApiApp::findOrFail($appId);
  136. $app->update([
  137. 'status' => APP_STATUS::DISABLED->value,
  138. 'approved_note' => $reason,
  139. ]);
  140. // 清除缓存
  141. $this->clearAppCache($app->app_id);
  142. return $app;
  143. }
  144. /**
  145. * 重新生成应用密钥
  146. *
  147. * @param int $appId
  148. * @return OpenApiApp
  149. */
  150. public function regenerateSecret(int $appId): OpenApiApp
  151. {
  152. $app = OpenApiApp::findOrFail($appId);
  153. if (!config('openapi.api_key.allow_regenerate', true)) {
  154. throw new \InvalidArgumentException('不允许重新生成密钥');
  155. }
  156. $newSecret = OpenApiApp::generateAppSecret();
  157. // 加密应用密钥
  158. if (config('openapi.security.encrypt_secrets', true)) {
  159. $newSecret = encrypt($newSecret);
  160. }
  161. $app->update(['app_secret' => $newSecret]);
  162. // 清除缓存
  163. $this->clearAppCache($app->app_id);
  164. return $app;
  165. }
  166. /**
  167. * 更新应用权限
  168. *
  169. * @param int $appId
  170. * @param array $scopes
  171. * @return OpenApiApp
  172. */
  173. public function updateScopes(int $appId, array $scopes): OpenApiApp
  174. {
  175. $app = OpenApiApp::findOrFail($appId);
  176. // 验证权限范围
  177. $this->validateScopes($scopes);
  178. $app->update(['scopes' => $scopes]);
  179. // 清除缓存
  180. $this->clearAppCache($app->app_id);
  181. return $app;
  182. }
  183. /**
  184. * 验证权限范围
  185. *
  186. * @param array $scopes
  187. * @throws \InvalidArgumentException
  188. */
  189. protected function validateScopes(array $scopes): void
  190. {
  191. $validScopes = array_column(SCOPE_TYPE::cases(), 'value');
  192. $validScopes[] = '*'; // 允许通配符
  193. foreach ($scopes as $scope) {
  194. if (!in_array($scope, $validScopes)) {
  195. throw new \InvalidArgumentException("无效的权限范围: {$scope}");
  196. }
  197. }
  198. }
  199. /**
  200. * 通过应用ID获取应用信息
  201. *
  202. * @param string $appId
  203. * @return OpenApiApp|null
  204. */
  205. public function getAppByAppId(string $appId): ?OpenApiApp
  206. {
  207. $cacheKey = config('openapi.cache.keys.app_info');
  208. $cacheKey = str_replace('{app_id}', $appId, $cacheKey);
  209. return Cache::remember($cacheKey, config('openapi.cache.default_ttl', 300), function () use ($appId) {
  210. return OpenApiApp::where('app_id', $appId)->first();
  211. });
  212. }
  213. /**
  214. * 验证应用认证
  215. *
  216. * @param string $appId
  217. * @param string $appSecret
  218. * @return OpenApiApp|null
  219. */
  220. public function validateApp(string $appId, string $appSecret): ?OpenApiApp
  221. {
  222. $app = $this->getAppByAppId($appId);
  223. if (!$app) {
  224. return null;
  225. }
  226. // 检查应用状态
  227. if (!$app->can_call_api) {
  228. return null;
  229. }
  230. // 检查应用是否过期
  231. if ($app->is_expired) {
  232. return null;
  233. }
  234. // 验证密钥
  235. $storedSecret = $app->app_secret;
  236. if (config('openapi.security.encrypt_secrets', true)) {
  237. $storedSecret = decrypt($storedSecret);
  238. }
  239. if (!hash_equals($storedSecret, $appSecret)) {
  240. return null;
  241. }
  242. return $app;
  243. }
  244. /**
  245. * 检查应用权限
  246. *
  247. * @param OpenApiApp $app
  248. * @param string $scope
  249. * @return bool
  250. */
  251. public function checkScope(OpenApiApp $app, string $scope): bool
  252. {
  253. return $app->hasScope($scope);
  254. }
  255. /**
  256. * 检查IP白名单
  257. *
  258. * @param OpenApiApp $app
  259. * @param string $ip
  260. * @return bool
  261. */
  262. public function checkIpWhitelist(OpenApiApp $app, string $ip): bool
  263. {
  264. return $app->isIpAllowed($ip);
  265. }
  266. /**
  267. * 更新应用最后使用时间
  268. *
  269. * @param OpenApiApp $app
  270. * @return void
  271. */
  272. public function updateLastUsed(OpenApiApp $app): void
  273. {
  274. $app->updateLastUsed();
  275. }
  276. /**
  277. * 获取应用统计信息
  278. *
  279. * @param int $appId
  280. * @param int $days
  281. * @return array
  282. */
  283. public function getAppStats(int $appId, int $days = 30): array
  284. {
  285. // 这里可以实现统计逻辑
  286. // 暂时返回模拟数据
  287. return [
  288. 'total_requests' => 0,
  289. 'successful_requests' => 0,
  290. 'failed_requests' => 0,
  291. 'average_response_time' => 0,
  292. 'last_request_at' => null,
  293. ];
  294. }
  295. /**
  296. * 清除应用缓存
  297. *
  298. * @param string $appId
  299. * @return void
  300. */
  301. protected function clearAppCache(string $appId): void
  302. {
  303. $cacheKey = config('openapi.cache.keys.app_info');
  304. $cacheKey = str_replace('{app_id}', $appId, $cacheKey);
  305. Cache::forget($cacheKey);
  306. }
  307. /**
  308. * 批量处理过期应用
  309. *
  310. * @return int
  311. */
  312. public function handleExpiredApps(): int
  313. {
  314. $expiredApps = OpenApiApp::where('expires_at', '<', now())
  315. ->where('status', '!=', APP_STATUS::EXPIRED->value)
  316. ->get();
  317. $count = 0;
  318. foreach ($expiredApps as $app) {
  319. $app->update(['status' => APP_STATUS::EXPIRED->value]);
  320. $this->clearAppCache($app->app_id);
  321. $count++;
  322. }
  323. return $count;
  324. }
  325. /**
  326. * 获取用户的应用列表
  327. *
  328. * @param int $userId
  329. * @return \Illuminate\Database\Eloquent\Collection
  330. */
  331. public function getUserApps(int $userId)
  332. {
  333. return OpenApiApp::byUser($userId)->orderBy('created_at', 'desc')->get();
  334. }
  335. /**
  336. * 检查用户是否可以创建更多应用
  337. *
  338. * @param int $userId
  339. * @return bool
  340. */
  341. public function canCreateMoreApps(int $userId): bool
  342. {
  343. $maxApps = config('openapi.app.max_apps_per_user', 10);
  344. $currentCount = OpenApiApp::byUser($userId)->count();
  345. return $currentCount < $maxApps;
  346. }
  347. }