WebhookDispatchService.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <?php
  2. namespace App\Module\ThirdParty\Services;
  3. use Illuminate\Http\Request;
  4. use Illuminate\Support\Facades\Log;
  5. use App\Module\ThirdParty\Models\ThirdPartyService as ServiceModel;
  6. /**
  7. * Webhook分发服务
  8. *
  9. * 负责将Webhook请求分发到对应的第三方包处理器
  10. * 支持ThirdParty命名空间下的包注册和管理
  11. */
  12. class WebhookDispatchService
  13. {
  14. /**
  15. * 已注册的包处理器映射
  16. * 格式:['包名' => ['路由' => '处理器类名']]
  17. *
  18. * @var array
  19. */
  20. protected static array $packageHandlers = [];
  21. /**
  22. * 分发Webhook请求到指定包的处理器
  23. *
  24. * @param string $packageName 包名
  25. * @param string $handlerRoute Handler路由
  26. * @param Request $request 请求对象
  27. * @return array
  28. * @throws \Exception
  29. */
  30. public function dispatch(string $packageName, string $handlerRoute, Request $request,$noSign = false): array
  31. {
  32. // 只支持POST请求
  33. if ($request->method() !== 'POST') {
  34. throw new \Exception("Webhook只支持POST请求,当前方法: {$request->method()}");
  35. }
  36. // 获取服务配置
  37. $service = $this->getServiceByPackage($packageName);
  38. if(!$noSign){
  39. // 验证Webhook签名(在实例化之前)
  40. if (!$this->validateWebhookSignature($request, $service)) {
  41. throw new \Exception('Webhook签名验证失败');
  42. }
  43. }
  44. // 获取处理器类
  45. $handlerClass = $this->getPackageHandler($packageName, $handlerRoute);
  46. if (!$handlerClass) {
  47. throw new \Exception("包 {$packageName} 的处理器 {$handlerRoute} 不存在");
  48. }
  49. // 检查处理器类是否存在
  50. if (!class_exists($handlerClass)) {
  51. throw new \Exception("处理器类 {$handlerClass} 不存在");
  52. }
  53. // 检查处理器是否继承自WebhookReceiver
  54. if (!is_subclass_of($handlerClass, WebhookReceiver::class)) {
  55. throw new \Exception("处理器类 {$handlerClass} 必须继承自 WebhookReceiver");
  56. }
  57. // 创建处理器实例,传入服务配置对象
  58. $handler = new $handlerClass($packageName, $request, $service);
  59. // 执行处理
  60. $response = $handler->handle($handlerRoute);
  61. // 返回响应数据
  62. return $response->getData(true);
  63. }
  64. /**
  65. * 注册包处理器
  66. *
  67. * @param string $packageName 包名
  68. * @param string $handlerRoute Handler路由
  69. * @param string $handlerClass 处理器类名
  70. */
  71. public static function registerPackageHandler(string $packageName, string $handlerRoute, string $handlerClass): void
  72. {
  73. if (!isset(static::$packageHandlers[$packageName])) {
  74. static::$packageHandlers[$packageName] = [];
  75. }
  76. static::$packageHandlers[$packageName][$handlerRoute] = $handlerClass;
  77. Log::info("注册包处理器", [
  78. 'package_name' => $packageName,
  79. 'handler_route' => $handlerRoute,
  80. 'handler_class' => $handlerClass,
  81. ]);
  82. }
  83. /**
  84. * 批量注册包处理器
  85. *
  86. * @param string $packageName 包名
  87. * @param array $handlers 处理器映射 ['route' => 'ClassName']
  88. */
  89. public static function registerPackageHandlers(string $packageName, array $handlers): void
  90. {
  91. foreach ($handlers as $route => $handlerClass) {
  92. static::registerPackageHandler($packageName, $route, $handlerClass);
  93. }
  94. }
  95. /**
  96. * 获取包的处理器类
  97. *
  98. * @param string $packageName 包名
  99. * @param string $handlerRoute Handler路由
  100. * @return string|null
  101. */
  102. protected function getPackageHandler(string $packageName, string $handlerRoute): ?string
  103. {
  104. return static::$packageHandlers[$packageName][$handlerRoute] ?? null;
  105. }
  106. /**
  107. * 根据包名获取服务配置对象
  108. *
  109. * @param string $packageName 包名
  110. * @return ServiceModel
  111. * @throws \Exception
  112. */
  113. protected function getServiceByPackage(string $packageName): ServiceModel
  114. {
  115. // 查找对应的服务配置
  116. $service = ServiceModel::where('code', $packageName)->first();
  117. if (!$service) {
  118. throw new \Exception("包 {$packageName} 对应的服务配置不存在,请先在thirdparty_services表中注册");
  119. }
  120. return $service;
  121. }
  122. /**
  123. * 根据包名获取服务代码
  124. *
  125. * @param string $packageName 包名
  126. * @return string
  127. * @throws \Exception
  128. */
  129. protected function getServiceCodeByPackage(string $packageName): string
  130. {
  131. return $this->getServiceByPackage($packageName)->code;
  132. }
  133. /**
  134. * 验证Webhook签名
  135. *
  136. * @param Request $request 请求对象
  137. * @param ServiceModel $service 服务配置
  138. * @return bool
  139. */
  140. protected function validateWebhookSignature(Request $request, ServiceModel $service): bool
  141. {
  142. $webhookSecret = $service->webhook_secret;
  143. if (!$webhookSecret) {
  144. // 如果没有配置密钥,则跳过签名验证
  145. return true;
  146. }
  147. $signature = $request->header('X-Signature');
  148. if (!$signature) {
  149. return false;
  150. }
  151. // 使用POST参数排序后拼接的方式生成签名字符串
  152. $signString = $this->buildSignString($request->post());
  153. $expectedSignature = hash_hmac('sha256', $signString, $webhookSecret);
  154. return hash_equals($expectedSignature, $signature);
  155. }
  156. /**
  157. * 构建签名字符串(支付宝方式:参数排序后拼接)
  158. *
  159. * @param array $params 请求参数
  160. * @return string
  161. */
  162. protected function buildSignString(array $params): string
  163. {
  164. // 过滤空值参数
  165. $params = array_filter($params, function ($value) {
  166. return $value !== '' && $value !== null;
  167. });
  168. // 按参数名排序
  169. ksort($params);
  170. // 拼接参数
  171. $stringToBeSigned = '';
  172. foreach ($params as $key => $value) {
  173. // 跳过签名参数本身
  174. if ($key === 'sign' || $key === 'signature') {
  175. continue;
  176. }
  177. // 处理数组和对象类型的值
  178. if (is_array($value) || is_object($value)) {
  179. $value = json_encode($value, JSON_UNESCAPED_UNICODE);
  180. }
  181. $stringToBeSigned .= $key . '=' . $value . '&';
  182. }
  183. // 去除最后一个&符号
  184. return rtrim($stringToBeSigned, '&');
  185. }
  186. /**
  187. * 获取已注册的包列表
  188. *
  189. * @return array
  190. */
  191. public function getRegisteredPackages(): array
  192. {
  193. $packages = [];
  194. foreach (static::$packageHandlers as $packageName => $handlers) {
  195. $packages[$packageName] = [
  196. 'name' => $packageName,
  197. 'handlers' => array_keys($handlers),
  198. 'handler_count' => count($handlers),
  199. ];
  200. }
  201. return $packages;
  202. }
  203. /**
  204. * 获取指定包的处理器列表
  205. *
  206. * @param string $packageName 包名
  207. * @return array
  208. */
  209. public function getPackageHandlers(string $packageName): array
  210. {
  211. return static::$packageHandlers[$packageName] ?? [];
  212. }
  213. /**
  214. * 检查包是否已注册
  215. *
  216. * @param string $packageName 包名
  217. * @return bool
  218. */
  219. public function isPackageRegistered(string $packageName): bool
  220. {
  221. return isset(static::$packageHandlers[$packageName]);
  222. }
  223. /**
  224. * 检查包的处理器是否已注册
  225. *
  226. * @param string $packageName 包名
  227. * @param string $handlerRoute Handler路由
  228. * @return bool
  229. */
  230. public function isHandlerRegistered(string $packageName, string $handlerRoute): bool
  231. {
  232. return isset(static::$packageHandlers[$packageName][$handlerRoute]);
  233. }
  234. /**
  235. * 注销包处理器
  236. *
  237. * @param string $packageName 包名
  238. * @param string|null $handlerRoute Handler路由,为空则注销整个包
  239. */
  240. public static function unregisterPackageHandler(string $packageName, ?string $handlerRoute = null): void
  241. {
  242. if ($handlerRoute === null) {
  243. // 注销整个包
  244. unset(static::$packageHandlers[$packageName]);
  245. Log::info("注销包", ['package_name' => $packageName]);
  246. } else {
  247. // 注销指定处理器
  248. unset(static::$packageHandlers[$packageName][$handlerRoute]);
  249. Log::info("注销包处理器", [
  250. 'package_name' => $packageName,
  251. 'handler_route' => $handlerRoute,
  252. ]);
  253. }
  254. }
  255. /**
  256. * 清空所有已注册的包处理器
  257. */
  258. public static function clearAllHandlers(): void
  259. {
  260. static::$packageHandlers = [];
  261. Log::info("清空所有包处理器");
  262. }
  263. }