ServiceLogic.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <?php
  2. namespace App\Module\ThirdParty\Logics;
  3. use App\Module\ThirdParty\Models\ThirdPartyService;
  4. use App\Module\ThirdParty\Models\ThirdPartyCredential;
  5. use App\Module\ThirdParty\Models\ThirdPartyQuota;
  6. use App\Module\ThirdParty\Enums\SERVICE_STATUS;
  7. use App\Module\ThirdParty\Enums\SERVICE_TYPE;
  8. use App\Module\ThirdParty\Enums\AUTH_TYPE;
  9. use Illuminate\Support\Collection;
  10. /**
  11. * 第三方服务业务逻辑类
  12. */
  13. class ServiceLogic
  14. {
  15. /**
  16. * 创建新的第三方服务
  17. *
  18. * @param array $data
  19. * @return ThirdPartyService
  20. * @throws \Exception
  21. */
  22. public static function createService(array $data): ThirdPartyService
  23. {
  24. // 验证服务类型
  25. if (!SERVICE_TYPE::tryFrom($data['type'])) {
  26. throw new \Exception("不支持的服务类型: {$data['type']}");
  27. }
  28. // 验证认证类型
  29. if (!AUTH_TYPE::tryFrom($data['auth_type'])) {
  30. throw new \Exception("不支持的认证类型: {$data['auth_type']}");
  31. }
  32. // 生成唯一的服务代码
  33. if (empty($data['code'])) {
  34. $data['code'] = static::generateUniqueCode($data['name'], $data['provider']);
  35. }
  36. // 验证代码唯一性
  37. if (ThirdPartyService::where('code', $data['code'])->exists()) {
  38. throw new \Exception("服务代码已存在: {$data['code']}");
  39. }
  40. // 设置默认值
  41. $data = array_merge([
  42. 'version' => 'v1',
  43. 'status' => SERVICE_STATUS::INACTIVE->value,
  44. 'priority' => 0,
  45. 'timeout' => config('thirdparty.defaults.timeout', 30),
  46. 'retry_times' => config('thirdparty.defaults.retry_times', 3),
  47. 'retry_delay' => config('thirdparty.defaults.retry_delay', 1000),
  48. 'health_check_interval' => config('thirdparty.defaults.health_check_interval', 300),
  49. 'health_status' => 'UNKNOWN',
  50. ], $data);
  51. return ThirdPartyService::create($data);
  52. }
  53. /**
  54. * 更新服务信息
  55. *
  56. * @param ThirdPartyService $service
  57. * @param array $data
  58. * @return bool
  59. * @throws \Exception
  60. */
  61. public static function updateService(ThirdPartyService $service, array $data): bool
  62. {
  63. // 如果更新服务类型,验证类型有效性
  64. if (isset($data['type']) && !SERVICE_TYPE::tryFrom($data['type'])) {
  65. throw new \Exception("不支持的服务类型: {$data['type']}");
  66. }
  67. // 如果更新认证类型,验证类型有效性
  68. if (isset($data['auth_type']) && !AUTH_TYPE::tryFrom($data['auth_type'])) {
  69. throw new \Exception("不支持的认证类型: {$data['auth_type']}");
  70. }
  71. // 如果更新代码,验证唯一性
  72. if (isset($data['code']) && $data['code'] !== $service->code) {
  73. if (ThirdPartyService::where('code', $data['code'])->where('id', '!=', $service->id)->exists()) {
  74. throw new \Exception("服务代码已存在: {$data['code']}");
  75. }
  76. }
  77. return $service->update($data);
  78. }
  79. /**
  80. * 删除服务
  81. *
  82. * @param ThirdPartyService $service
  83. * @return bool
  84. * @throws \Exception
  85. */
  86. public static function deleteService(ThirdPartyService $service): bool
  87. {
  88. // 检查服务是否有活跃的凭证
  89. $activeCredentials = $service->credentials()->where('is_active', true)->count();
  90. if ($activeCredentials > 0) {
  91. throw new \Exception("服务有 {$activeCredentials} 个活跃凭证,无法删除");
  92. }
  93. // 检查服务状态
  94. if ($service->status === SERVICE_STATUS::ACTIVE->value) {
  95. throw new \Exception("活跃状态的服务无法删除,请先停用服务");
  96. }
  97. return $service->delete();
  98. }
  99. /**
  100. * 更新服务状态
  101. *
  102. * @param ThirdPartyService $service
  103. * @param string $status
  104. * @return bool
  105. * @throws \Exception
  106. */
  107. public static function updateServiceStatus(ThirdPartyService $service, string $status): bool
  108. {
  109. $newStatus = SERVICE_STATUS::tryFrom($status);
  110. if (!$newStatus) {
  111. throw new \Exception("无效的服务状态: {$status}");
  112. }
  113. $currentStatus = SERVICE_STATUS::from($service->status);
  114. // 检查状态转换是否允许
  115. if (!$currentStatus->canTransitionTo($newStatus)) {
  116. throw new \Exception("不能从状态 {$currentStatus->getLabel()} 转换到 {$newStatus->getLabel()}");
  117. }
  118. // 如果要激活服务,检查是否有可用的凭证
  119. if ($newStatus === SERVICE_STATUS::ACTIVE) {
  120. $hasActiveCredential = $service->credentials()
  121. ->where('is_active', true)
  122. ->where(function ($query) {
  123. $query->whereNull('expires_at')
  124. ->orWhere('expires_at', '>', now());
  125. })
  126. ->exists();
  127. if (!$hasActiveCredential) {
  128. throw new \Exception("服务没有可用的认证凭证,无法激活");
  129. }
  130. }
  131. return $service->update(['status' => $status]);
  132. }
  133. /**
  134. * 获取服务列表
  135. *
  136. * @param array $filters
  137. * @param array $options
  138. * @return Collection
  139. */
  140. public static function getServiceList(array $filters = [], array $options = []): Collection
  141. {
  142. $query = ThirdPartyService::query();
  143. // 应用过滤条件
  144. if (isset($filters['type'])) {
  145. $query->where('type', $filters['type']);
  146. }
  147. if (isset($filters['status'])) {
  148. $query->where('status', $filters['status']);
  149. }
  150. if (isset($filters['provider'])) {
  151. $query->where('provider', $filters['provider']);
  152. }
  153. if (isset($filters['search'])) {
  154. $search = $filters['search'];
  155. $query->where(function ($q) use ($search) {
  156. $q->where('name', 'like', "%{$search}%")
  157. ->orWhere('code', 'like', "%{$search}%")
  158. ->orWhere('provider', 'like', "%{$search}%");
  159. });
  160. }
  161. // 应用排序
  162. $sortBy = $options['sort_by'] ?? 'priority';
  163. $sortOrder = $options['sort_order'] ?? 'asc';
  164. $query->orderBy($sortBy, $sortOrder);
  165. // 如果优先级相同,按名称排序
  166. if ($sortBy !== 'name') {
  167. $query->orderBy('name', 'asc');
  168. }
  169. return $query->get();
  170. }
  171. /**
  172. * 获取可用的服务列表
  173. *
  174. * @param string|null $type
  175. * @return Collection
  176. */
  177. public static function getAvailableServices(?string $type = null): Collection
  178. {
  179. $query = ThirdPartyService::query()
  180. ->where('status', SERVICE_STATUS::ACTIVE->value);
  181. if ($type) {
  182. $query->where('type', $type);
  183. }
  184. return $query->orderBy('priority')->orderBy('name')->get();
  185. }
  186. /**
  187. * 根据代码获取服务
  188. *
  189. * @param string $code
  190. * @return ThirdPartyService|null
  191. */
  192. public static function getServiceByCode(string $code): ?ThirdPartyService
  193. {
  194. return ThirdPartyService::where('code', $code)->first();
  195. }
  196. /**
  197. * 检查服务健康状态
  198. *
  199. * @param ThirdPartyService $service
  200. * @return array
  201. */
  202. public static function checkServiceHealth(ThirdPartyService $service): array
  203. {
  204. $result = [
  205. 'service_id' => $service->id,
  206. 'service_code' => $service->code,
  207. 'status' => 'unknown',
  208. 'message' => '',
  209. 'details' => [],
  210. ];
  211. try {
  212. // 检查服务状态
  213. if (!$service->isAvailable()) {
  214. $result['status'] = 'unavailable';
  215. $result['message'] = "服务状态为 {$service->getStatusLabel()},不可用";
  216. return $result;
  217. }
  218. // 检查认证凭证
  219. $credential = $service->getActiveCredential();
  220. if (!$credential) {
  221. $result['status'] = 'error';
  222. $result['message'] = '没有可用的认证凭证';
  223. return $result;
  224. }
  225. if ($credential->isExpired()) {
  226. $result['status'] = 'error';
  227. $result['message'] = '认证凭证已过期';
  228. return $result;
  229. }
  230. // 检查配额
  231. $quotaStatus = static::checkServiceQuota($service);
  232. if (!$quotaStatus['available']) {
  233. $result['status'] = 'quota_exceeded';
  234. $result['message'] = '服务配额已用完';
  235. $result['details']['quota'] = $quotaStatus;
  236. return $result;
  237. }
  238. $result['status'] = 'healthy';
  239. $result['message'] = '服务健康';
  240. $result['details'] = [
  241. 'credential_expires_at' => $credential->expires_at?->toDateTimeString(),
  242. 'quota' => $quotaStatus,
  243. ];
  244. } catch (\Exception $e) {
  245. $result['status'] = 'error';
  246. $result['message'] = $e->getMessage();
  247. }
  248. return $result;
  249. }
  250. /**
  251. * 检查服务配额状态
  252. *
  253. * @param ThirdPartyService $service
  254. * @return array
  255. */
  256. public static function checkServiceQuota(ThirdPartyService $service): array
  257. {
  258. $quotas = $service->quotas()->active()->get();
  259. $result = [
  260. 'available' => true,
  261. 'quotas' => [],
  262. ];
  263. foreach ($quotas as $quota) {
  264. $quotaInfo = [
  265. 'type' => $quota->type,
  266. 'limit' => $quota->limit_value,
  267. 'used' => $quota->used_value,
  268. 'remaining' => $quota->getRemainingQuota(),
  269. 'percentage' => $quota->getUsagePercentage(),
  270. 'exceeded' => $quota->isExceeded(),
  271. 'near_threshold' => $quota->isNearThreshold(),
  272. ];
  273. $result['quotas'][] = $quotaInfo;
  274. if ($quota->isExceeded()) {
  275. $result['available'] = false;
  276. }
  277. }
  278. return $result;
  279. }
  280. /**
  281. * 生成唯一的服务代码
  282. *
  283. * @param string $name
  284. * @param string $provider
  285. * @return string
  286. */
  287. protected static function generateUniqueCode(string $name, string $provider): string
  288. {
  289. $code = strtolower($provider . '_' . str_replace([' ', '-', '.'], '_', $name));
  290. $code = preg_replace('/[^a-z0-9_]/', '', $code);
  291. // 确保代码唯一
  292. $counter = 1;
  293. $originalCode = $code;
  294. while (ThirdPartyService::where('code', $code)->exists()) {
  295. $code = $originalCode . '_' . $counter;
  296. $counter++;
  297. }
  298. return $code;
  299. }
  300. /**
  301. * 获取服务统计信息
  302. *
  303. * @return array
  304. */
  305. public static function getServiceStats(): array
  306. {
  307. $total = ThirdPartyService::count();
  308. $active = ThirdPartyService::where('status', SERVICE_STATUS::ACTIVE->value)->count();
  309. $inactive = ThirdPartyService::where('status', SERVICE_STATUS::INACTIVE->value)->count();
  310. $error = ThirdPartyService::where('status', SERVICE_STATUS::ERROR->value)->count();
  311. $typeStats = ThirdPartyService::selectRaw('type, COUNT(*) as count')
  312. ->groupBy('type')
  313. ->pluck('count', 'type')
  314. ->toArray();
  315. return [
  316. 'total' => $total,
  317. 'active' => $active,
  318. 'inactive' => $inactive,
  319. 'error' => $error,
  320. 'by_type' => $typeStats,
  321. 'health_rate' => $total > 0 ? round(($active / $total) * 100, 2) : 0,
  322. ];
  323. }
  324. }