WebhookEventsValidator.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. <?php
  2. namespace App\Module\OpenAPI\Validators;
  3. use UCore\Validator;
  4. /**
  5. * Webhook事件验证器
  6. */
  7. class WebhookEventsValidator extends Validator
  8. {
  9. /**
  10. * 验证Webhook事件列表是否有效
  11. *
  12. * @param mixed $value 事件列表数组
  13. * @param array $data 验证数据
  14. * @return bool 验证是否通过
  15. */
  16. public function validate(mixed $value, array $data): bool
  17. {
  18. // 从 args 获取参数
  19. $processedKey = $this->args[0] ?? 'processedEvents';
  20. if (empty($value)) {
  21. $this->addError('事件类型不能为空');
  22. return false;
  23. }
  24. if (!is_array($value)) {
  25. $this->addError('事件类型必须是数组格式');
  26. return false;
  27. }
  28. // 验证事件数量
  29. if (count($value) > 50) {
  30. $this->addError('事件类型数量不能超过50个');
  31. return false;
  32. }
  33. $processedEvents = [];
  34. $validEvents = $this->getValidEvents();
  35. foreach ($value as $index => $event) {
  36. if (!is_string($event)) {
  37. $this->addError("第" . ($index + 1) . "个事件类型必须是字符串");
  38. return false;
  39. }
  40. $event = trim($event);
  41. if (empty($event)) {
  42. continue;
  43. }
  44. // 验证事件格式
  45. if (!$this->validateEventFormat($event)) {
  46. return false;
  47. }
  48. // 验证事件是否有效
  49. if (!$this->isValidEvent($event, $validEvents)) {
  50. $this->addError("无效的事件类型: {$event}");
  51. return false;
  52. }
  53. $processedEvents[] = $event;
  54. }
  55. // 去重
  56. $processedEvents = array_unique($processedEvents);
  57. if (empty($processedEvents)) {
  58. $this->addError('至少需要配置一个事件类型');
  59. return false;
  60. }
  61. // 验证事件组合的合理性
  62. if (!$this->validateEventCombination($processedEvents)) {
  63. return false;
  64. }
  65. // 将处理后的事件列表保存到验证对象中
  66. $this->validation->$processedKey = array_values($processedEvents);
  67. return true;
  68. }
  69. /**
  70. * 获取有效的事件类型
  71. *
  72. * @return array
  73. */
  74. protected function getValidEvents(): array
  75. {
  76. return [
  77. // 通配符事件
  78. '*',
  79. // 用户事件
  80. 'user.created',
  81. 'user.updated',
  82. 'user.deleted',
  83. 'user.login',
  84. 'user.logout',
  85. 'user.password_changed',
  86. // 游戏事件
  87. 'game.started',
  88. 'game.finished',
  89. 'game.paused',
  90. 'game.resumed',
  91. 'game.level_up',
  92. 'game.achievement_unlocked',
  93. // 物品事件
  94. 'item.created',
  95. 'item.updated',
  96. 'item.deleted',
  97. 'item.transferred',
  98. 'item.used',
  99. 'item.expired',
  100. // 资金事件
  101. 'fund.deposited',
  102. 'fund.withdrawn',
  103. 'fund.transferred',
  104. 'fund.frozen',
  105. 'fund.unfrozen',
  106. 'fund.balance_changed',
  107. // 交易事件
  108. 'trade.created',
  109. 'trade.completed',
  110. 'trade.cancelled',
  111. 'trade.expired',
  112. 'trade.disputed',
  113. // 系统事件
  114. 'system.maintenance',
  115. 'system.upgrade',
  116. 'system.alert',
  117. 'system.backup',
  118. // API事件
  119. 'api.rate_limit_exceeded',
  120. 'api.authentication_failed',
  121. 'api.permission_denied',
  122. // 应用事件
  123. 'app.created',
  124. 'app.updated',
  125. 'app.suspended',
  126. 'app.activated',
  127. ];
  128. }
  129. /**
  130. * 验证事件格式
  131. *
  132. * @param string $event
  133. * @return bool
  134. */
  135. protected function validateEventFormat(string $event): bool
  136. {
  137. // 通配符事件
  138. if ($event === '*') {
  139. return true;
  140. }
  141. // 事件格式:category.action
  142. if (!preg_match('/^[a-z_]+\.[a-z_]+$/', $event)) {
  143. $this->addError("事件格式错误: {$event},应为 category.action 格式");
  144. return false;
  145. }
  146. // 验证长度
  147. if (strlen($event) > 50) {
  148. $this->addError("事件名称过长: {$event}");
  149. return false;
  150. }
  151. return true;
  152. }
  153. /**
  154. * 检查事件是否有效
  155. *
  156. * @param string $event
  157. * @param array $validEvents
  158. * @return bool
  159. */
  160. protected function isValidEvent(string $event, array $validEvents): bool
  161. {
  162. // 直接匹配
  163. if (in_array($event, $validEvents)) {
  164. return true;
  165. }
  166. // 模式匹配(支持通配符)
  167. foreach ($validEvents as $validEvent) {
  168. if ($this->matchEventPattern($event, $validEvent)) {
  169. return true;
  170. }
  171. }
  172. return false;
  173. }
  174. /**
  175. * 事件模式匹配
  176. *
  177. * @param string $event
  178. * @param string $pattern
  179. * @return bool
  180. */
  181. protected function matchEventPattern(string $event, string $pattern): bool
  182. {
  183. // 精确匹配
  184. if ($event === $pattern) {
  185. return true;
  186. }
  187. // 通配符匹配
  188. if ($pattern === '*') {
  189. return true;
  190. }
  191. // 分类通配符匹配(如 user.*)
  192. if (str_ends_with($pattern, '.*')) {
  193. $category = substr($pattern, 0, -2);
  194. return str_starts_with($event, $category . '.');
  195. }
  196. return false;
  197. }
  198. /**
  199. * 验证事件组合的合理性
  200. *
  201. * @param array $events
  202. * @return bool
  203. */
  204. protected function validateEventCombination(array $events): bool
  205. {
  206. // 如果包含通配符,警告可能的性能问题
  207. if (in_array('*', $events)) {
  208. $this->addWarning('使用通配符事件(*)会接收所有事件,可能影响性能');
  209. // 如果有通配符,其他事件就没有意义了
  210. if (count($events) > 1) {
  211. $this->addWarning('已配置通配符事件(*),其他事件配置将被忽略');
  212. }
  213. }
  214. // 检查是否有冲突的事件组合
  215. $conflictGroups = [
  216. ['user.created', 'user.deleted'],
  217. ['game.started', 'game.finished'],
  218. ['fund.deposited', 'fund.withdrawn'],
  219. ];
  220. foreach ($conflictGroups as $group) {
  221. $foundEvents = array_intersect($events, $group);
  222. if (count($foundEvents) === count($group)) {
  223. $this->addWarning('配置了相互冲突的事件: ' . implode(', ', $foundEvents));
  224. }
  225. }
  226. return true;
  227. }
  228. }