BaseWebhook.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. <?php
  2. namespace App\Module\ThirdParty\Services;
  3. use App\Module\ThirdParty\Models\ThirdPartyService as ServiceModel;
  4. use App\Module\ThirdParty\Models\ThirdPartyCredential;
  5. use App\Module\ThirdParty\Models\ThirdPartyLog;
  6. use App\Module\ThirdParty\Enums\LOG_LEVEL;
  7. use Illuminate\Http\Request;
  8. use Illuminate\Http\JsonResponse;
  9. use Illuminate\Support\Facades\Log;
  10. /**
  11. * 第三方Webhook基类
  12. *
  13. * 处理Webhook分发机制,包含配置读取、配额管理、日志记录等通用功能
  14. * 基类提供handler接口用于实现Webhook的具体逻辑
  15. */
  16. abstract class BaseWebhook
  17. {
  18. /**
  19. * 服务代码
  20. */
  21. protected string $serviceCode;
  22. /**
  23. * 服务配置
  24. */
  25. protected ?ServiceModel $service = null;
  26. /**
  27. * 认证凭证
  28. */
  29. protected ?ThirdPartyCredential $credential = null;
  30. /**
  31. * 请求对象
  32. */
  33. protected Request $request;
  34. /**
  35. * 请求ID
  36. */
  37. protected string $requestId;
  38. /**
  39. * 开始时间
  40. */
  41. protected float $startTime;
  42. /**
  43. * 构造函数
  44. *
  45. * @param string $serviceCode 服务代码
  46. * @param Request $request 请求对象
  47. */
  48. public function __construct(string $serviceCode, Request $request)
  49. {
  50. $this->serviceCode = $serviceCode;
  51. $this->request = $request;
  52. $this->requestId = uniqid('webhook_', true);
  53. $this->startTime = microtime(true);
  54. // 初始化服务配置
  55. $this->initializeService();
  56. }
  57. /**
  58. * 初始化服务配置
  59. *
  60. * @throws \Exception
  61. */
  62. protected function initializeService(): void
  63. {
  64. $this->service = ServiceModel::where('code', $this->serviceCode)->first();
  65. if (!$this->service) {
  66. throw new \Exception("服务 {$this->serviceCode} 不存在");
  67. }
  68. // 获取认证凭证
  69. $this->credential = $this->service->getActiveCredential();
  70. if (!$this->credential) {
  71. throw new \Exception("服务 {$this->serviceCode} 没有可用的认证凭证");
  72. }
  73. }
  74. /**
  75. * 处理Webhook请求
  76. *
  77. * @param string $action 操作类型
  78. * @return JsonResponse
  79. */
  80. public function handle(string $action): JsonResponse
  81. {
  82. try {
  83. // 验证签名
  84. if (!$this->validateSignature()) {
  85. throw new \Exception('Webhook签名验证失败');
  86. }
  87. // 验证请求格式
  88. $this->validateRequest();
  89. // 执行具体的处理逻辑
  90. $result = $this->handler($action, $this->request);
  91. // 记录成功日志
  92. $this->logWebhook($action, $this->request->all(), $result, true);
  93. return response()->json([
  94. 'success' => true,
  95. 'data' => $result,
  96. 'request_id' => $this->requestId,
  97. ]);
  98. } catch (\Exception $e) {
  99. // 记录失败日志
  100. $this->logWebhook($action, $this->request->all(), ['error' => $e->getMessage()], false);
  101. Log::error("Webhook处理失败: {$this->serviceCode}/{$action}", [
  102. 'error' => $e->getMessage(),
  103. 'request_id' => $this->requestId,
  104. 'request_data' => $this->request->all(),
  105. ]);
  106. return response()->json([
  107. 'success' => false,
  108. 'error' => $e->getMessage(),
  109. 'request_id' => $this->requestId,
  110. ], 400);
  111. }
  112. }
  113. /**
  114. * 具体的Webhook处理逻辑(由子类实现)
  115. *
  116. * @param string $action 操作类型
  117. * @param Request $request 请求对象
  118. * @return array
  119. */
  120. abstract protected function handler(string $action, Request $request): array;
  121. /**
  122. * 验证Webhook签名
  123. *
  124. * @return bool
  125. */
  126. protected function validateSignature(): bool
  127. {
  128. $webhookSecret = $this->service->webhook_secret;
  129. if (!$webhookSecret) {
  130. // 如果没有配置密钥,则跳过签名验证
  131. return true;
  132. }
  133. $signature = $this->request->header('X-Signature');
  134. if (!$signature) {
  135. return false;
  136. }
  137. $payload = $this->request->getContent();
  138. $expectedSignature = hash_hmac('sha256', $payload, $webhookSecret);
  139. return hash_equals($expectedSignature, $signature);
  140. }
  141. /**
  142. * 验证请求格式(由子类重写)
  143. *
  144. * @throws \Exception
  145. */
  146. protected function validateRequest(): void
  147. {
  148. // 默认不做验证,子类可以重写此方法
  149. }
  150. /**
  151. * 记录Webhook日志
  152. *
  153. * @param string $action 操作类型
  154. * @param array $requestData 请求数据
  155. * @param array $result 处理结果
  156. * @param bool $success 是否成功
  157. */
  158. protected function logWebhook(string $action, array $requestData, array $result, bool $success): void
  159. {
  160. $responseTime = (int)((microtime(true) - $this->startTime) * 1000);
  161. ThirdPartyLog::create([
  162. 'service_id' => $this->service->id,
  163. 'credential_id' => $this->credential->id,
  164. 'request_id' => $this->requestId,
  165. 'method' => 'WEBHOOK',
  166. 'url' => $this->request->fullUrl(),
  167. 'request_headers' => json_encode($this->request->headers->all()),
  168. 'request_body' => json_encode($requestData),
  169. 'response_status' => $success ? 200 : 400,
  170. 'response_headers' => json_encode([]),
  171. 'response_body' => json_encode($result),
  172. 'response_time' => $responseTime,
  173. 'success' => $success,
  174. 'error_message' => $success ? null : ($result['error'] ?? '未知错误'),
  175. 'level' => $success ? LOG_LEVEL::INFO->value : LOG_LEVEL::ERROR->value,
  176. 'ip_address' => $this->request->ip(),
  177. 'user_agent' => $this->request->userAgent(),
  178. 'called_at' => now(),
  179. ]);
  180. }
  181. /**
  182. * 获取服务配置
  183. *
  184. * @param string|null $key 配置键名,为空则返回全部配置
  185. * @return mixed
  186. */
  187. protected function getConfig(?string $key = null)
  188. {
  189. $config = $this->service->config ?? [];
  190. if ($key === null) {
  191. return $config;
  192. }
  193. return $config[$key] ?? null;
  194. }
  195. /**
  196. * 获取服务信息
  197. *
  198. * @return ServiceModel
  199. */
  200. protected function getService(): ServiceModel
  201. {
  202. return $this->service;
  203. }
  204. /**
  205. * 获取认证凭证
  206. *
  207. * @return ThirdPartyCredential
  208. */
  209. protected function getCredential(): ThirdPartyCredential
  210. {
  211. return $this->credential;
  212. }
  213. /**
  214. * 获取请求ID
  215. *
  216. * @return string
  217. */
  218. protected function getRequestId(): string
  219. {
  220. return $this->requestId;
  221. }
  222. /**
  223. * 获取请求对象
  224. *
  225. * @return Request
  226. */
  227. protected function getRequest(): Request
  228. {
  229. return $this->request;
  230. }
  231. }