OpenApiWebhook.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <?php
  2. namespace App\Module\OpenAPI\Models;
  3. use UCore\ModelCore;
  4. /**
  5. * OpenAPI Webhook配置模型
  6. *
  7. * field start
  8. * @property int $id 主键ID
  9. * @property string $app_id 应用ID
  10. * @property string $name Webhook名称
  11. * @property string $url 回调URL
  12. * @property array $events 监听的事件类型
  13. * @property string $secret 签名密钥
  14. * @property string $status 状态
  15. * @property int $timeout 超时时间(秒)
  16. * @property int $retry_count 重试次数
  17. * @property int $current_retry_count 当前重试次数
  18. * @property int $total_deliveries 总投递次数
  19. * @property int $successful_deliveries 成功投递次数
  20. * @property int $failed_deliveries 失败投递次数
  21. * @property \Carbon\Carbon $last_success_at 最后成功时间
  22. * @property \Carbon\Carbon $last_failure_at 最后失败时间
  23. * @property \Carbon\Carbon $created_at 创建时间
  24. * @property \Carbon\Carbon $updated_at 更新时间
  25. * field end
  26. */
  27. class OpenApiWebhook extends ModelCore
  28. {
  29. /**
  30. * 数据表名
  31. *
  32. * @var string
  33. */
  34. protected $table = 'openapi_webhooks';
  35. /**
  36. * 可批量赋值的属性
  37. *
  38. * @var array
  39. */
  40. // attrlist start
  41. protected $fillable = [
  42. 'id',
  43. 'app_id',
  44. 'name',
  45. 'url',
  46. 'events',
  47. 'secret',
  48. 'status',
  49. 'timeout',
  50. 'retry_count',
  51. 'current_retry_count',
  52. 'total_deliveries',
  53. 'successful_deliveries',
  54. 'failed_deliveries',
  55. 'last_success_at',
  56. 'last_failure_at',
  57. ];
  58. // attrlist end
  59. /**
  60. * 数据类型转换
  61. *
  62. * @var array
  63. */
  64. protected $casts = [
  65. 'events' => 'array',
  66. 'timeout' => 'integer',
  67. 'retry_count' => 'integer',
  68. 'current_retry_count' => 'integer',
  69. 'total_deliveries' => 'integer',
  70. 'successful_deliveries' => 'integer',
  71. 'failed_deliveries' => 'integer',
  72. 'last_success_at' => 'datetime',
  73. 'last_failure_at' => 'datetime',
  74. ];
  75. /**
  76. * 默认值
  77. *
  78. * @var array
  79. */
  80. protected $attributes = [
  81. 'status' => 'ACTIVE',
  82. 'timeout' => 30,
  83. 'retry_count' => 3,
  84. 'current_retry_count' => 0,
  85. 'total_deliveries' => 0,
  86. 'successful_deliveries' => 0,
  87. 'failed_deliveries' => 0,
  88. ];
  89. /**
  90. * 关联应用模型
  91. *
  92. * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
  93. */
  94. public function app()
  95. {
  96. return $this->belongsTo(OpenApiApp::class, 'app_id', 'app_id');
  97. }
  98. /**
  99. * 获取状态标签
  100. *
  101. * @return string
  102. */
  103. public function getStatusLabelAttribute(): string
  104. {
  105. return match ($this->status) {
  106. 'ACTIVE' => '激活',
  107. 'INACTIVE' => '停用',
  108. 'FAILED' => '失败',
  109. default => $this->status,
  110. };
  111. }
  112. /**
  113. * 获取状态颜色
  114. *
  115. * @return string
  116. */
  117. public function getStatusColorAttribute(): string
  118. {
  119. return match ($this->status) {
  120. 'ACTIVE' => 'success',
  121. 'INACTIVE' => 'warning',
  122. 'FAILED' => 'danger',
  123. default => 'secondary',
  124. };
  125. }
  126. /**
  127. * 获取成功率
  128. *
  129. * @return float
  130. */
  131. public function getSuccessRateAttribute(): float
  132. {
  133. if ($this->total_deliveries === 0) {
  134. return 0;
  135. }
  136. return round(($this->successful_deliveries / $this->total_deliveries) * 100, 2);
  137. }
  138. /**
  139. * 获取失败率
  140. *
  141. * @return float
  142. */
  143. public function getFailureRateAttribute(): float
  144. {
  145. if ($this->total_deliveries === 0) {
  146. return 0;
  147. }
  148. return round(($this->failed_deliveries / $this->total_deliveries) * 100, 2);
  149. }
  150. /**
  151. * 获取事件列表的字符串表示
  152. *
  153. * @return string
  154. */
  155. public function getEventsStringAttribute(): string
  156. {
  157. if (empty($this->events)) {
  158. return '无';
  159. }
  160. if (in_array('*', $this->events)) {
  161. return '全部事件';
  162. }
  163. return implode(', ', $this->events);
  164. }
  165. /**
  166. * 检查是否监听指定事件
  167. *
  168. * @param string $event
  169. * @return bool
  170. */
  171. public function listensToEvent(string $event): bool
  172. {
  173. if (empty($this->events)) {
  174. return false;
  175. }
  176. return in_array('*', $this->events) || in_array($event, $this->events);
  177. }
  178. /**
  179. * 重置重试计数
  180. *
  181. * @return void
  182. */
  183. public function resetRetryCount(): void
  184. {
  185. $this->update(['current_retry_count' => 0]);
  186. }
  187. /**
  188. * 检查是否可以重试
  189. *
  190. * @return bool
  191. */
  192. public function canRetry(): bool
  193. {
  194. return $this->current_retry_count < $this->retry_count;
  195. }
  196. /**
  197. * 按应用ID查询
  198. *
  199. * @param \Illuminate\Database\Eloquent\Builder $query
  200. * @param string $appId
  201. * @return \Illuminate\Database\Eloquent\Builder
  202. */
  203. public function scopeByApp($query, string $appId)
  204. {
  205. return $query->where('app_id', $appId);
  206. }
  207. /**
  208. * 查询激活状态的Webhook
  209. *
  210. * @param \Illuminate\Database\Eloquent\Builder $query
  211. * @return \Illuminate\Database\Eloquent\Builder
  212. */
  213. public function scopeActive($query)
  214. {
  215. return $query->where('status', 'ACTIVE');
  216. }
  217. /**
  218. * 按事件类型查询
  219. *
  220. * @param \Illuminate\Database\Eloquent\Builder $query
  221. * @param string $event
  222. * @return \Illuminate\Database\Eloquent\Builder
  223. */
  224. public function scopeForEvent($query, string $event)
  225. {
  226. return $query->where(function ($q) use ($event) {
  227. $q->whereJsonContains('events', $event)
  228. ->orWhereJsonContains('events', '*');
  229. });
  230. }
  231. /**
  232. * 查询最近失败的Webhook
  233. *
  234. * @param \Illuminate\Database\Eloquent\Builder $query
  235. * @param int $hours
  236. * @return \Illuminate\Database\Eloquent\Builder
  237. */
  238. public function scopeRecentlyFailed($query, int $hours = 24)
  239. {
  240. return $query->where('last_failure_at', '>=', now()->subHours($hours));
  241. }
  242. /**
  243. * 查询需要重试的Webhook
  244. *
  245. * @param \Illuminate\Database\Eloquent\Builder $query
  246. * @return \Illuminate\Database\Eloquent\Builder
  247. */
  248. public function scopeNeedsRetry($query)
  249. {
  250. return $query->whereColumn('current_retry_count', '<', 'retry_count')
  251. ->where('status', 'ACTIVE');
  252. }
  253. /**
  254. * 生成新的密钥
  255. *
  256. * @return string
  257. */
  258. public function regenerateSecret(): string
  259. {
  260. $secret = bin2hex(random_bytes(32));
  261. $this->update(['secret' => $secret]);
  262. return $secret;
  263. }
  264. /**
  265. * 获取掩码后的密钥(用于显示)
  266. *
  267. * @return string
  268. */
  269. public function getMaskedSecretAttribute(): string
  270. {
  271. if (empty($this->secret)) {
  272. return '';
  273. }
  274. return substr($this->secret, 0, 8) . str_repeat('*', 48) . substr($this->secret, -8);
  275. }
  276. }