ServiceValidator.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. <?php
  2. namespace App\Module\ThirdParty\Validators;
  3. use App\Module\ThirdParty\Models\ThirdPartyService;
  4. use App\Module\ThirdParty\Enums\SERVICE_TYPE;
  5. use App\Module\ThirdParty\Enums\AUTH_TYPE;
  6. use App\Module\ThirdParty\Enums\SERVICE_STATUS;
  7. /**
  8. * 第三方服务验证器
  9. */
  10. class ServiceValidator
  11. {
  12. /**
  13. * 错误信息
  14. *
  15. * @var array
  16. */
  17. protected array $errors = [];
  18. /**
  19. * 验证创建服务数据
  20. *
  21. * @param array $data
  22. * @return bool
  23. */
  24. public function validateCreate(array $data): bool
  25. {
  26. $this->errors = [];
  27. // 验证必填字段
  28. $this->validateRequired($data, [
  29. 'name' => '服务名称',
  30. 'type' => '服务类型',
  31. 'provider' => '服务提供商',
  32. 'auth_type' => '认证类型',
  33. ]);
  34. // 验证字段格式
  35. $this->validateServiceData($data);
  36. // 验证代码唯一性
  37. if (isset($data['code'])) {
  38. $this->validateCodeUnique($data['code']);
  39. }
  40. return empty($this->errors);
  41. }
  42. /**
  43. * 验证更新服务数据
  44. *
  45. * @param array $data
  46. * @param int $serviceId
  47. * @return bool
  48. */
  49. public function validateUpdate(array $data, int $serviceId): bool
  50. {
  51. $this->errors = [];
  52. // 验证字段格式
  53. $this->validateServiceData($data);
  54. // 验证代码唯一性(排除当前服务)
  55. if (isset($data['code'])) {
  56. $this->validateCodeUnique($data['code'], $serviceId);
  57. }
  58. return empty($this->errors);
  59. }
  60. /**
  61. * 验证服务状态更新
  62. *
  63. * @param string $status
  64. * @param int $serviceId
  65. * @return bool
  66. */
  67. public function validateStatusUpdate(string $status, int $serviceId): bool
  68. {
  69. $this->errors = [];
  70. // 验证状态值
  71. if (!SERVICE_STATUS::tryFrom($status)) {
  72. $this->addError('status', '无效的服务状态');
  73. return false;
  74. }
  75. $service = ThirdPartyService::find($serviceId);
  76. if (!$service) {
  77. $this->addError('service', '服务不存在');
  78. return false;
  79. }
  80. $newStatus = SERVICE_STATUS::from($status);
  81. $currentStatus = SERVICE_STATUS::from($service->status);
  82. // 验证状态转换
  83. if (!$currentStatus->canTransitionTo($newStatus)) {
  84. $this->addError('status', "不能从状态 {$currentStatus->getLabel()} 转换到 {$newStatus->getLabel()}");
  85. }
  86. // 如果要激活服务,检查前置条件
  87. if ($newStatus === SERVICE_STATUS::ACTIVE) {
  88. $this->validateActivationRequirements($service);
  89. }
  90. return empty($this->errors);
  91. }
  92. /**
  93. * 验证服务删除
  94. *
  95. * @param int $serviceId
  96. * @return bool
  97. */
  98. public function validateDelete(int $serviceId): bool
  99. {
  100. $this->errors = [];
  101. $service = ThirdPartyService::find($serviceId);
  102. if (!$service) {
  103. $this->addError('service', '服务不存在');
  104. return false;
  105. }
  106. // 检查服务状态
  107. if ($service->status === SERVICE_STATUS::ACTIVE->value) {
  108. $this->addError('status', '活跃状态的服务无法删除,请先停用服务');
  109. }
  110. // 检查是否有活跃的凭证
  111. $activeCredentials = $service->credentials()->where('is_active', true)->count();
  112. if ($activeCredentials > 0) {
  113. $this->addError('credentials', "服务有 {$activeCredentials} 个活跃凭证,无法删除");
  114. }
  115. // 检查是否有最近的调用记录
  116. $recentLogs = $service->logs()->where('created_at', '>', now()->subHours(1))->count();
  117. if ($recentLogs > 0) {
  118. $this->addError('logs', '服务在1小时内有调用记录,无法删除');
  119. }
  120. return empty($this->errors);
  121. }
  122. /**
  123. * 验证服务数据
  124. *
  125. * @param array $data
  126. * @return void
  127. */
  128. protected function validateServiceData(array $data): void
  129. {
  130. // 验证服务类型
  131. if (isset($data['type']) && !SERVICE_TYPE::tryFrom($data['type'])) {
  132. $this->addError('type', '不支持的服务类型');
  133. }
  134. // 验证认证类型
  135. if (isset($data['auth_type']) && !AUTH_TYPE::tryFrom($data['auth_type'])) {
  136. $this->addError('auth_type', '不支持的认证类型');
  137. }
  138. // 验证服务状态
  139. if (isset($data['status']) && !SERVICE_STATUS::tryFrom($data['status'])) {
  140. $this->addError('status', '无效的服务状态');
  141. }
  142. // 验证名称长度
  143. if (isset($data['name'])) {
  144. if (strlen($data['name']) < 2) {
  145. $this->addError('name', '服务名称至少需要2个字符');
  146. }
  147. if (strlen($data['name']) > 100) {
  148. $this->addError('name', '服务名称不能超过100个字符');
  149. }
  150. }
  151. // 验证代码格式
  152. if (isset($data['code'])) {
  153. if (!preg_match('/^[a-z0-9_]+$/', $data['code'])) {
  154. $this->addError('code', '服务代码只能包含小写字母、数字和下划线');
  155. }
  156. if (strlen($data['code']) < 3) {
  157. $this->addError('code', '服务代码至少需要3个字符');
  158. }
  159. if (strlen($data['code']) > 50) {
  160. $this->addError('code', '服务代码不能超过50个字符');
  161. }
  162. }
  163. // 验证提供商
  164. if (isset($data['provider'])) {
  165. if (strlen($data['provider']) < 2) {
  166. $this->addError('provider', '服务提供商至少需要2个字符');
  167. }
  168. if (strlen($data['provider']) > 50) {
  169. $this->addError('provider', '服务提供商不能超过50个字符');
  170. }
  171. }
  172. // 验证URL格式
  173. if (isset($data['base_url']) && !empty($data['base_url'])) {
  174. if (!filter_var($data['base_url'], FILTER_VALIDATE_URL)) {
  175. $this->addError('base_url', '基础URL格式无效');
  176. }
  177. }
  178. if (isset($data['health_check_url']) && !empty($data['health_check_url'])) {
  179. if (!filter_var($data['health_check_url'], FILTER_VALIDATE_URL)) {
  180. $this->addError('health_check_url', '健康检查URL格式无效');
  181. }
  182. }
  183. if (isset($data['webhook_url']) && !empty($data['webhook_url'])) {
  184. if (!filter_var($data['webhook_url'], FILTER_VALIDATE_URL)) {
  185. $this->addError('webhook_url', 'Webhook URL格式无效');
  186. }
  187. }
  188. // 验证数值范围
  189. if (isset($data['timeout'])) {
  190. if (!is_numeric($data['timeout']) || $data['timeout'] < 1 || $data['timeout'] > 300) {
  191. $this->addError('timeout', '超时时间必须在1-300秒之间');
  192. }
  193. }
  194. if (isset($data['retry_times'])) {
  195. if (!is_numeric($data['retry_times']) || $data['retry_times'] < 0 || $data['retry_times'] > 10) {
  196. $this->addError('retry_times', '重试次数必须在0-10次之间');
  197. }
  198. }
  199. if (isset($data['retry_delay'])) {
  200. if (!is_numeric($data['retry_delay']) || $data['retry_delay'] < 100 || $data['retry_delay'] > 60000) {
  201. $this->addError('retry_delay', '重试延迟必须在100-60000毫秒之间');
  202. }
  203. }
  204. if (isset($data['priority'])) {
  205. if (!is_numeric($data['priority']) || $data['priority'] < 0 || $data['priority'] > 999) {
  206. $this->addError('priority', '优先级必须在0-999之间');
  207. }
  208. }
  209. if (isset($data['health_check_interval'])) {
  210. if (!is_numeric($data['health_check_interval']) || $data['health_check_interval'] < 60 || $data['health_check_interval'] > 86400) {
  211. $this->addError('health_check_interval', '健康检查间隔必须在60-86400秒之间');
  212. }
  213. }
  214. // 验证JSON格式
  215. if (isset($data['config']) && !empty($data['config'])) {
  216. if (is_string($data['config'])) {
  217. json_decode($data['config']);
  218. if (json_last_error() !== JSON_ERROR_NONE) {
  219. $this->addError('config', '配置信息必须是有效的JSON格式');
  220. }
  221. }
  222. }
  223. if (isset($data['headers']) && !empty($data['headers'])) {
  224. if (is_string($data['headers'])) {
  225. json_decode($data['headers']);
  226. if (json_last_error() !== JSON_ERROR_NONE) {
  227. $this->addError('headers', '请求头必须是有效的JSON格式');
  228. }
  229. }
  230. }
  231. if (isset($data['params']) && !empty($data['params'])) {
  232. if (is_string($data['params'])) {
  233. json_decode($data['params']);
  234. if (json_last_error() !== JSON_ERROR_NONE) {
  235. $this->addError('params', '默认参数必须是有效的JSON格式');
  236. }
  237. }
  238. }
  239. }
  240. /**
  241. * 验证激活服务的前置条件
  242. *
  243. * @param ThirdPartyService $service
  244. * @return void
  245. */
  246. protected function validateActivationRequirements(ThirdPartyService $service): void
  247. {
  248. // 检查是否有可用的凭证
  249. $hasActiveCredential = $service->credentials()
  250. ->where('is_active', true)
  251. ->where(function ($query) {
  252. $query->whereNull('expires_at')
  253. ->orWhere('expires_at', '>', now());
  254. })
  255. ->exists();
  256. if (!$hasActiveCredential) {
  257. $this->addError('credentials', '服务没有可用的认证凭证,无法激活');
  258. }
  259. // 检查基础配置
  260. if (empty($service->base_url)) {
  261. $this->addError('base_url', '服务缺少基础URL配置,无法激活');
  262. }
  263. }
  264. /**
  265. * 验证代码唯一性
  266. *
  267. * @param string $code
  268. * @param int|null $excludeId
  269. * @return void
  270. */
  271. protected function validateCodeUnique(string $code, ?int $excludeId = null): void
  272. {
  273. $query = ThirdPartyService::where('code', $code);
  274. if ($excludeId) {
  275. $query->where('id', '!=', $excludeId);
  276. }
  277. if ($query->exists()) {
  278. $this->addError('code', '服务代码已存在');
  279. }
  280. }
  281. /**
  282. * 验证必填字段
  283. *
  284. * @param array $data
  285. * @param array $required
  286. * @return void
  287. */
  288. protected function validateRequired(array $data, array $required): void
  289. {
  290. foreach ($required as $field => $label) {
  291. if (!isset($data[$field]) || empty($data[$field])) {
  292. $this->addError($field, "{$label}不能为空");
  293. }
  294. }
  295. }
  296. /**
  297. * 添加错误信息
  298. *
  299. * @param string $field
  300. * @param string $message
  301. * @return void
  302. */
  303. protected function addError(string $field, string $message): void
  304. {
  305. if (!isset($this->errors[$field])) {
  306. $this->errors[$field] = [];
  307. }
  308. $this->errors[$field][] = $message;
  309. }
  310. /**
  311. * 获取错误信息
  312. *
  313. * @return array
  314. */
  315. public function getErrors(): array
  316. {
  317. return $this->errors;
  318. }
  319. /**
  320. * 获取第一个错误信息
  321. *
  322. * @return string|null
  323. */
  324. public function getFirstError(): ?string
  325. {
  326. if (empty($this->errors)) {
  327. return null;
  328. }
  329. $firstField = array_key_first($this->errors);
  330. return $this->errors[$firstField][0] ?? null;
  331. }
  332. /**
  333. * 检查是否有错误
  334. *
  335. * @return bool
  336. */
  337. public function hasErrors(): bool
  338. {
  339. return !empty($this->errors);
  340. }
  341. }