OpenApiWebhook.php 6.8 KB

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