| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- <?php
- namespace App\Module\ThirdParty\Services;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\Log;
- use App\Module\ThirdParty\Models\ThirdPartyService as ServiceModel;
- /**
- * Webhook分发服务
- *
- * 负责将Webhook请求分发到对应的第三方包处理器
- * 支持ThirdParty命名空间下的包注册和管理
- */
- class WebhookDispatchService
- {
- /**
- * 已注册的包处理器映射
- * 格式:['包名' => ['路由' => '处理器类名']]
- *
- * @var array
- */
- protected static array $packageHandlers = [];
- /**
- * 分发Webhook请求到指定包的处理器
- *
- * @param string $packageName 包名
- * @param string $handlerRoute Handler路由
- * @param Request $request 请求对象
- * @return array
- * @throws \Exception
- */
- public function dispatch(string $packageName, string $handlerRoute, Request $request,$noSign = false): array
- {
- // 只支持POST请求
- if ($request->method() !== 'POST') {
- throw new \Exception("Webhook只支持POST请求,当前方法: {$request->method()}");
- }
- // 获取服务配置
- $service = $this->getServiceByPackage($packageName);
- if(!$noSign){
- // 验证Webhook签名(在实例化之前)
- if (!$this->validateWebhookSignature($request, $service)) {
- throw new \Exception('Webhook签名验证失败');
- }
- }
- // 获取处理器类
- $handlerClass = $this->getPackageHandler($packageName, $handlerRoute);
- if (!$handlerClass) {
- // 尝试重新注册处理器(防止静态变量被重置的情况)
- $this->tryReregisterPackageHandlers($packageName);
- $handlerClass = $this->getPackageHandler($packageName, $handlerRoute);
- if (!$handlerClass) {
- throw new \Exception("包 {$packageName} 的处理器 {$handlerRoute} 不存在");
- }
- }
- // 检查处理器类是否存在
- if (!class_exists($handlerClass)) {
- throw new \Exception("处理器类 {$handlerClass} 不存在");
- }
- // 检查处理器是否继承自WebhookReceiver
- if (!is_subclass_of($handlerClass, WebhookReceiver::class)) {
- throw new \Exception("处理器类 {$handlerClass} 必须继承自 WebhookReceiver");
- }
- // 创建处理器实例,传入服务配置对象
- $handler = new $handlerClass($packageName, $request, $service);
- // 执行处理
- $response = $handler->handle($handlerRoute);
- // 返回响应数据
- return $response->getData(true);
- }
- /**
- * 注册包处理器
- *
- * @param string $packageName 包名
- * @param string $handlerRoute Handler路由
- * @param string $handlerClass 处理器类名
- */
- public static function registerPackageHandler(string $packageName, string $handlerRoute, string $handlerClass): void
- {
- if (!isset(static::$packageHandlers[$packageName])) {
- static::$packageHandlers[$packageName] = [];
- }
- static::$packageHandlers[$packageName][$handlerRoute] = $handlerClass;
- Log::info("注册包处理器", [
- 'package_name' => $packageName,
- 'handler_route' => $handlerRoute,
- 'handler_class' => $handlerClass,
- ]);
- }
- /**
- * 批量注册包处理器
- *
- * @param string $packageName 包名
- * @param array $handlers 处理器映射 ['route' => 'ClassName']
- */
- public static function registerPackageHandlers(string $packageName, array $handlers): void
- {
- foreach ($handlers as $route => $handlerClass) {
- static::registerPackageHandler($packageName, $route, $handlerClass);
- }
- }
- /**
- * 获取包的处理器类
- *
- * @param string $packageName 包名
- * @param string $handlerRoute Handler路由
- * @return string|null
- */
- protected function getPackageHandler(string $packageName, string $handlerRoute): ?string
- {
- return static::$packageHandlers[$packageName][$handlerRoute] ?? null;
- }
- /**
- * 根据包名获取服务配置对象
- *
- * @param string $packageName 包名
- * @return ServiceModel
- * @throws \Exception
- */
- protected function getServiceByPackage(string $packageName): ServiceModel
- {
- // 查找对应的服务配置
- $service = ServiceModel::where('code', $packageName)->first();
- if (!$service) {
- throw new \Exception("包 {$packageName} 对应的服务配置不存在,请先在thirdparty_services表中注册");
- }
- return $service;
- }
- /**
- * 根据包名获取服务代码
- *
- * @param string $packageName 包名
- * @return string
- * @throws \Exception
- */
- protected function getServiceCodeByPackage(string $packageName): string
- {
- return $this->getServiceByPackage($packageName)->code;
- }
- /**
- * 验证Webhook签名
- *
- * @param Request $request 请求对象
- * @param ServiceModel $service 服务配置
- * @return bool
- */
- protected function validateWebhookSignature(Request $request, ServiceModel $service): bool
- {
- $webhookSecret = $service->webhook_secret;
- if (!$webhookSecret) {
- // 如果没有配置密钥,则跳过签名验证
- return true;
- }
- $signature = $request->header('X-Signature');
- if (!$signature) {
- return false;
- }
- // 使用POST参数排序后拼接的方式生成签名字符串
- $signString = $this->buildSignString($request->post());
- $expectedSignature = hash_hmac('sha256', $signString, $webhookSecret);
- return hash_equals($expectedSignature, $signature);
- }
- /**
- * 构建签名字符串(支付宝方式:参数排序后拼接)
- *
- * @param array $params 请求参数
- * @return string
- */
- protected function buildSignString(array $params): string
- {
- // 过滤空值参数
- $params = array_filter($params, function ($value) {
- return $value !== '' && $value !== null;
- });
- // 按参数名排序
- ksort($params);
- // 拼接参数
- $stringToBeSigned = '';
- foreach ($params as $key => $value) {
- // 跳过签名参数本身
- if ($key === 'sign' || $key === 'signature') {
- continue;
- }
- // 处理数组和对象类型的值
- if (is_array($value) || is_object($value)) {
- $value = json_encode($value, JSON_UNESCAPED_UNICODE);
- }
- $stringToBeSigned .= $key . '=' . $value . '&';
- }
- // 去除最后一个&符号
- return rtrim($stringToBeSigned, '&');
- }
- /**
- * 获取已注册的包列表
- *
- * @return array
- */
- public function getRegisteredPackages(): array
- {
- $packages = [];
- foreach (static::$packageHandlers as $packageName => $handlers) {
- $packages[$packageName] = [
- 'name' => $packageName,
- 'handlers' => array_keys($handlers),
- 'handler_count' => count($handlers),
- ];
- }
- return $packages;
- }
- /**
- * 获取指定包的处理器列表
- *
- * @param string $packageName 包名
- * @return array
- */
- public function getPackageHandlers(string $packageName): array
- {
- return static::$packageHandlers[$packageName] ?? [];
- }
- /**
- * 检查包是否已注册
- *
- * @param string $packageName 包名
- * @return bool
- */
- public function isPackageRegistered(string $packageName): bool
- {
- return isset(static::$packageHandlers[$packageName]);
- }
- /**
- * 检查包的处理器是否已注册
- *
- * @param string $packageName 包名
- * @param string $handlerRoute Handler路由
- * @return bool
- */
- public function isHandlerRegistered(string $packageName, string $handlerRoute): bool
- {
- return isset(static::$packageHandlers[$packageName][$handlerRoute]);
- }
- /**
- * 注销包处理器
- *
- * @param string $packageName 包名
- * @param string|null $handlerRoute Handler路由,为空则注销整个包
- */
- public static function unregisterPackageHandler(string $packageName, ?string $handlerRoute = null): void
- {
- if ($handlerRoute === null) {
- // 注销整个包
- unset(static::$packageHandlers[$packageName]);
- Log::info("注销包", ['package_name' => $packageName]);
- } else {
- // 注销指定处理器
- unset(static::$packageHandlers[$packageName][$handlerRoute]);
- Log::info("注销包处理器", [
- 'package_name' => $packageName,
- 'handler_route' => $handlerRoute,
- ]);
- }
- }
- /**
- * 清空所有已注册的包处理器
- */
- public static function clearAllHandlers(): void
- {
- static::$packageHandlers = [];
- Log::info("清空所有包处理器");
- }
- /**
- * 尝试重新注册包处理器
- *
- * @param string $packageName 包名
- */
- protected function tryReregisterPackageHandlers(string $packageName): void
- {
- try {
- // 根据包名尝试重新触发服务提供者的注册
- switch ($packageName) {
- case 'urs':
- // 重新注册URS包的处理器
- if (class_exists('\ThirdParty\Urs\UrsServiceProvider')) {
- $provider = new \ThirdParty\Urs\UrsServiceProvider(app());
- $provider->boot();
- Log::info("重新注册URS包处理器成功");
- }
- break;
- // 可以在这里添加其他包的重新注册逻辑
- default:
- Log::warning("未知包名,无法重新注册处理器", ['package_name' => $packageName]);
- break;
- }
- } catch (\Exception $e) {
- Log::error("重新注册包处理器失败", [
- 'package_name' => $packageName,
- 'error' => $e->getMessage()
- ]);
- }
- }
- }
|