WebhookDispatchService.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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. // 尝试重新注册处理器(防止静态变量被重置的情况)
  48. $this->tryReregisterPackageHandlers($packageName);
  49. $handlerClass = $this->getPackageHandler($packageName, $handlerRoute);
  50. if (!$handlerClass) {
  51. throw new \Exception("包 {$packageName} 的处理器 {$handlerRoute} 不存在");
  52. }
  53. }
  54. // 检查处理器类是否存在
  55. if (!class_exists($handlerClass)) {
  56. throw new \Exception("处理器类 {$handlerClass} 不存在");
  57. }
  58. // 检查处理器是否继承自WebhookReceiver
  59. if (!is_subclass_of($handlerClass, WebhookReceiver::class)) {
  60. throw new \Exception("处理器类 {$handlerClass} 必须继承自 WebhookReceiver");
  61. }
  62. // 创建处理器实例,传入服务配置对象
  63. $handler = new $handlerClass($packageName, $request, $service);
  64. // 执行处理
  65. $response = $handler->handle($handlerRoute);
  66. // 返回响应数据
  67. return $response->getData(true);
  68. }
  69. /**
  70. * 注册包处理器
  71. *
  72. * @param string $packageName 包名
  73. * @param string $handlerRoute Handler路由
  74. * @param string $handlerClass 处理器类名
  75. */
  76. public static function registerPackageHandler(string $packageName, string $handlerRoute, string $handlerClass): void
  77. {
  78. if (!isset(static::$packageHandlers[$packageName])) {
  79. static::$packageHandlers[$packageName] = [];
  80. }
  81. static::$packageHandlers[$packageName][$handlerRoute] = $handlerClass;
  82. Log::info("注册包处理器", [
  83. 'package_name' => $packageName,
  84. 'handler_route' => $handlerRoute,
  85. 'handler_class' => $handlerClass,
  86. ]);
  87. }
  88. /**
  89. * 批量注册包处理器
  90. *
  91. * @param string $packageName 包名
  92. * @param array $handlers 处理器映射 ['route' => 'ClassName']
  93. */
  94. public static function registerPackageHandlers(string $packageName, array $handlers): void
  95. {
  96. foreach ($handlers as $route => $handlerClass) {
  97. static::registerPackageHandler($packageName, $route, $handlerClass);
  98. }
  99. }
  100. /**
  101. * 获取包的处理器类
  102. *
  103. * @param string $packageName 包名
  104. * @param string $handlerRoute Handler路由
  105. * @return string|null
  106. */
  107. protected function getPackageHandler(string $packageName, string $handlerRoute): ?string
  108. {
  109. return static::$packageHandlers[$packageName][$handlerRoute] ?? null;
  110. }
  111. /**
  112. * 根据包名获取服务配置对象
  113. *
  114. * @param string $packageName 包名
  115. * @return ServiceModel
  116. * @throws \Exception
  117. */
  118. protected function getServiceByPackage(string $packageName): ServiceModel
  119. {
  120. // 查找对应的服务配置
  121. $service = ServiceModel::where('code', $packageName)->first();
  122. if (!$service) {
  123. throw new \Exception("包 {$packageName} 对应的服务配置不存在,请先在thirdparty_services表中注册");
  124. }
  125. return $service;
  126. }
  127. /**
  128. * 根据包名获取服务代码
  129. *
  130. * @param string $packageName 包名
  131. * @return string
  132. * @throws \Exception
  133. */
  134. protected function getServiceCodeByPackage(string $packageName): string
  135. {
  136. return $this->getServiceByPackage($packageName)->code;
  137. }
  138. /**
  139. * 验证Webhook签名
  140. *
  141. * @param Request $request 请求对象
  142. * @param ServiceModel $service 服务配置
  143. * @return bool
  144. */
  145. protected function validateWebhookSignature(Request $request, ServiceModel $service): bool
  146. {
  147. $webhookSecret = $service->webhook_secret;
  148. if (!$webhookSecret) {
  149. // 如果没有配置密钥,则跳过签名验证
  150. return true;
  151. }
  152. $signature = $request->header('X-Signature');
  153. if (!$signature) {
  154. return false;
  155. }
  156. // 使用POST参数排序后拼接的方式生成签名字符串
  157. $signString = $this->buildSignString($request->post());
  158. $expectedSignature = hash_hmac('sha256', $signString, $webhookSecret);
  159. return hash_equals($expectedSignature, $signature);
  160. }
  161. /**
  162. * 构建签名字符串(支付宝方式:参数排序后拼接)
  163. *
  164. * @param array $params 请求参数
  165. * @return string
  166. */
  167. protected function buildSignString(array $params): string
  168. {
  169. // 过滤空值参数
  170. $params = array_filter($params, function ($value) {
  171. return $value !== '' && $value !== null;
  172. });
  173. // 按参数名排序
  174. ksort($params);
  175. // 拼接参数
  176. $stringToBeSigned = '';
  177. foreach ($params as $key => $value) {
  178. // 跳过签名参数本身
  179. if ($key === 'sign' || $key === 'signature') {
  180. continue;
  181. }
  182. // 处理数组和对象类型的值
  183. if (is_array($value) || is_object($value)) {
  184. $value = json_encode($value, JSON_UNESCAPED_UNICODE);
  185. }
  186. $stringToBeSigned .= $key . '=' . $value . '&';
  187. }
  188. // 去除最后一个&符号
  189. return rtrim($stringToBeSigned, '&');
  190. }
  191. /**
  192. * 获取已注册的包列表
  193. *
  194. * @return array
  195. */
  196. public function getRegisteredPackages(): array
  197. {
  198. $packages = [];
  199. foreach (static::$packageHandlers as $packageName => $handlers) {
  200. $packages[$packageName] = [
  201. 'name' => $packageName,
  202. 'handlers' => array_keys($handlers),
  203. 'handler_count' => count($handlers),
  204. ];
  205. }
  206. return $packages;
  207. }
  208. /**
  209. * 获取指定包的处理器列表
  210. *
  211. * @param string $packageName 包名
  212. * @return array
  213. */
  214. public function getPackageHandlers(string $packageName): array
  215. {
  216. return static::$packageHandlers[$packageName] ?? [];
  217. }
  218. /**
  219. * 检查包是否已注册
  220. *
  221. * @param string $packageName 包名
  222. * @return bool
  223. */
  224. public function isPackageRegistered(string $packageName): bool
  225. {
  226. return isset(static::$packageHandlers[$packageName]);
  227. }
  228. /**
  229. * 检查包的处理器是否已注册
  230. *
  231. * @param string $packageName 包名
  232. * @param string $handlerRoute Handler路由
  233. * @return bool
  234. */
  235. public function isHandlerRegistered(string $packageName, string $handlerRoute): bool
  236. {
  237. return isset(static::$packageHandlers[$packageName][$handlerRoute]);
  238. }
  239. /**
  240. * 注销包处理器
  241. *
  242. * @param string $packageName 包名
  243. * @param string|null $handlerRoute Handler路由,为空则注销整个包
  244. */
  245. public static function unregisterPackageHandler(string $packageName, ?string $handlerRoute = null): void
  246. {
  247. if ($handlerRoute === null) {
  248. // 注销整个包
  249. unset(static::$packageHandlers[$packageName]);
  250. Log::info("注销包", ['package_name' => $packageName]);
  251. } else {
  252. // 注销指定处理器
  253. unset(static::$packageHandlers[$packageName][$handlerRoute]);
  254. Log::info("注销包处理器", [
  255. 'package_name' => $packageName,
  256. 'handler_route' => $handlerRoute,
  257. ]);
  258. }
  259. }
  260. /**
  261. * 清空所有已注册的包处理器
  262. */
  263. public static function clearAllHandlers(): void
  264. {
  265. static::$packageHandlers = [];
  266. Log::info("清空所有包处理器");
  267. }
  268. /**
  269. * 尝试重新注册包处理器
  270. *
  271. * @param string $packageName 包名
  272. */
  273. protected function tryReregisterPackageHandlers(string $packageName): void
  274. {
  275. try {
  276. // 根据包名尝试重新触发服务提供者的注册
  277. switch ($packageName) {
  278. case 'urs':
  279. // 重新注册URS包的处理器
  280. if (class_exists('\ThirdParty\Urs\UrsServiceProvider')) {
  281. $provider = new \ThirdParty\Urs\UrsServiceProvider(app());
  282. $provider->boot();
  283. Log::info("重新注册URS包处理器成功");
  284. }
  285. break;
  286. // 可以在这里添加其他包的重新注册逻辑
  287. default:
  288. Log::warning("未知包名,无法重新注册处理器", ['package_name' => $packageName]);
  289. break;
  290. }
  291. } catch (\Exception $e) {
  292. Log::error("重新注册包处理器失败", [
  293. 'package_name' => $packageName,
  294. 'error' => $e->getMessage()
  295. ]);
  296. }
  297. }
  298. }