OpenApiApp.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. <?php
  2. namespace App\Module\OpenAPI\Models;
  3. use App\Module\OpenAPI\Enums\APP_STATUS;
  4. use App\Module\OpenAPI\Enums\AUTH_TYPE;
  5. use UCore\ModelCore;
  6. /**
  7. * 开放API应用模型
  8. *
  9. * @property int $id
  10. * @property string $app_id 应用ID
  11. * @property string $app_secret 应用密钥
  12. * @property string $name 应用名称
  13. * @property string $description 应用描述
  14. * @property string $website 应用网站
  15. * @property string $logo 应用Logo
  16. * @property string $callback_url 回调地址
  17. * @property string $contact_email 联系邮箱
  18. * @property string $status 应用状态
  19. * @property string $auth_type 认证类型
  20. * @property array $scopes 权限范围
  21. * @property array $rate_limits 限流配置
  22. * @property array $ip_whitelist IP白名单
  23. * @property int $user_id 创建用户ID
  24. * @property string $user_name 创建用户名称
  25. * @property \Carbon\Carbon $approved_at 审核时间
  26. * @property int $approved_by 审核人ID
  27. * @property string $approved_note 审核备注
  28. * @property \Carbon\Carbon $expires_at 过期时间
  29. * @property \Carbon\Carbon $last_used_at 最后使用时间
  30. * @property \Carbon\Carbon $created_at 创建时间
  31. * @property \Carbon\Carbon $updated_at 更新时间
  32. */
  33. class OpenApiApp extends ModelCore
  34. {
  35. /**
  36. * 数据表名
  37. *
  38. * @var string
  39. */
  40. protected $table = 'openapi_apps';
  41. /**
  42. * 可批量赋值的属性
  43. *
  44. * @var array
  45. */
  46. protected $fillable = [
  47. 'app_id',
  48. 'app_secret',
  49. 'name',
  50. 'description',
  51. 'website',
  52. 'logo',
  53. 'callback_url',
  54. 'contact_email',
  55. 'status',
  56. 'auth_type',
  57. 'scopes',
  58. 'rate_limits',
  59. 'ip_whitelist',
  60. 'user_id',
  61. 'user_name',
  62. 'approved_at',
  63. 'approved_by',
  64. 'approved_note',
  65. 'expires_at',
  66. 'last_used_at',
  67. ];
  68. /**
  69. * 属性类型转换
  70. *
  71. * @var array
  72. */
  73. protected $casts = [
  74. 'scopes' => 'array',
  75. 'rate_limits' => 'array',
  76. 'ip_whitelist' => 'array',
  77. 'user_id' => 'integer',
  78. 'approved_by' => 'integer',
  79. 'approved_at' => 'datetime',
  80. 'expires_at' => 'datetime',
  81. 'last_used_at' => 'datetime',
  82. 'created_at' => 'datetime',
  83. 'updated_at' => 'datetime',
  84. ];
  85. /**
  86. * 隐藏的属性
  87. *
  88. * @var array
  89. */
  90. protected $hidden = [
  91. 'app_secret',
  92. ];
  93. /**
  94. * 获取应用状态枚举
  95. *
  96. * @return APP_STATUS|null
  97. */
  98. public function getStatusEnumAttribute(): ?APP_STATUS
  99. {
  100. try {
  101. return APP_STATUS::from($this->status);
  102. } catch (\ValueError $e) {
  103. return null;
  104. }
  105. }
  106. /**
  107. * 获取认证类型枚举
  108. *
  109. * @return AUTH_TYPE|null
  110. */
  111. public function getAuthTypeEnumAttribute(): ?AUTH_TYPE
  112. {
  113. try {
  114. return AUTH_TYPE::from($this->auth_type);
  115. } catch (\ValueError $e) {
  116. return null;
  117. }
  118. }
  119. /**
  120. * 获取状态标签
  121. *
  122. * @return string
  123. */
  124. public function getStatusLabelAttribute(): string
  125. {
  126. $enum = $this->getStatusEnumAttribute();
  127. return $enum ? $enum->getLabel() : $this->status;
  128. }
  129. /**
  130. * 获取状态颜色
  131. *
  132. * @return string
  133. */
  134. public function getStatusColorAttribute(): string
  135. {
  136. $enum = $this->getStatusEnumAttribute();
  137. return $enum ? $enum->getColor() : 'secondary';
  138. }
  139. /**
  140. * 获取认证类型标签
  141. *
  142. * @return string
  143. */
  144. public function getAuthTypeLabelAttribute(): string
  145. {
  146. $enum = $this->getAuthTypeEnumAttribute();
  147. return $enum ? $enum->getLabel() : $this->auth_type;
  148. }
  149. /**
  150. * 判断应用是否可以调用API
  151. *
  152. * @return bool
  153. */
  154. public function getCanCallApiAttribute(): bool
  155. {
  156. $enum = $this->getStatusEnumAttribute();
  157. return $enum ? $enum->canCallApi() : false;
  158. }
  159. /**
  160. * 判断应用是否已过期
  161. *
  162. * @return bool
  163. */
  164. public function getIsExpiredAttribute(): bool
  165. {
  166. return $this->expires_at && $this->expires_at->isPast();
  167. }
  168. /**
  169. * 判断应用是否需要审核
  170. *
  171. * @return bool
  172. */
  173. public function getNeedsApprovalAttribute(): bool
  174. {
  175. $enum = $this->getStatusEnumAttribute();
  176. return $enum ? $enum->needsApproval() : false;
  177. }
  178. /**
  179. * 获取权限范围标签
  180. *
  181. * @return array
  182. */
  183. public function getScopeLabelsAttribute(): array
  184. {
  185. if (empty($this->scopes)) {
  186. return [];
  187. }
  188. $labels = [];
  189. foreach ($this->scopes as $scope) {
  190. try {
  191. $enum = \App\Module\OpenAPI\Enums\SCOPE_TYPE::from($scope);
  192. $labels[] = $enum->getLabel();
  193. } catch (\ValueError $e) {
  194. $labels[] = $scope;
  195. }
  196. }
  197. return $labels;
  198. }
  199. /**
  200. * 获取掩码后的应用密钥
  201. *
  202. * @return string
  203. */
  204. public function getMaskedSecretAttribute(): string
  205. {
  206. if (empty($this->app_secret)) {
  207. return '';
  208. }
  209. $length = strlen($this->app_secret);
  210. if ($length <= 8) {
  211. return str_repeat('*', $length);
  212. }
  213. return substr($this->app_secret, 0, 4) . str_repeat('*', $length - 8) . substr($this->app_secret, -4);
  214. }
  215. /**
  216. * 生成新的应用ID
  217. *
  218. * @return string
  219. */
  220. public static function generateAppId(): string
  221. {
  222. $prefix = config('openapi.api_key.prefix', 'ak_');
  223. $length = config('openapi.api_key.length', 32);
  224. do {
  225. $appId = $prefix . bin2hex(random_bytes($length / 2));
  226. } while (self::where('app_id', $appId)->exists());
  227. return $appId;
  228. }
  229. /**
  230. * 生成新的应用密钥
  231. *
  232. * @return string
  233. */
  234. public static function generateAppSecret(): string
  235. {
  236. $prefix = config('openapi.api_key.secret_prefix', 'sk_');
  237. $length = config('openapi.api_key.secret_length', 64);
  238. return $prefix . bin2hex(random_bytes($length / 2));
  239. }
  240. /**
  241. * 按状态查询
  242. *
  243. * @param \Illuminate\Database\Eloquent\Builder $query
  244. * @param string|APP_STATUS $status
  245. * @return \Illuminate\Database\Eloquent\Builder
  246. */
  247. public function scopeByStatus($query, $status)
  248. {
  249. if ($status instanceof APP_STATUS) {
  250. $status = $status->value;
  251. }
  252. return $query->where('status', $status);
  253. }
  254. /**
  255. * 按用户查询
  256. *
  257. * @param \Illuminate\Database\Eloquent\Builder $query
  258. * @param int $userId
  259. * @return \Illuminate\Database\Eloquent\Builder
  260. */
  261. public function scopeByUser($query, int $userId)
  262. {
  263. return $query->where('user_id', $userId);
  264. }
  265. /**
  266. * 查询活跃应用
  267. *
  268. * @param \Illuminate\Database\Eloquent\Builder $query
  269. * @return \Illuminate\Database\Eloquent\Builder
  270. */
  271. public function scopeActive($query)
  272. {
  273. return $query->where('status', APP_STATUS::ACTIVE->value)
  274. ->where(function ($q) {
  275. $q->whereNull('expires_at')
  276. ->orWhere('expires_at', '>', now());
  277. });
  278. }
  279. /**
  280. * 查询需要审核的应用
  281. *
  282. * @param \Illuminate\Database\Eloquent\Builder $query
  283. * @return \Illuminate\Database\Eloquent\Builder
  284. */
  285. public function scopePendingApproval($query)
  286. {
  287. return $query->where('status', APP_STATUS::PENDING->value);
  288. }
  289. /**
  290. * 查询即将过期的应用
  291. *
  292. * @param \Illuminate\Database\Eloquent\Builder $query
  293. * @param int $days
  294. * @return \Illuminate\Database\Eloquent\Builder
  295. */
  296. public function scopeExpiringSoon($query, int $days = 30)
  297. {
  298. return $query->whereNotNull('expires_at')
  299. ->whereBetween('expires_at', [now(), now()->addDays($days)]);
  300. }
  301. /**
  302. * 更新最后使用时间
  303. *
  304. * @return void
  305. */
  306. public function updateLastUsed(): void
  307. {
  308. $this->update(['last_used_at' => now()]);
  309. }
  310. /**
  311. * 检查是否有指定权限
  312. *
  313. * @param string $scope
  314. * @return bool
  315. */
  316. public function hasScope(string $scope): bool
  317. {
  318. if (empty($this->scopes)) {
  319. return false;
  320. }
  321. return in_array($scope, $this->scopes) || in_array('*', $this->scopes);
  322. }
  323. /**
  324. * 检查应用是否激活
  325. *
  326. * @return bool
  327. */
  328. public function isActive(): bool
  329. {
  330. return $this->status === APP_STATUS::ACTIVE->value;
  331. }
  332. /**
  333. * 检查应用是否被暂停
  334. *
  335. * @return bool
  336. */
  337. public function isSuspended(): bool
  338. {
  339. return $this->status === APP_STATUS::SUSPENDED->value;
  340. }
  341. /**
  342. * 检查应用是否过期
  343. *
  344. * @return bool
  345. */
  346. public function isExpired(): bool
  347. {
  348. return $this->expires_at && $this->expires_at->isPast();
  349. }
  350. /**
  351. * 检查IP是否在白名单中
  352. *
  353. * @param string $ip
  354. * @return bool
  355. */
  356. public function isIpAllowed(string $ip): bool
  357. {
  358. if (empty($this->ip_whitelist)) {
  359. return true; // 没有设置白名单,允许所有IP
  360. }
  361. foreach ($this->ip_whitelist as $allowedIp) {
  362. if ($this->matchIp($ip, $allowedIp)) {
  363. return true;
  364. }
  365. }
  366. return false;
  367. }
  368. /**
  369. * 匹配IP地址
  370. *
  371. * @param string $ip
  372. * @param string $pattern
  373. * @return bool
  374. */
  375. protected function matchIp(string $ip, string $pattern): bool
  376. {
  377. // 精确匹配
  378. if ($ip === $pattern) {
  379. return true;
  380. }
  381. // CIDR匹配
  382. if (strpos($pattern, '/') !== false) {
  383. list($subnet, $mask) = explode('/', $pattern);
  384. return (ip2long($ip) & ~((1 << (32 - $mask)) - 1)) === ip2long($subnet);
  385. }
  386. // 通配符匹配
  387. if (strpos($pattern, '*') !== false) {
  388. $pattern = str_replace('*', '.*', $pattern);
  389. return preg_match('/^' . $pattern . '$/', $ip);
  390. }
  391. return false;
  392. }
  393. }