瀏覽代碼

ThirdParty模块继续开发 - 完善业务逻辑和管理功能

✨ 新增功能:
- 创建完整的业务逻辑层(ServiceLogic, CredentialLogic)
- 实现4个核心服务类(CredentialService, MonitorService, LogService, QuotaService)
- 开发5个命令行工具(健康检查、配额重置、日志清理、服务同步、服务测试)
- 创建后台管理控制器(ThirdPartyServiceController)
- 实现数据仓库层(5个Repository类)
- 开发验证器和验证规则(ServiceValidator, CredentialValidator, ServiceValidation)

🔧 业务逻辑层:
- ServiceLogic: 服务创建、更新、删除、状态管理、健康检查、配额检查
- CredentialLogic: 凭证创建、更新、删除、激活管理、测试连接、轮换凭证

📊 服务层功能:
- CredentialService: 凭证管理的完整API接口
- MonitorService: 健康检查、性能监控、可用性报告
- LogService: 日志记录、统计分析、错误追踪、数据导出
- QuotaService: 配额管理、使用统计、自动重置、阈值告警

🛠️ 命令行工具:
- HealthCheckCommand: 执行服务健康检查,支持单个/批量检查
- QuotaResetCommand: 配额重置管理,支持预览和强制重置
- CleanupLogsCommand: 日志清理工具,支持按时间和类型清理
- SyncServicesCommand: 从配置文件同步服务,支持批量导入
- TestServiceCommand: 服务连接测试,支持凭证和API测试

🎛️ 后台管理:
- ThirdPartyServiceController: 完整的服务管理界面
- 5个Repository类: 专门用于后台数据访问
- 丰富的Grid显示、Form表单、Show详情页面

✅ 验证体系:
- ServiceValidator: 服务创建、更新、删除、状态变更验证
- CredentialValidator: 凭证创建、更新、删除、激活验证
- ServiceValidation: 统一的验证规则封装

🔧 技术特性:
- 完整的错误处理和异常管理
- 丰富的过滤、排序、搜索功能
- 批量操作支持
- 数据导出功能
- 统计分析功能
- 自动化运维工具
notfff 7 月之前
父節點
當前提交
5ca6bf3381
共有 22 個文件被更改,包括 5076 次插入0 次删除
  1. 277 0
      app/Module/ThirdParty/AdminControllers/ThirdPartyServiceController.php
  2. 311 0
      app/Module/ThirdParty/Commands/CleanupLogsCommand.php
  3. 226 0
      app/Module/ThirdParty/Commands/HealthCheckCommand.php
  4. 227 0
      app/Module/ThirdParty/Commands/QuotaResetCommand.php
  5. 346 0
      app/Module/ThirdParty/Commands/SyncServicesCommand.php
  6. 355 0
      app/Module/ThirdParty/Commands/TestServiceCommand.php
  7. 434 0
      app/Module/ThirdParty/Logics/CredentialLogic.php
  8. 377 0
      app/Module/ThirdParty/Logics/ServiceLogic.php
  9. 4 0
      app/Module/ThirdParty/Providers/ThirdPartyServiceProvider.php
  10. 21 0
      app/Module/ThirdParty/Repositorys/ThirdPartyCredentialRepository.php
  11. 21 0
      app/Module/ThirdParty/Repositorys/ThirdPartyLogRepository.php
  12. 21 0
      app/Module/ThirdParty/Repositorys/ThirdPartyMonitorRepository.php
  13. 21 0
      app/Module/ThirdParty/Repositorys/ThirdPartyQuotaRepository.php
  14. 21 0
      app/Module/ThirdParty/Repositorys/ThirdPartyServiceRepository.php
  15. 311 0
      app/Module/ThirdParty/Services/CredentialService.php
  16. 317 0
      app/Module/ThirdParty/Services/LogService.php
  17. 426 0
      app/Module/ThirdParty/Services/MonitorService.php
  18. 405 0
      app/Module/ThirdParty/Services/QuotaService.php
  19. 162 0
      app/Module/ThirdParty/Validations/ServiceValidation.php
  20. 386 0
      app/Module/ThirdParty/Validators/CredentialValidator.php
  21. 395 0
      app/Module/ThirdParty/Validators/ServiceValidator.php
  22. 12 0
      app/PlugIn/Uraus/readme.md

+ 277 - 0
app/Module/ThirdParty/AdminControllers/ThirdPartyServiceController.php

@@ -0,0 +1,277 @@
+<?php
+
+namespace App\Module\ThirdParty\AdminControllers;
+
+use UCore\DcatAdmin\AdminController;
+use App\Module\ThirdParty\Models\ThirdPartyService;
+use App\Module\ThirdParty\Repositorys\ThirdPartyServiceRepository;
+use App\Module\ThirdParty\Enums\SERVICE_TYPE;
+use App\Module\ThirdParty\Enums\AUTH_TYPE;
+use App\Module\ThirdParty\Enums\SERVICE_STATUS;
+use Dcat\Admin\Form;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Show;
+use Dcat\Admin\Http\Controllers\AdminController as BaseAdminController;
+
+/**
+ * 第三方服务管理控制器
+ * 
+ * 路由: /admin/thirdparty/services
+ */
+class ThirdPartyServiceController extends AdminController
+{
+    /**
+     * 页面标题
+     *
+     * @var string
+     */
+    protected $title = '第三方服务管理';
+
+    /**
+     * 数据仓库
+     *
+     * @return string
+     */
+    protected function repository()
+    {
+        return ThirdPartyServiceRepository::class;
+    }
+
+    /**
+     * 列表页面
+     *
+     * @return Grid
+     */
+    protected function grid(): Grid
+    {
+        return Grid::make(new ThirdPartyServiceRepository(), function (Grid $grid) {
+            // 基础设置
+            $grid->column('id', 'ID')->sortable();
+            $grid->column('name', '服务名称')->sortable();
+            $grid->column('code', '服务代码')->sortable();
+            
+            // 服务类型
+            $grid->column('type', '服务类型')->display(function ($type) {
+                $serviceType = SERVICE_TYPE::tryFrom($type);
+                if ($serviceType) {
+                    $label = $serviceType->getLabel();
+                    $color = $serviceType->getColor();
+                    return "<span class='badge badge-{$color}'>{$label}</span>";
+                }
+                return $type;
+            });
+
+            // 服务提供商
+            $grid->column('provider', '服务提供商')->sortable();
+
+            // 认证类型
+            $grid->column('auth_type', '认证类型')->display(function ($authType) {
+                $auth = AUTH_TYPE::tryFrom($authType);
+                if ($auth) {
+                    $label = $auth->getLabel();
+                    $level = $auth->getSecurityLevel();
+                    $color = $auth->getSecurityLevelColor();
+                    return "<span class='badge badge-{$color}' title='安全级别: {$level}'>{$label}</span>";
+                }
+                return $authType;
+            });
+
+            // 服务状态
+            $grid->column('status', '状态')->display(function ($status) {
+                $serviceStatus = SERVICE_STATUS::tryFrom($status);
+                if ($serviceStatus) {
+                    $label = $serviceStatus->getLabel();
+                    $color = $serviceStatus->getColor();
+                    $icon = $serviceStatus->getIcon();
+                    return "<span class='badge badge-{$color}'><i class='{$icon}'></i> {$label}</span>";
+                }
+                return $status;
+            });
+
+            // 优先级
+            $grid->column('priority', '优先级')->sortable();
+
+            // 健康状态
+            $grid->column('health_status', '健康状态')->display(function ($healthStatus) {
+                $colors = [
+                    'HEALTHY' => 'success',
+                    'WARNING' => 'warning',
+                    'ERROR' => 'danger',
+                    'UNKNOWN' => 'secondary',
+                ];
+                $color = $colors[$healthStatus] ?? 'secondary';
+                return "<span class='badge badge-{$color}'>{$healthStatus}</span>";
+            });
+
+            // 最后健康检查时间
+            $grid->column('last_health_check', '最后检查')->display(function ($time) {
+                return $time ? $time : '未检查';
+            });
+
+            // 创建时间
+            $grid->column('created_at', '创建时间')->sortable();
+
+            // 过滤器
+            $grid->filter(function (Grid\Filter $filter) {
+                $filter->equal('type', '服务类型')->select(SERVICE_TYPE::getOptions());
+                $filter->equal('status', '状态')->select(SERVICE_STATUS::getOptions());
+                $filter->equal('auth_type', '认证类型')->select(AUTH_TYPE::getOptions());
+                $filter->like('name', '服务名称');
+                $filter->like('code', '服务代码');
+                $filter->like('provider', '服务提供商');
+            });
+
+            // 批量操作
+            $grid->batchActions(function (Grid\Tools\BatchActions $batch) {
+                $batch->add('批量激活', new \App\Module\ThirdParty\AdminActions\BatchActivateServiceAction());
+                $batch->add('批量停用', new \App\Module\ThirdParty\AdminActions\BatchDeactivateServiceAction());
+            });
+
+            // 工具栏
+            $grid->tools(function (Grid\Tools $tools) {
+                $tools->append('<a href="/admin/thirdparty/services/health-check" class="btn btn-sm btn-success"><i class="fa fa-heartbeat"></i> 健康检查</a>');
+                $tools->append('<a href="/admin/thirdparty/services/stats" class="btn btn-sm btn-info"><i class="fa fa-chart-bar"></i> 统计报告</a>');
+            });
+
+            // 行操作
+            $grid->actions(function (Grid\Displayers\Actions $actions) {
+                $actions->append('<a href="/admin/thirdparty/credentials?service_id=' . $actions->getKey() . '" class="btn btn-xs btn-primary"><i class="fa fa-key"></i> 凭证</a>');
+                $actions->append('<a href="/admin/thirdparty/logs?service_id=' . $actions->getKey() . '" class="btn btn-xs btn-info"><i class="fa fa-list"></i> 日志</a>');
+                
+                if ($actions->row->status === SERVICE_STATUS::ACTIVE->value) {
+                    $actions->append('<a href="/admin/thirdparty/services/' . $actions->getKey() . '/test" class="btn btn-xs btn-warning"><i class="fa fa-flask"></i> 测试</a>');
+                }
+            });
+
+            // 默认排序
+            $grid->model()->orderBy('priority')->orderBy('name');
+        });
+    }
+
+    /**
+     * 详情页面
+     *
+     * @return Show
+     */
+    protected function detail($id): Show
+    {
+        return Show::make($id, new ThirdPartyServiceRepository(), function (Show $show) {
+            $show->field('id', 'ID');
+            $show->field('name', '服务名称');
+            $show->field('code', '服务代码');
+            
+            $show->field('type', '服务类型')->as(function ($type) {
+                $serviceType = SERVICE_TYPE::tryFrom($type);
+                return $serviceType ? $serviceType->getLabel() : $type;
+            });
+
+            $show->field('provider', '服务提供商');
+            $show->field('description', '服务描述');
+            $show->field('base_url', '基础URL');
+            $show->field('version', 'API版本');
+
+            $show->field('auth_type', '认证类型')->as(function ($authType) {
+                $auth = AUTH_TYPE::tryFrom($authType);
+                return $auth ? $auth->getLabel() : $authType;
+            });
+
+            $show->field('status', '状态')->as(function ($status) {
+                $serviceStatus = SERVICE_STATUS::tryFrom($status);
+                return $serviceStatus ? $serviceStatus->getLabel() : $status;
+            });
+
+            $show->field('priority', '优先级');
+            $show->field('timeout', '超时时间')->as(function ($timeout) {
+                return $timeout . ' 秒';
+            });
+
+            $show->field('retry_times', '重试次数');
+            $show->field('retry_delay', '重试延迟')->as(function ($delay) {
+                return $delay . ' 毫秒';
+            });
+
+            $show->field('config', '服务配置')->json();
+            $show->field('headers', '默认请求头')->json();
+            $show->field('params', '默认参数')->json();
+
+            $show->field('webhook_url', 'Webhook URL');
+            $show->field('health_check_url', '健康检查URL');
+            $show->field('health_check_interval', '健康检查间隔')->as(function ($interval) {
+                return $interval . ' 秒';
+            });
+
+            $show->field('health_status', '健康状态');
+            $show->field('last_health_check', '最后健康检查时间');
+
+            $show->field('created_at', '创建时间');
+            $show->field('updated_at', '更新时间');
+
+            // 关联信息
+            $show->relation('credentials', '认证凭证', function ($model) {
+                $grid = new Grid(new \App\Module\ThirdParty\Models\ThirdPartyCredential());
+                $grid->model()->where('service_id', $model->id);
+                $grid->column('name', '凭证名称');
+                $grid->column('type', '认证类型');
+                $grid->column('environment', '环境');
+                $grid->column('is_active', '状态')->display(function ($active) {
+                    return $active ? '<span class="badge badge-success">激活</span>' : '<span class="badge badge-secondary">未激活</span>';
+                });
+                $grid->column('expires_at', '过期时间');
+                $grid->disableCreateButton();
+                $grid->disableActions();
+                return $grid;
+            });
+        });
+    }
+
+    /**
+     * 表单页面
+     *
+     * @return Form
+     */
+    protected function form(): Form
+    {
+        return Form::make(new ThirdPartyServiceRepository(), function (Form $form) {
+            $form->display('id', 'ID');
+
+            $form->text('name', '服务名称')->required()->help('第三方服务的显示名称');
+            $form->text('code', '服务代码')->help('唯一标识符,留空自动生成');
+            
+            $form->select('type', '服务类型')->options(SERVICE_TYPE::getOptions())->required();
+            $form->text('provider', '服务提供商')->required();
+            $form->textarea('description', '服务描述');
+
+            $form->url('base_url', '基础URL')->help('第三方服务的API基础地址');
+            $form->text('version', 'API版本')->default('v1');
+
+            $form->select('auth_type', '认证类型')->options(AUTH_TYPE::getOptions())->required();
+            $form->select('status', '状态')->options(SERVICE_STATUS::getOptions())->default(SERVICE_STATUS::INACTIVE->value);
+
+            $form->number('priority', '优先级')->default(0)->help('数字越小优先级越高');
+            $form->number('timeout', '超时时间(秒)')->default(30)->min(1)->max(300);
+            $form->number('retry_times', '重试次数')->default(3)->min(0)->max(10);
+            $form->number('retry_delay', '重试延迟(毫秒)')->default(1000)->min(100)->max(60000);
+
+            $form->json('config', '服务配置')->help('JSON格式的服务特定配置');
+            $form->json('headers', '默认请求头')->help('JSON格式的默认HTTP请求头');
+            $form->json('params', '默认参数')->help('JSON格式的默认请求参数');
+
+            $form->url('webhook_url', 'Webhook URL')->help('接收第三方服务回调的地址');
+            $form->text('webhook_secret', 'Webhook密钥')->help('用于验证Webhook请求的密钥');
+
+            $form->url('health_check_url', '健康检查URL')->help('用于检查服务健康状态的URL');
+            $form->number('health_check_interval', '健康检查间隔(秒)')->default(300)->min(60)->max(86400);
+
+            $form->display('created_at', '创建时间');
+            $form->display('updated_at', '更新时间');
+
+            // 保存前处理
+            $form->saving(function (Form $form) {
+                // 如果没有提供代码,自动生成
+                if (empty($form->code)) {
+                    $form->code = ThirdPartyService::generateCode($form->name, $form->provider);
+                }
+            });
+        });
+    }
+}

+ 311 - 0
app/Module/ThirdParty/Commands/CleanupLogsCommand.php

@@ -0,0 +1,311 @@
+<?php
+
+namespace App\Module\ThirdParty\Commands;
+
+use Illuminate\Console\Command;
+use App\Module\ThirdParty\Models\ThirdPartyLog;
+use App\Module\ThirdParty\Models\ThirdPartyMonitor;
+
+/**
+ * 第三方服务日志清理命令
+ */
+class CleanupLogsCommand extends Command
+{
+    /**
+     * 命令签名
+     *
+     * @var string
+     */
+    protected $signature = 'thirdparty:cleanup-logs 
+                            {--days=30 : 保留天数}
+                            {--type=all : 清理类型 (logs|monitors|all)}
+                            {--service= : 指定服务ID}
+                            {--dry-run : 只显示将要删除的记录数,不实际执行}
+                            {--force : 强制执行,不询问确认}';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '清理第三方服务的旧日志和监控记录';
+
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle(): int
+    {
+        $days = (int)$this->option('days');
+        $type = $this->option('type');
+        $serviceId = $this->option('service');
+        $dryRun = $this->option('dry-run');
+        $force = $this->option('force');
+
+        if ($days < 1) {
+            $this->error('保留天数必须大于0');
+            return Command::FAILURE;
+        }
+
+        if (!in_array($type, ['logs', 'monitors', 'all'])) {
+            $this->error('清理类型必须是 logs、monitors 或 all');
+            return Command::FAILURE;
+        }
+
+        $this->info("开始清理 {$days} 天前的记录...");
+
+        try {
+            $cutoffDate = now()->subDays($days);
+            $results = [];
+
+            if ($type === 'logs' || $type === 'all') {
+                $results['logs'] = $this->cleanupLogs($cutoffDate, $serviceId, $dryRun);
+            }
+
+            if ($type === 'monitors' || $type === 'all') {
+                $results['monitors'] = $this->cleanupMonitors($cutoffDate, $serviceId, $dryRun);
+            }
+
+            $this->displayResults($results, $dryRun);
+
+            if ($dryRun) {
+                $this->info('这是预览模式,没有实际删除记录');
+                return Command::SUCCESS;
+            }
+
+            if (!$force && !$this->confirmDeletion($results)) {
+                $this->info('操作已取消');
+                return Command::SUCCESS;
+            }
+
+            // 实际执行删除
+            $actualResults = [];
+            if ($type === 'logs' || $type === 'all') {
+                $actualResults['logs'] = $this->cleanupLogs($cutoffDate, $serviceId, false);
+            }
+
+            if ($type === 'monitors' || $type === 'all') {
+                $actualResults['monitors'] = $this->cleanupMonitors($cutoffDate, $serviceId, false);
+            }
+
+            $this->displayResults($actualResults, false);
+            $this->info('清理完成');
+
+            return Command::SUCCESS;
+
+        } catch (\Exception $e) {
+            $this->error("清理失败: {$e->getMessage()}");
+            return Command::FAILURE;
+        }
+    }
+
+    /**
+     * 清理调用日志
+     *
+     * @param \Carbon\Carbon $cutoffDate
+     * @param int|null $serviceId
+     * @param bool $dryRun
+     * @return array
+     */
+    protected function cleanupLogs($cutoffDate, $serviceId, bool $dryRun): array
+    {
+        $query = ThirdPartyLog::where('created_at', '<', $cutoffDate);
+
+        if ($serviceId) {
+            $query->where('service_id', $serviceId);
+        }
+
+        $count = $query->count();
+
+        if (!$dryRun && $count > 0) {
+            $deleted = $query->delete();
+            return [
+                'type' => '调用日志',
+                'count' => $deleted,
+                'size' => $this->estimateLogSize($deleted),
+            ];
+        }
+
+        return [
+            'type' => '调用日志',
+            'count' => $count,
+            'size' => $this->estimateLogSize($count),
+        ];
+    }
+
+    /**
+     * 清理监控记录
+     *
+     * @param \Carbon\Carbon $cutoffDate
+     * @param int|null $serviceId
+     * @param bool $dryRun
+     * @return array
+     */
+    protected function cleanupMonitors($cutoffDate, $serviceId, bool $dryRun): array
+    {
+        $query = ThirdPartyMonitor::where('checked_at', '<', $cutoffDate);
+
+        if ($serviceId) {
+            $query->where('service_id', $serviceId);
+        }
+
+        $count = $query->count();
+
+        if (!$dryRun && $count > 0) {
+            $deleted = $query->delete();
+            return [
+                'type' => '监控记录',
+                'count' => $deleted,
+                'size' => $this->estimateMonitorSize($deleted),
+            ];
+        }
+
+        return [
+            'type' => '监控记录',
+            'count' => $count,
+            'size' => $this->estimateMonitorSize($count),
+        ];
+    }
+
+    /**
+     * 估算日志大小
+     *
+     * @param int $count
+     * @return string
+     */
+    protected function estimateLogSize(int $count): string
+    {
+        // 估算每条日志记录约2KB
+        $sizeInBytes = $count * 2048;
+        return $this->formatBytes($sizeInBytes);
+    }
+
+    /**
+     * 估算监控记录大小
+     *
+     * @param int $count
+     * @return string
+     */
+    protected function estimateMonitorSize(int $count): string
+    {
+        // 估算每条监控记录约512字节
+        $sizeInBytes = $count * 512;
+        return $this->formatBytes($sizeInBytes);
+    }
+
+    /**
+     * 格式化字节大小
+     *
+     * @param int $bytes
+     * @return string
+     */
+    protected function formatBytes(int $bytes): string
+    {
+        $units = ['B', 'KB', 'MB', 'GB'];
+        $unitIndex = 0;
+
+        while ($bytes >= 1024 && $unitIndex < count($units) - 1) {
+            $bytes /= 1024;
+            $unitIndex++;
+        }
+
+        return round($bytes, 2) . ' ' . $units[$unitIndex];
+    }
+
+    /**
+     * 显示清理结果
+     *
+     * @param array $results
+     * @param bool $dryRun
+     * @return void
+     */
+    protected function displayResults(array $results, bool $dryRun): void
+    {
+        if (empty($results)) {
+            return;
+        }
+
+        $action = $dryRun ? '将要删除' : '已删除';
+        $this->info("\n清理结果:");
+        $this->line(str_repeat('-', 60));
+
+        $totalCount = 0;
+        $totalSize = 0;
+
+        foreach ($results as $result) {
+            $this->line("{$action} {$result['type']}: {$result['count']} 条记录 ({$result['size']})");
+            $totalCount += $result['count'];
+            
+            // 简单累加字节数(这里简化处理)
+            $sizeInBytes = $this->parseSizeToBytes($result['size']);
+            $totalSize += $sizeInBytes;
+        }
+
+        $this->line(str_repeat('-', 60));
+        $this->line("总计: {$totalCount} 条记录 ({$this->formatBytes($totalSize)})");
+
+        if ($totalCount === 0) {
+            $this->info('没有找到需要清理的记录');
+        }
+    }
+
+    /**
+     * 解析大小字符串为字节数
+     *
+     * @param string $size
+     * @return int
+     */
+    protected function parseSizeToBytes(string $size): int
+    {
+        $units = ['B' => 1, 'KB' => 1024, 'MB' => 1024*1024, 'GB' => 1024*1024*1024];
+        
+        foreach ($units as $unit => $multiplier) {
+            if (str_contains($size, $unit)) {
+                $value = (float)str_replace(' ' . $unit, '', $size);
+                return (int)($value * $multiplier);
+            }
+        }
+        
+        return 0;
+    }
+
+    /**
+     * 确认删除操作
+     *
+     * @param array $results
+     * @return bool
+     */
+    protected function confirmDeletion(array $results): bool
+    {
+        $totalCount = array_sum(array_column($results, 'count'));
+        
+        if ($totalCount === 0) {
+            return false;
+        }
+
+        return $this->confirm("确认要删除 {$totalCount} 条记录吗?此操作不可恢复!");
+    }
+
+    /**
+     * 显示清理统计信息
+     *
+     * @return void
+     */
+    protected function displayCleanupStats(): void
+    {
+        $logCount = ThirdPartyLog::count();
+        $monitorCount = ThirdPartyMonitor::count();
+        
+        $oldLogCount = ThirdPartyLog::where('created_at', '<', now()->subDays(30))->count();
+        $oldMonitorCount = ThirdPartyMonitor::where('checked_at', '<', now()->subDays(30))->count();
+
+        $this->info("\n当前统计:");
+        $this->line("调用日志总数: {$logCount} (30天前: {$oldLogCount})");
+        $this->line("监控记录总数: {$monitorCount} (30天前: {$oldMonitorCount})");
+
+        if ($oldLogCount > 0 || $oldMonitorCount > 0) {
+            $this->warn("建议定期清理旧记录以节省存储空间");
+        }
+    }
+}

+ 226 - 0
app/Module/ThirdParty/Commands/HealthCheckCommand.php

@@ -0,0 +1,226 @@
+<?php
+
+namespace App\Module\ThirdParty\Commands;
+
+use Illuminate\Console\Command;
+use App\Module\ThirdParty\Services\MonitorService;
+use App\Module\ThirdParty\Models\ThirdPartyService;
+
+/**
+ * 第三方服务健康检查命令
+ */
+class HealthCheckCommand extends Command
+{
+    /**
+     * 命令签名
+     *
+     * @var string
+     */
+    protected $signature = 'thirdparty:health-check 
+                            {--service= : 指定服务ID或代码}
+                            {--all : 检查所有服务}
+                            {--active-only : 只检查激活的服务}
+                            {--verbose : 显示详细信息}';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '执行第三方服务健康检查';
+
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle(): int
+    {
+        $this->info('开始执行第三方服务健康检查...');
+
+        try {
+            $serviceId = $this->getServiceId();
+            $results = MonitorService::performHealthCheck($serviceId);
+
+            $this->displayResults($results);
+            $this->displaySummary($results);
+
+            return Command::SUCCESS;
+
+        } catch (\Exception $e) {
+            $this->error("健康检查失败: {$e->getMessage()}");
+            return Command::FAILURE;
+        }
+    }
+
+    /**
+     * 获取要检查的服务ID
+     *
+     * @return int|null
+     */
+    protected function getServiceId(): ?int
+    {
+        $serviceOption = $this->option('service');
+        
+        if (!$serviceOption) {
+            return null;
+        }
+
+        // 如果是数字,直接返回
+        if (is_numeric($serviceOption)) {
+            $service = ThirdPartyService::find($serviceOption);
+            if (!$service) {
+                throw new \Exception("服务ID {$serviceOption} 不存在");
+            }
+            return (int)$serviceOption;
+        }
+
+        // 如果是代码,查找对应的服务
+        $service = ThirdPartyService::where('code', $serviceOption)->first();
+        if (!$service) {
+            throw new \Exception("服务代码 {$serviceOption} 不存在");
+        }
+
+        return $service->id;
+    }
+
+    /**
+     * 显示检查结果
+     *
+     * @param array $results
+     * @return void
+     */
+    protected function displayResults(array $results): void
+    {
+        if (empty($results)) {
+            $this->warn('没有找到需要检查的服务');
+            return;
+        }
+
+        $this->info("\n检查结果:");
+        $this->line(str_repeat('-', 80));
+
+        foreach ($results as $result) {
+            $this->displaySingleResult($result);
+        }
+    }
+
+    /**
+     * 显示单个服务的检查结果
+     *
+     * @param array $result
+     * @return void
+     */
+    protected function displaySingleResult(array $result): void
+    {
+        $status = $result['status'];
+        $serviceName = $result['service_name'];
+        $serviceCode = $result['service_code'];
+
+        // 根据状态选择颜色
+        $color = match ($status) {
+            'success' => 'green',
+            'warning' => 'yellow',
+            'error', 'timeout' => 'red',
+            default => 'white',
+        };
+
+        $this->line("<fg={$color}>[{$status}]</fg> {$serviceName} ({$serviceCode})");
+
+        if ($this->option('verbose')) {
+            if ($result['response_time']) {
+                $this->line("  响应时间: {$result['response_time']}ms");
+            }
+
+            if ($result['status_code']) {
+                $this->line("  HTTP状态码: {$result['status_code']}");
+            }
+
+            if ($result['error_message']) {
+                $this->line("  错误信息: {$result['error_message']}");
+            }
+
+            if (!empty($result['details'])) {
+                $this->line("  详细信息:");
+                foreach ($result['details'] as $key => $value) {
+                    if (is_array($value)) {
+                        $value = json_encode($value, JSON_UNESCAPED_UNICODE);
+                    }
+                    $this->line("    {$key}: {$value}");
+                }
+            }
+        } else {
+            if ($result['error_message']) {
+                $this->line("  {$result['error_message']}");
+            }
+        }
+
+        $this->line('');
+    }
+
+    /**
+     * 显示检查摘要
+     *
+     * @param array $results
+     * @return void
+     */
+    protected function displaySummary(array $results): void
+    {
+        if (empty($results)) {
+            return;
+        }
+
+        $total = count($results);
+        $successful = count(array_filter($results, fn($r) => $r['status'] === 'success'));
+        $warnings = count(array_filter($results, fn($r) => $r['status'] === 'warning'));
+        $errors = count(array_filter($results, fn($r) => in_array($r['status'], ['error', 'timeout'])));
+
+        $this->line(str_repeat('-', 80));
+        $this->info('检查摘要:');
+        $this->line("总计: {$total}");
+        $this->line("<fg=green>成功: {$successful}</fg>");
+        
+        if ($warnings > 0) {
+            $this->line("<fg=yellow>警告: {$warnings}</fg>");
+        }
+        
+        if ($errors > 0) {
+            $this->line("<fg=red>错误: {$errors}</fg>");
+        }
+
+        $successRate = $total > 0 ? round(($successful / $total) * 100, 2) : 0;
+        $this->line("成功率: {$successRate}%");
+
+        // 如果有失败的服务,给出建议
+        if ($errors > 0) {
+            $this->line('');
+            $this->warn('建议:');
+            $this->line('- 检查失败服务的配置和凭证');
+            $this->line('- 确认第三方服务是否正常运行');
+            $this->line('- 查看详细日志了解具体错误原因');
+        }
+    }
+
+    /**
+     * 获取要检查的服务列表
+     *
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    protected function getServicesToCheck()
+    {
+        $query = ThirdPartyService::query();
+
+        if ($this->option('active-only')) {
+            $query->where('status', 'ACTIVE');
+        }
+
+        if ($this->option('all')) {
+            return $query->get();
+        }
+
+        // 默认只检查有健康检查URL的激活服务
+        return $query->where('status', 'ACTIVE')
+            ->whereNotNull('health_check_url')
+            ->get();
+    }
+}

+ 227 - 0
app/Module/ThirdParty/Commands/QuotaResetCommand.php

@@ -0,0 +1,227 @@
+<?php
+
+namespace App\Module\ThirdParty\Commands;
+
+use Illuminate\Console\Command;
+use App\Module\ThirdParty\Models\ThirdPartyQuota;
+use App\Module\ThirdParty\Enums\QUOTA_TYPE;
+
+/**
+ * 第三方服务配额重置命令
+ */
+class QuotaResetCommand extends Command
+{
+    /**
+     * 命令签名
+     *
+     * @var string
+     */
+    protected $signature = 'thirdparty:quota-reset 
+                            {--service= : 指定服务ID}
+                            {--type= : 指定配额类型}
+                            {--force : 强制重置所有配额}
+                            {--dry-run : 只显示将要重置的配额,不实际执行}';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '重置第三方服务配额';
+
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle(): int
+    {
+        $this->info('开始检查和重置第三方服务配额...');
+
+        try {
+            $quotas = $this->getQuotasToReset();
+            
+            if ($quotas->isEmpty()) {
+                $this->info('没有找到需要重置的配额');
+                return Command::SUCCESS;
+            }
+
+            $this->displayQuotasToReset($quotas);
+
+            if ($this->option('dry-run')) {
+                $this->info('这是预览模式,没有实际重置配额');
+                return Command::SUCCESS;
+            }
+
+            if (!$this->option('force') && !$this->confirm('确认要重置这些配额吗?')) {
+                $this->info('操作已取消');
+                return Command::SUCCESS;
+            }
+
+            $resetCount = $this->resetQuotas($quotas);
+            
+            $this->info("成功重置了 {$resetCount} 个配额");
+            return Command::SUCCESS;
+
+        } catch (\Exception $e) {
+            $this->error("配额重置失败: {$e->getMessage()}");
+            return Command::FAILURE;
+        }
+    }
+
+    /**
+     * 获取需要重置的配额
+     *
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    protected function getQuotasToReset()
+    {
+        $query = ThirdPartyQuota::with('service')->where('is_active', true);
+
+        // 按服务过滤
+        if ($serviceId = $this->option('service')) {
+            $query->where('service_id', $serviceId);
+        }
+
+        // 按类型过滤
+        if ($type = $this->option('type')) {
+            if (!QUOTA_TYPE::tryFrom($type)) {
+                throw new \Exception("无效的配额类型: {$type}");
+            }
+            $query->where('type', $type);
+        }
+
+        // 如果强制重置,返回所有匹配的配额
+        if ($this->option('force')) {
+            return $query->get();
+        }
+
+        // 否则只返回需要重置的配额
+        return $query->get()->filter(function ($quota) {
+            return $quota->needsReset();
+        });
+    }
+
+    /**
+     * 显示将要重置的配额
+     *
+     * @param \Illuminate\Database\Eloquent\Collection $quotas
+     * @return void
+     */
+    protected function displayQuotasToReset($quotas): void
+    {
+        $this->info("\n将要重置的配额:");
+        $this->line(str_repeat('-', 100));
+
+        $headers = ['服务', '配额类型', '限制值', '已使用', '使用率', '状态', '重置原因'];
+        $rows = [];
+
+        foreach ($quotas as $quota) {
+            $usagePercentage = round($quota->getUsagePercentage(), 2) . '%';
+            $status = $quota->getStatusLabel();
+            
+            $reason = '';
+            if ($this->option('force')) {
+                $reason = '强制重置';
+            } elseif ($quota->needsReset()) {
+                if ($quota->window_end && now()->gt($quota->window_end)) {
+                    $reason = '时间窗口已过期';
+                } elseif ($quota->reset_at && now()->gte($quota->reset_at)) {
+                    $reason = '到达重置时间';
+                } else {
+                    $reason = '需要重置';
+                }
+            }
+
+            $rows[] = [
+                $quota->service->name,
+                $quota->getTypeLabel(),
+                number_format($quota->limit_value),
+                number_format($quota->used_value),
+                $usagePercentage,
+                $status,
+                $reason,
+            ];
+        }
+
+        $this->table($headers, $rows);
+    }
+
+    /**
+     * 重置配额
+     *
+     * @param \Illuminate\Database\Eloquent\Collection $quotas
+     * @return int
+     */
+    protected function resetQuotas($quotas): int
+    {
+        $resetCount = 0;
+        $progressBar = $this->output->createProgressBar($quotas->count());
+        $progressBar->start();
+
+        foreach ($quotas as $quota) {
+            try {
+                $quota->resetQuota();
+                $resetCount++;
+                
+                $this->line("\n重置配额: {$quota->service->name} - {$quota->getTypeLabel()}");
+                
+            } catch (\Exception $e) {
+                $this->line("\n<fg=red>重置失败: {$quota->service->name} - {$quota->getTypeLabel()}: {$e->getMessage()}</fg>");
+            }
+            
+            $progressBar->advance();
+        }
+
+        $progressBar->finish();
+        $this->line('');
+
+        return $resetCount;
+    }
+
+    /**
+     * 显示配额统计信息
+     *
+     * @return void
+     */
+    protected function displayQuotaStats(): void
+    {
+        $totalQuotas = ThirdPartyQuota::where('is_active', true)->count();
+        $exceededQuotas = ThirdPartyQuota::where('is_active', true)
+            ->where('is_exceeded', true)
+            ->count();
+        $nearThresholdQuotas = ThirdPartyQuota::where('is_active', true)
+            ->whereRaw('(used_value / limit_value * 100) >= alert_threshold')
+            ->count();
+
+        $this->info("\n配额统计:");
+        $this->line("总配额数: {$totalQuotas}");
+        $this->line("已超限配额: {$exceededQuotas}");
+        $this->line("接近阈值配额: {$nearThresholdQuotas}");
+
+        if ($exceededQuotas > 0) {
+            $this->warn("\n警告: 有 {$exceededQuotas} 个配额已超限,可能影响服务调用");
+        }
+    }
+
+    /**
+     * 验证配额类型
+     *
+     * @param string $type
+     * @return bool
+     */
+    protected function validateQuotaType(string $type): bool
+    {
+        return QUOTA_TYPE::tryFrom($type) !== null;
+    }
+
+    /**
+     * 获取所有可用的配额类型
+     *
+     * @return array
+     */
+    protected function getAvailableQuotaTypes(): array
+    {
+        return array_map(fn($case) => $case->value, QUOTA_TYPE::cases());
+    }
+}

+ 346 - 0
app/Module/ThirdParty/Commands/SyncServicesCommand.php

@@ -0,0 +1,346 @@
+<?php
+
+namespace App\Module\ThirdParty\Commands;
+
+use Illuminate\Console\Command;
+use App\Module\ThirdParty\Models\ThirdPartyService;
+use App\Module\ThirdParty\Enums\SERVICE_TYPE;
+use App\Module\ThirdParty\Enums\AUTH_TYPE;
+
+/**
+ * 第三方服务同步命令
+ */
+class SyncServicesCommand extends Command
+{
+    /**
+     * 命令签名
+     *
+     * @var string
+     */
+    protected $signature = 'thirdparty:sync-services 
+                            {--config= : 配置文件路径}
+                            {--dry-run : 只显示将要同步的服务,不实际执行}
+                            {--force : 强制覆盖现有服务}';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '从配置文件同步第三方服务';
+
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle(): int
+    {
+        $this->info('开始同步第三方服务...');
+
+        try {
+            $services = $this->loadServicesConfig();
+            
+            if (empty($services)) {
+                $this->warn('没有找到需要同步的服务配置');
+                return Command::SUCCESS;
+            }
+
+            $this->displayServicesToSync($services);
+
+            if ($this->option('dry-run')) {
+                $this->info('这是预览模式,没有实际同步服务');
+                return Command::SUCCESS;
+            }
+
+            if (!$this->option('force') && !$this->confirm('确认要同步这些服务吗?')) {
+                $this->info('操作已取消');
+                return Command::SUCCESS;
+            }
+
+            $results = $this->syncServices($services);
+            $this->displayResults($results);
+
+            return Command::SUCCESS;
+
+        } catch (\Exception $e) {
+            $this->error("同步失败: {$e->getMessage()}");
+            return Command::FAILURE;
+        }
+    }
+
+    /**
+     * 加载服务配置
+     *
+     * @return array
+     */
+    protected function loadServicesConfig(): array
+    {
+        $configPath = $this->option('config');
+        
+        if ($configPath) {
+            if (!file_exists($configPath)) {
+                throw new \Exception("配置文件不存在: {$configPath}");
+            }
+            
+            $config = json_decode(file_get_contents($configPath), true);
+            if (json_last_error() !== JSON_ERROR_NONE) {
+                throw new \Exception("配置文件格式错误: " . json_last_error_msg());
+            }
+            
+            return $config['services'] ?? [];
+        }
+
+        // 使用默认配置
+        return $this->getDefaultServicesConfig();
+    }
+
+    /**
+     * 获取默认服务配置
+     *
+     * @return array
+     */
+    protected function getDefaultServicesConfig(): array
+    {
+        return [
+            [
+                'name' => '阿里云短信服务',
+                'code' => 'aliyun_sms',
+                'type' => 'SMS',
+                'provider' => 'ALIYUN',
+                'description' => '阿里云短信发送服务',
+                'base_url' => 'https://dysmsapi.aliyuncs.com',
+                'auth_type' => 'API_KEY',
+                'status' => 'INACTIVE',
+            ],
+            [
+                'name' => '腾讯云短信服务',
+                'code' => 'tencent_sms',
+                'type' => 'SMS',
+                'provider' => 'TENCENT',
+                'description' => '腾讯云短信发送服务',
+                'base_url' => 'https://sms.tencentcloudapi.com',
+                'auth_type' => 'SIGNATURE',
+                'status' => 'INACTIVE',
+            ],
+            [
+                'name' => '支付宝支付',
+                'code' => 'alipay',
+                'type' => 'PAYMENT',
+                'provider' => 'ALIPAY',
+                'description' => '支付宝开放平台支付服务',
+                'base_url' => 'https://openapi.alipay.com/gateway.do',
+                'auth_type' => 'SIGNATURE',
+                'status' => 'INACTIVE',
+            ],
+            [
+                'name' => '微信支付',
+                'code' => 'wechat_pay',
+                'type' => 'PAYMENT',
+                'provider' => 'WECHAT',
+                'description' => '微信支付API服务',
+                'base_url' => 'https://api.mch.weixin.qq.com',
+                'auth_type' => 'SIGNATURE',
+                'status' => 'INACTIVE',
+            ],
+        ];
+    }
+
+    /**
+     * 显示将要同步的服务
+     *
+     * @param array $services
+     * @return void
+     */
+    protected function displayServicesToSync(array $services): void
+    {
+        $this->info("\n将要同步的服务:");
+        $this->line(str_repeat('-', 80));
+
+        $headers = ['名称', '代码', '类型', '提供商', '状态'];
+        $rows = [];
+
+        foreach ($services as $service) {
+            $rows[] = [
+                $service['name'],
+                $service['code'],
+                $service['type'],
+                $service['provider'],
+                $service['status'] ?? 'INACTIVE',
+            ];
+        }
+
+        $this->table($headers, $rows);
+    }
+
+    /**
+     * 同步服务
+     *
+     * @param array $services
+     * @return array
+     */
+    protected function syncServices(array $services): array
+    {
+        $results = [
+            'created' => [],
+            'updated' => [],
+            'skipped' => [],
+            'failed' => [],
+        ];
+
+        $progressBar = $this->output->createProgressBar(count($services));
+        $progressBar->start();
+
+        foreach ($services as $serviceConfig) {
+            try {
+                $result = $this->syncSingleService($serviceConfig);
+                $results[$result['action']][] = $result;
+                
+                $this->line("\n{$result['action']}: {$serviceConfig['name']}");
+                
+            } catch (\Exception $e) {
+                $results['failed'][] = [
+                    'service' => $serviceConfig,
+                    'error' => $e->getMessage(),
+                ];
+                $this->line("\n<fg=red>失败: {$serviceConfig['name']}: {$e->getMessage()}</fg>");
+            }
+            
+            $progressBar->advance();
+        }
+
+        $progressBar->finish();
+        $this->line('');
+
+        return $results;
+    }
+
+    /**
+     * 同步单个服务
+     *
+     * @param array $serviceConfig
+     * @return array
+     */
+    protected function syncSingleService(array $serviceConfig): array
+    {
+        // 验证配置
+        $this->validateServiceConfig($serviceConfig);
+
+        $existingService = ThirdPartyService::where('code', $serviceConfig['code'])->first();
+
+        if ($existingService) {
+            if ($this->option('force')) {
+                // 更新现有服务
+                $existingService->update($serviceConfig);
+                return [
+                    'action' => 'updated',
+                    'service' => $existingService,
+                ];
+            } else {
+                // 跳过现有服务
+                return [
+                    'action' => 'skipped',
+                    'service' => $existingService,
+                    'reason' => '服务已存在',
+                ];
+            }
+        } else {
+            // 创建新服务
+            $service = ThirdPartyService::create($serviceConfig);
+            return [
+                'action' => 'created',
+                'service' => $service,
+            ];
+        }
+    }
+
+    /**
+     * 验证服务配置
+     *
+     * @param array $config
+     * @return void
+     * @throws \Exception
+     */
+    protected function validateServiceConfig(array $config): void
+    {
+        $required = ['name', 'code', 'type', 'provider', 'auth_type'];
+        
+        foreach ($required as $field) {
+            if (empty($config[$field])) {
+                throw new \Exception("服务配置缺少必填字段: {$field}");
+            }
+        }
+
+        // 验证服务类型
+        if (!SERVICE_TYPE::tryFrom($config['type'])) {
+            throw new \Exception("无效的服务类型: {$config['type']}");
+        }
+
+        // 验证认证类型
+        if (!AUTH_TYPE::tryFrom($config['auth_type'])) {
+            throw new \Exception("无效的认证类型: {$config['auth_type']}");
+        }
+
+        // 验证URL格式
+        if (isset($config['base_url']) && !filter_var($config['base_url'], FILTER_VALIDATE_URL)) {
+            throw new \Exception("无效的基础URL: {$config['base_url']}");
+        }
+    }
+
+    /**
+     * 显示同步结果
+     *
+     * @param array $results
+     * @return void
+     */
+    protected function displayResults(array $results): void
+    {
+        $this->line(str_repeat('-', 80));
+        $this->info('同步结果:');
+
+        $created = count($results['created']);
+        $updated = count($results['updated']);
+        $skipped = count($results['skipped']);
+        $failed = count($results['failed']);
+
+        $this->line("<fg=green>创建: {$created}</fg>");
+        $this->line("<fg=blue>更新: {$updated}</fg>");
+        $this->line("<fg=yellow>跳过: {$skipped}</fg>");
+        $this->line("<fg=red>失败: {$failed}</fg>");
+
+        // 显示失败的详情
+        if ($failed > 0) {
+            $this->line('');
+            $this->error('失败的服务:');
+            foreach ($results['failed'] as $failure) {
+                $this->line("- {$failure['service']['name']}: {$failure['error']}");
+            }
+        }
+
+        // 显示跳过的详情
+        if ($skipped > 0 && !$this->option('force')) {
+            $this->line('');
+            $this->warn('跳过的服务 (使用 --force 强制更新):');
+            foreach ($results['skipped'] as $skipped) {
+                $this->line("- {$skipped['service']['name']}: {$skipped['reason']}");
+            }
+        }
+    }
+
+    /**
+     * 生成示例配置文件
+     *
+     * @return void
+     */
+    protected function generateExampleConfig(): void
+    {
+        $config = [
+            'services' => $this->getDefaultServicesConfig()
+        ];
+
+        $configPath = storage_path('app/thirdparty_services_example.json');
+        file_put_contents($configPath, json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
+
+        $this->info("示例配置文件已生成: {$configPath}");
+    }
+}

+ 355 - 0
app/Module/ThirdParty/Commands/TestServiceCommand.php

@@ -0,0 +1,355 @@
+<?php
+
+namespace App\Module\ThirdParty\Commands;
+
+use Illuminate\Console\Command;
+use App\Module\ThirdParty\Services\ThirdPartyService;
+use App\Module\ThirdParty\Models\ThirdPartyService as ServiceModel;
+
+/**
+ * 第三方服务测试命令
+ */
+class TestServiceCommand extends Command
+{
+    /**
+     * 命令签名
+     *
+     * @var string
+     */
+    protected $signature = 'thirdparty:test-service 
+                            {service : 服务ID或代码}
+                            {--endpoint= : 测试端点}
+                            {--method=GET : HTTP方法}
+                            {--data= : 测试数据(JSON格式)}
+                            {--environment=production : 环境}
+                            {--timeout=30 : 超时时间}';
+
+    /**
+     * 命令描述
+     *
+     * @var string
+     */
+    protected $description = '测试第三方服务连接和API调用';
+
+    /**
+     * 执行命令
+     *
+     * @return int
+     */
+    public function handle(): int
+    {
+        $serviceIdentifier = $this->argument('service');
+        $endpoint = $this->option('endpoint') ?? '';
+        $method = strtoupper($this->option('method'));
+        $environment = $this->option('environment');
+        $timeout = (int)$this->option('timeout');
+
+        $this->info("开始测试第三方服务: {$serviceIdentifier}");
+
+        try {
+            // 获取服务
+            $service = $this->getService($serviceIdentifier);
+            $this->displayServiceInfo($service);
+
+            // 解析测试数据
+            $data = $this->parseTestData();
+
+            // 执行测试
+            $result = $this->performTest($service, $endpoint, $method, $data, $environment, $timeout);
+            
+            $this->displayTestResult($result);
+
+            return $result['success'] ? Command::SUCCESS : Command::FAILURE;
+
+        } catch (\Exception $e) {
+            $this->error("测试失败: {$e->getMessage()}");
+            return Command::FAILURE;
+        }
+    }
+
+    /**
+     * 获取服务
+     *
+     * @param string $identifier
+     * @return ServiceModel
+     * @throws \Exception
+     */
+    protected function getService(string $identifier): ServiceModel
+    {
+        // 尝试按ID查找
+        if (is_numeric($identifier)) {
+            $service = ServiceModel::find($identifier);
+            if ($service) {
+                return $service;
+            }
+        }
+
+        // 按代码查找
+        $service = ServiceModel::where('code', $identifier)->first();
+        if ($service) {
+            return $service;
+        }
+
+        throw new \Exception("服务不存在: {$identifier}");
+    }
+
+    /**
+     * 显示服务信息
+     *
+     * @param ServiceModel $service
+     * @return void
+     */
+    protected function displayServiceInfo(ServiceModel $service): void
+    {
+        $this->line('');
+        $this->info('服务信息:');
+        $this->line(str_repeat('-', 50));
+        $this->line("名称: {$service->name}");
+        $this->line("代码: {$service->code}");
+        $this->line("类型: {$service->getTypeLabel()}");
+        $this->line("提供商: {$service->provider}");
+        $this->line("状态: {$service->getStatusLabel()}");
+        $this->line("认证类型: {$service->getAuthTypeLabel()}");
+        $this->line("基础URL: {$service->base_url}");
+        $this->line('');
+    }
+
+    /**
+     * 解析测试数据
+     *
+     * @return array
+     * @throws \Exception
+     */
+    protected function parseTestData(): array
+    {
+        $dataOption = $this->option('data');
+        
+        if (!$dataOption) {
+            return [];
+        }
+
+        $data = json_decode($dataOption, true);
+        if (json_last_error() !== JSON_ERROR_NONE) {
+            throw new \Exception("测试数据格式错误: " . json_last_error_msg());
+        }
+
+        return $data;
+    }
+
+    /**
+     * 执行测试
+     *
+     * @param ServiceModel $service
+     * @param string $endpoint
+     * @param string $method
+     * @param array $data
+     * @param string $environment
+     * @param int $timeout
+     * @return array
+     */
+    protected function performTest(
+        ServiceModel $service,
+        string $endpoint,
+        string $method,
+        array $data,
+        string $environment,
+        int $timeout
+    ): array {
+        $result = [
+            'success' => false,
+            'message' => '',
+            'details' => [],
+        ];
+
+        try {
+            // 检查服务状态
+            if (!$service->isAvailable()) {
+                $result['message'] = "服务不可用,状态: {$service->getStatusLabel()}";
+                return $result;
+            }
+
+            // 检查凭证
+            $credential = $service->getActiveCredential($environment);
+            if (!$credential) {
+                $result['message'] = "环境 {$environment} 没有可用的认证凭证";
+                return $result;
+            }
+
+            if ($credential->isExpired()) {
+                $result['message'] = '认证凭证已过期';
+                return $result;
+            }
+
+            $this->line("使用凭证: {$credential->name} ({$environment})");
+
+            // 如果没有指定端点,只测试凭证
+            if (empty($endpoint)) {
+                $result = $this->testCredential($credential);
+                return $result;
+            }
+
+            // 执行API调用测试
+            $result = $this->testApiCall($service, $endpoint, $method, $data, $environment, $timeout);
+
+        } catch (\Exception $e) {
+            $result['message'] = $e->getMessage();
+            $result['details']['exception'] = get_class($e);
+        }
+
+        return $result;
+    }
+
+    /**
+     * 测试凭证
+     *
+     * @param \App\Module\ThirdParty\Models\ThirdPartyCredential $credential
+     * @return array
+     */
+    protected function testCredential($credential): array
+    {
+        $this->line('测试凭证配置...');
+
+        // 验证凭证配置
+        $validation = $credential->validateCredentials();
+        if (!$validation['valid']) {
+            return [
+                'success' => false,
+                'message' => '凭证配置不完整: ' . implode(', ', $validation['missing_fields']),
+                'details' => $validation,
+            ];
+        }
+
+        // 生成认证头
+        try {
+            $headers = $credential->generateAuthHeaders();
+            
+            return [
+                'success' => true,
+                'message' => '凭证测试成功',
+                'details' => [
+                    'auth_type' => $credential->getTypeLabel(),
+                    'headers_generated' => !empty($headers),
+                    'expires_at' => $credential->expires_at?->toDateTimeString(),
+                ],
+            ];
+        } catch (\Exception $e) {
+            return [
+                'success' => false,
+                'message' => '凭证测试失败: ' . $e->getMessage(),
+                'details' => [],
+            ];
+        }
+    }
+
+    /**
+     * 测试API调用
+     *
+     * @param ServiceModel $service
+     * @param string $endpoint
+     * @param string $method
+     * @param array $data
+     * @param string $environment
+     * @param int $timeout
+     * @return array
+     */
+    protected function testApiCall(
+        ServiceModel $service,
+        string $endpoint,
+        string $method,
+        array $data,
+        string $environment,
+        int $timeout
+    ): array {
+        $this->line("测试API调用: {$method} {$endpoint}");
+
+        try {
+            $options = [
+                'environment' => $environment,
+                'timeout' => $timeout,
+            ];
+
+            $result = ThirdPartyService::callApi(
+                $service->code,
+                $endpoint,
+                $data,
+                $method,
+                $options
+            );
+
+            return [
+                'success' => $result['success'],
+                'message' => $result['success'] ? 'API调用成功' : 'API调用失败',
+                'details' => [
+                    'status_code' => $result['status_code'],
+                    'response_time' => $result['response_time'] . 'ms',
+                    'request_id' => $result['request_id'],
+                    'data' => $result['data'],
+                ],
+            ];
+
+        } catch (\Exception $e) {
+            return [
+                'success' => false,
+                'message' => 'API调用失败: ' . $e->getMessage(),
+                'details' => [],
+            ];
+        }
+    }
+
+    /**
+     * 显示测试结果
+     *
+     * @param array $result
+     * @return void
+     */
+    protected function displayTestResult(array $result): void
+    {
+        $this->line('');
+        $this->line(str_repeat('-', 50));
+        
+        if ($result['success']) {
+            $this->info('✓ 测试成功');
+        } else {
+            $this->error('✗ 测试失败');
+        }
+
+        $this->line("结果: {$result['message']}");
+
+        if (!empty($result['details'])) {
+            $this->line('');
+            $this->line('详细信息:');
+            foreach ($result['details'] as $key => $value) {
+                if (is_array($value)) {
+                    $value = json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+                }
+                $this->line("  {$key}: {$value}");
+            }
+        }
+
+        $this->line('');
+    }
+
+    /**
+     * 显示使用示例
+     *
+     * @return void
+     */
+    protected function showExamples(): void
+    {
+        $this->line('');
+        $this->info('使用示例:');
+        $this->line('');
+        $this->line('# 测试服务凭证');
+        $this->line('php artisan thirdparty:test-service aliyun_sms');
+        $this->line('');
+        $this->line('# 测试API调用');
+        $this->line('php artisan thirdparty:test-service aliyun_sms --endpoint=/api/test --method=POST');
+        $this->line('');
+        $this->line('# 带测试数据');
+        $this->line('php artisan thirdparty:test-service alipay --endpoint=/api/pay --data=\'{"amount":100}\'');
+        $this->line('');
+        $this->line('# 指定环境');
+        $this->line('php artisan thirdparty:test-service wechat_pay --environment=staging');
+        $this->line('');
+    }
+}

+ 434 - 0
app/Module/ThirdParty/Logics/CredentialLogic.php

@@ -0,0 +1,434 @@
+<?php
+
+namespace App\Module\ThirdParty\Logics;
+
+use App\Module\ThirdParty\Models\ThirdPartyService;
+use App\Module\ThirdParty\Models\ThirdPartyCredential;
+use App\Module\ThirdParty\Enums\AUTH_TYPE;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Crypt;
+
+/**
+ * 第三方服务认证凭证业务逻辑类
+ */
+class CredentialLogic
+{
+    /**
+     * 创建新的认证凭证
+     *
+     * @param array $data
+     * @return ThirdPartyCredential
+     * @throws \Exception
+     */
+    public static function createCredential(array $data): ThirdPartyCredential
+    {
+        // 验证服务是否存在
+        $service = ThirdPartyService::find($data['service_id']);
+        if (!$service) {
+            throw new \Exception("服务不存在: {$data['service_id']}");
+        }
+
+        // 验证认证类型
+        $authType = AUTH_TYPE::tryFrom($data['type']);
+        if (!$authType) {
+            throw new \Exception("不支持的认证类型: {$data['type']}");
+        }
+
+        // 验证认证类型与服务匹配
+        if ($data['type'] !== $service->auth_type) {
+            throw new \Exception("认证类型 {$data['type']} 与服务要求的 {$service->auth_type} 不匹配");
+        }
+
+        // 验证凭证配置完整性
+        $validation = static::validateCredentialData($authType, $data['credentials'] ?? []);
+        if (!$validation['valid']) {
+            throw new \Exception("凭证配置不完整,缺少字段: " . implode(', ', $validation['missing_fields']));
+        }
+
+        // 检查同环境下是否已有激活的凭证
+        $environment = $data['environment'] ?? 'production';
+        $existingActive = ThirdPartyCredential::where('service_id', $data['service_id'])
+            ->where('environment', $environment)
+            ->where('is_active', true)
+            ->exists();
+
+        if ($existingActive && ($data['is_active'] ?? true)) {
+            throw new \Exception("环境 {$environment} 下已有激活的凭证,请先停用现有凭证");
+        }
+
+        // 设置默认值
+        $data = array_merge([
+            'environment' => 'production',
+            'is_active' => true,
+            'usage_count' => 0,
+        ], $data);
+
+        // 创建凭证
+        $credential = new ThirdPartyCredential();
+        $credential->fill($data);
+
+        // 加密敏感信息
+        if (isset($data['credentials'])) {
+            $credential->setEncryptedCredentials($data['credentials']);
+        }
+
+        $credential->save();
+
+        return $credential;
+    }
+
+    /**
+     * 更新认证凭证
+     *
+     * @param ThirdPartyCredential $credential
+     * @param array $data
+     * @return bool
+     * @throws \Exception
+     */
+    public static function updateCredential(ThirdPartyCredential $credential, array $data): bool
+    {
+        // 如果更新认证类型,验证类型有效性
+        if (isset($data['type'])) {
+            $authType = AUTH_TYPE::tryFrom($data['type']);
+            if (!$authType) {
+                throw new \Exception("不支持的认证类型: {$data['type']}");
+            }
+
+            // 验证认证类型与服务匹配
+            if ($data['type'] !== $credential->service->auth_type) {
+                throw new \Exception("认证类型 {$data['type']} 与服务要求的 {$credential->service->auth_type} 不匹配");
+            }
+        }
+
+        // 如果更新凭证信息,验证完整性
+        if (isset($data['credentials'])) {
+            $authType = AUTH_TYPE::from($data['type'] ?? $credential->type);
+            $validation = static::validateCredentialData($authType, $data['credentials']);
+            if (!$validation['valid']) {
+                throw new \Exception("凭证配置不完整,缺少字段: " . implode(', ', $validation['missing_fields']));
+            }
+        }
+
+        // 如果要激活凭证,检查同环境下是否已有其他激活的凭证
+        if (isset($data['is_active']) && $data['is_active']) {
+            $environment = $data['environment'] ?? $credential->environment;
+            $existingActive = ThirdPartyCredential::where('service_id', $credential->service_id)
+                ->where('environment', $environment)
+                ->where('is_active', true)
+                ->where('id', '!=', $credential->id)
+                ->exists();
+
+            if ($existingActive) {
+                throw new \Exception("环境 {$environment} 下已有其他激活的凭证,请先停用其他凭证");
+            }
+        }
+
+        // 更新基本信息
+        $updateData = array_except($data, ['credentials']);
+        if (!empty($updateData)) {
+            $credential->update($updateData);
+        }
+
+        // 更新凭证信息
+        if (isset($data['credentials'])) {
+            $credential->setEncryptedCredentials($data['credentials']);
+            $credential->save();
+        }
+
+        return true;
+    }
+
+    /**
+     * 删除认证凭证
+     *
+     * @param ThirdPartyCredential $credential
+     * @return bool
+     * @throws \Exception
+     */
+    public static function deleteCredential(ThirdPartyCredential $credential): bool
+    {
+        // 检查凭证是否正在使用
+        if ($credential->is_active) {
+            throw new \Exception("激活状态的凭证无法删除,请先停用凭证");
+        }
+
+        // 检查最近是否有使用记录
+        if ($credential->last_used_at && $credential->last_used_at->gt(now()->subHours(1))) {
+            throw new \Exception("凭证在1小时内有使用记录,无法删除");
+        }
+
+        return $credential->delete();
+    }
+
+    /**
+     * 激活/停用凭证
+     *
+     * @param ThirdPartyCredential $credential
+     * @param bool $active
+     * @return bool
+     * @throws \Exception
+     */
+    public static function toggleCredentialStatus(ThirdPartyCredential $credential, bool $active): bool
+    {
+        if ($active) {
+            // 激活凭证前检查
+            if ($credential->isExpired()) {
+                throw new \Exception("凭证已过期,无法激活");
+            }
+
+            // 检查同环境下是否已有其他激活的凭证
+            $existingActive = ThirdPartyCredential::where('service_id', $credential->service_id)
+                ->where('environment', $credential->environment)
+                ->where('is_active', true)
+                ->where('id', '!=', $credential->id)
+                ->exists();
+
+            if ($existingActive) {
+                throw new \Exception("环境 {$credential->environment} 下已有其他激活的凭证,请先停用其他凭证");
+            }
+        }
+
+        return $credential->update(['is_active' => $active]);
+    }
+
+    /**
+     * 获取凭证列表
+     *
+     * @param array $filters
+     * @param array $options
+     * @return Collection
+     */
+    public static function getCredentialList(array $filters = [], array $options = []): Collection
+    {
+        $query = ThirdPartyCredential::with('service');
+
+        // 应用过滤条件
+        if (isset($filters['service_id'])) {
+            $query->where('service_id', $filters['service_id']);
+        }
+
+        if (isset($filters['type'])) {
+            $query->where('type', $filters['type']);
+        }
+
+        if (isset($filters['environment'])) {
+            $query->where('environment', $filters['environment']);
+        }
+
+        if (isset($filters['is_active'])) {
+            $query->where('is_active', $filters['is_active']);
+        }
+
+        if (isset($filters['expired'])) {
+            if ($filters['expired']) {
+                $query->where('expires_at', '<=', now());
+            } else {
+                $query->where(function ($q) {
+                    $q->whereNull('expires_at')
+                      ->orWhere('expires_at', '>', now());
+                });
+            }
+        }
+
+        if (isset($filters['search'])) {
+            $search = $filters['search'];
+            $query->where(function ($q) use ($search) {
+                $q->where('name', 'like', "%{$search}%")
+                  ->orWhereHas('service', function ($sq) use ($search) {
+                      $sq->where('name', 'like', "%{$search}%")
+                        ->orWhere('code', 'like', "%{$search}%");
+                  });
+            });
+        }
+
+        // 应用排序
+        $sortBy = $options['sort_by'] ?? 'created_at';
+        $sortOrder = $options['sort_order'] ?? 'desc';
+        $query->orderBy($sortBy, $sortOrder);
+
+        return $query->get();
+    }
+
+    /**
+     * 获取即将过期的凭证
+     *
+     * @param int $days 提前天数
+     * @return Collection
+     */
+    public static function getExpiringCredentials(int $days = 7): Collection
+    {
+        return ThirdPartyCredential::with('service')
+            ->where('is_active', true)
+            ->whereNotNull('expires_at')
+            ->whereBetween('expires_at', [now(), now()->addDays($days)])
+            ->orderBy('expires_at')
+            ->get();
+    }
+
+    /**
+     * 获取未使用的凭证
+     *
+     * @param int $days 天数
+     * @return Collection
+     */
+    public static function getUnusedCredentials(int $days = 30): Collection
+    {
+        return ThirdPartyCredential::with('service')
+            ->where('is_active', true)
+            ->where(function ($query) use ($days) {
+                $query->whereNull('last_used_at')
+                      ->orWhere('last_used_at', '<', now()->subDays($days));
+            })
+            ->orderBy('created_at')
+            ->get();
+    }
+
+    /**
+     * 验证凭证数据完整性
+     *
+     * @param AUTH_TYPE $authType
+     * @param array $credentials
+     * @return array
+     */
+    public static function validateCredentialData(AUTH_TYPE $authType, array $credentials): array
+    {
+        $requiredFields = $authType->getRequiredFields();
+        $missingFields = [];
+
+        foreach ($requiredFields as $field) {
+            if (empty($credentials[$field])) {
+                $missingFields[] = $field;
+            }
+        }
+
+        return [
+            'valid' => empty($missingFields),
+            'missing_fields' => $missingFields,
+            'required_fields' => $requiredFields,
+        ];
+    }
+
+    /**
+     * 测试凭证连接
+     *
+     * @param ThirdPartyCredential $credential
+     * @return array
+     */
+    public static function testCredential(ThirdPartyCredential $credential): array
+    {
+        $result = [
+            'success' => false,
+            'message' => '',
+            'details' => [],
+        ];
+
+        try {
+            // 检查凭证状态
+            if (!$credential->isUsable()) {
+                $result['message'] = '凭证不可用';
+                return $result;
+            }
+
+            // 验证凭证配置
+            $validation = $credential->validateCredentials();
+            if (!$validation['valid']) {
+                $result['message'] = '凭证配置不完整: ' . implode(', ', $validation['missing_fields']);
+                return $result;
+            }
+
+            // 生成认证头
+            $headers = $credential->generateAuthHeaders();
+            if (empty($headers)) {
+                $result['message'] = '无法生成认证头';
+                return $result;
+            }
+
+            $result['success'] = true;
+            $result['message'] = '凭证测试成功';
+            $result['details'] = [
+                'auth_type' => $credential->getTypeLabel(),
+                'environment' => $credential->environment,
+                'expires_at' => $credential->expires_at?->toDateTimeString(),
+                'usage_count' => $credential->usage_count,
+            ];
+
+        } catch (\Exception $e) {
+            $result['message'] = '凭证测试失败: ' . $e->getMessage();
+        }
+
+        return $result;
+    }
+
+    /**
+     * 轮换凭证
+     *
+     * @param ThirdPartyCredential $credential
+     * @param array $newCredentials
+     * @return ThirdPartyCredential
+     * @throws \Exception
+     */
+    public static function rotateCredential(ThirdPartyCredential $credential, array $newCredentials): ThirdPartyCredential
+    {
+        // 验证新凭证数据
+        $authType = AUTH_TYPE::from($credential->type);
+        $validation = static::validateCredentialData($authType, $newCredentials);
+        if (!$validation['valid']) {
+            throw new \Exception("新凭证配置不完整,缺少字段: " . implode(', ', $validation['missing_fields']));
+        }
+
+        // 创建新凭证
+        $newCredential = static::createCredential([
+            'service_id' => $credential->service_id,
+            'name' => $credential->name . '_rotated_' . now()->format('YmdHis'),
+            'type' => $credential->type,
+            'credentials' => $newCredentials,
+            'environment' => $credential->environment,
+            'is_active' => false, // 先创建为非激活状态
+            'expires_at' => $credential->expires_at,
+        ]);
+
+        // 停用旧凭证
+        $credential->update(['is_active' => false]);
+
+        // 激活新凭证
+        $newCredential->update(['is_active' => true]);
+
+        return $newCredential;
+    }
+
+    /**
+     * 获取凭证统计信息
+     *
+     * @return array
+     */
+    public static function getCredentialStats(): array
+    {
+        $total = ThirdPartyCredential::count();
+        $active = ThirdPartyCredential::where('is_active', true)->count();
+        $expired = ThirdPartyCredential::where('expires_at', '<=', now())->count();
+        $expiringSoon = ThirdPartyCredential::where('is_active', true)
+            ->whereNotNull('expires_at')
+            ->whereBetween('expires_at', [now(), now()->addDays(7)])
+            ->count();
+
+        $envStats = ThirdPartyCredential::selectRaw('environment, COUNT(*) as count')
+            ->groupBy('environment')
+            ->pluck('count', 'environment')
+            ->toArray();
+
+        $typeStats = ThirdPartyCredential::selectRaw('type, COUNT(*) as count')
+            ->groupBy('type')
+            ->pluck('count', 'type')
+            ->toArray();
+
+        return [
+            'total' => $total,
+            'active' => $active,
+            'expired' => $expired,
+            'expiring_soon' => $expiringSoon,
+            'by_environment' => $envStats,
+            'by_type' => $typeStats,
+            'health_rate' => $total > 0 ? round((($active - $expired) / $total) * 100, 2) : 0,
+        ];
+    }
+}

+ 377 - 0
app/Module/ThirdParty/Logics/ServiceLogic.php

@@ -0,0 +1,377 @@
+<?php
+
+namespace App\Module\ThirdParty\Logics;
+
+use App\Module\ThirdParty\Models\ThirdPartyService;
+use App\Module\ThirdParty\Models\ThirdPartyCredential;
+use App\Module\ThirdParty\Models\ThirdPartyQuota;
+use App\Module\ThirdParty\Enums\SERVICE_STATUS;
+use App\Module\ThirdParty\Enums\SERVICE_TYPE;
+use App\Module\ThirdParty\Enums\AUTH_TYPE;
+use Illuminate\Support\Collection;
+
+/**
+ * 第三方服务业务逻辑类
+ */
+class ServiceLogic
+{
+    /**
+     * 创建新的第三方服务
+     *
+     * @param array $data
+     * @return ThirdPartyService
+     * @throws \Exception
+     */
+    public static function createService(array $data): ThirdPartyService
+    {
+        // 验证服务类型
+        if (!SERVICE_TYPE::tryFrom($data['type'])) {
+            throw new \Exception("不支持的服务类型: {$data['type']}");
+        }
+
+        // 验证认证类型
+        if (!AUTH_TYPE::tryFrom($data['auth_type'])) {
+            throw new \Exception("不支持的认证类型: {$data['auth_type']}");
+        }
+
+        // 生成唯一的服务代码
+        if (empty($data['code'])) {
+            $data['code'] = static::generateUniqueCode($data['name'], $data['provider']);
+        }
+
+        // 验证代码唯一性
+        if (ThirdPartyService::where('code', $data['code'])->exists()) {
+            throw new \Exception("服务代码已存在: {$data['code']}");
+        }
+
+        // 设置默认值
+        $data = array_merge([
+            'version' => 'v1',
+            'status' => SERVICE_STATUS::INACTIVE->value,
+            'priority' => 0,
+            'timeout' => config('thirdparty.defaults.timeout', 30),
+            'retry_times' => config('thirdparty.defaults.retry_times', 3),
+            'retry_delay' => config('thirdparty.defaults.retry_delay', 1000),
+            'health_check_interval' => config('thirdparty.defaults.health_check_interval', 300),
+            'health_status' => 'UNKNOWN',
+        ], $data);
+
+        return ThirdPartyService::create($data);
+    }
+
+    /**
+     * 更新服务信息
+     *
+     * @param ThirdPartyService $service
+     * @param array $data
+     * @return bool
+     * @throws \Exception
+     */
+    public static function updateService(ThirdPartyService $service, array $data): bool
+    {
+        // 如果更新服务类型,验证类型有效性
+        if (isset($data['type']) && !SERVICE_TYPE::tryFrom($data['type'])) {
+            throw new \Exception("不支持的服务类型: {$data['type']}");
+        }
+
+        // 如果更新认证类型,验证类型有效性
+        if (isset($data['auth_type']) && !AUTH_TYPE::tryFrom($data['auth_type'])) {
+            throw new \Exception("不支持的认证类型: {$data['auth_type']}");
+        }
+
+        // 如果更新代码,验证唯一性
+        if (isset($data['code']) && $data['code'] !== $service->code) {
+            if (ThirdPartyService::where('code', $data['code'])->where('id', '!=', $service->id)->exists()) {
+                throw new \Exception("服务代码已存在: {$data['code']}");
+            }
+        }
+
+        return $service->update($data);
+    }
+
+    /**
+     * 删除服务
+     *
+     * @param ThirdPartyService $service
+     * @return bool
+     * @throws \Exception
+     */
+    public static function deleteService(ThirdPartyService $service): bool
+    {
+        // 检查服务是否有活跃的凭证
+        $activeCredentials = $service->credentials()->where('is_active', true)->count();
+        if ($activeCredentials > 0) {
+            throw new \Exception("服务有 {$activeCredentials} 个活跃凭证,无法删除");
+        }
+
+        // 检查服务状态
+        if ($service->status === SERVICE_STATUS::ACTIVE->value) {
+            throw new \Exception("活跃状态的服务无法删除,请先停用服务");
+        }
+
+        return $service->delete();
+    }
+
+    /**
+     * 更新服务状态
+     *
+     * @param ThirdPartyService $service
+     * @param string $status
+     * @return bool
+     * @throws \Exception
+     */
+    public static function updateServiceStatus(ThirdPartyService $service, string $status): bool
+    {
+        $newStatus = SERVICE_STATUS::tryFrom($status);
+        if (!$newStatus) {
+            throw new \Exception("无效的服务状态: {$status}");
+        }
+
+        $currentStatus = SERVICE_STATUS::from($service->status);
+
+        // 检查状态转换是否允许
+        if (!$currentStatus->canTransitionTo($newStatus)) {
+            throw new \Exception("不能从状态 {$currentStatus->getLabel()} 转换到 {$newStatus->getLabel()}");
+        }
+
+        // 如果要激活服务,检查是否有可用的凭证
+        if ($newStatus === SERVICE_STATUS::ACTIVE) {
+            $hasActiveCredential = $service->credentials()
+                ->where('is_active', true)
+                ->where(function ($query) {
+                    $query->whereNull('expires_at')
+                        ->orWhere('expires_at', '>', now());
+                })
+                ->exists();
+
+            if (!$hasActiveCredential) {
+                throw new \Exception("服务没有可用的认证凭证,无法激活");
+            }
+        }
+
+        return $service->update(['status' => $status]);
+    }
+
+    /**
+     * 获取服务列表
+     *
+     * @param array $filters
+     * @param array $options
+     * @return Collection
+     */
+    public static function getServiceList(array $filters = [], array $options = []): Collection
+    {
+        $query = ThirdPartyService::query();
+
+        // 应用过滤条件
+        if (isset($filters['type'])) {
+            $query->where('type', $filters['type']);
+        }
+
+        if (isset($filters['status'])) {
+            $query->where('status', $filters['status']);
+        }
+
+        if (isset($filters['provider'])) {
+            $query->where('provider', $filters['provider']);
+        }
+
+        if (isset($filters['search'])) {
+            $search = $filters['search'];
+            $query->where(function ($q) use ($search) {
+                $q->where('name', 'like', "%{$search}%")
+                  ->orWhere('code', 'like', "%{$search}%")
+                  ->orWhere('provider', 'like', "%{$search}%");
+            });
+        }
+
+        // 应用排序
+        $sortBy = $options['sort_by'] ?? 'priority';
+        $sortOrder = $options['sort_order'] ?? 'asc';
+        $query->orderBy($sortBy, $sortOrder);
+
+        // 如果优先级相同,按名称排序
+        if ($sortBy !== 'name') {
+            $query->orderBy('name', 'asc');
+        }
+
+        return $query->get();
+    }
+
+    /**
+     * 获取可用的服务列表
+     *
+     * @param string|null $type
+     * @return Collection
+     */
+    public static function getAvailableServices(?string $type = null): Collection
+    {
+        $query = ThirdPartyService::query()
+            ->where('status', SERVICE_STATUS::ACTIVE->value);
+
+        if ($type) {
+            $query->where('type', $type);
+        }
+
+        return $query->orderBy('priority')->orderBy('name')->get();
+    }
+
+    /**
+     * 根据代码获取服务
+     *
+     * @param string $code
+     * @return ThirdPartyService|null
+     */
+    public static function getServiceByCode(string $code): ?ThirdPartyService
+    {
+        return ThirdPartyService::where('code', $code)->first();
+    }
+
+    /**
+     * 检查服务健康状态
+     *
+     * @param ThirdPartyService $service
+     * @return array
+     */
+    public static function checkServiceHealth(ThirdPartyService $service): array
+    {
+        $result = [
+            'service_id' => $service->id,
+            'service_code' => $service->code,
+            'status' => 'unknown',
+            'message' => '',
+            'details' => [],
+        ];
+
+        try {
+            // 检查服务状态
+            if (!$service->isAvailable()) {
+                $result['status'] = 'unavailable';
+                $result['message'] = "服务状态为 {$service->getStatusLabel()},不可用";
+                return $result;
+            }
+
+            // 检查认证凭证
+            $credential = $service->getActiveCredential();
+            if (!$credential) {
+                $result['status'] = 'error';
+                $result['message'] = '没有可用的认证凭证';
+                return $result;
+            }
+
+            if ($credential->isExpired()) {
+                $result['status'] = 'error';
+                $result['message'] = '认证凭证已过期';
+                return $result;
+            }
+
+            // 检查配额
+            $quotaStatus = static::checkServiceQuota($service);
+            if (!$quotaStatus['available']) {
+                $result['status'] = 'quota_exceeded';
+                $result['message'] = '服务配额已用完';
+                $result['details']['quota'] = $quotaStatus;
+                return $result;
+            }
+
+            $result['status'] = 'healthy';
+            $result['message'] = '服务健康';
+            $result['details'] = [
+                'credential_expires_at' => $credential->expires_at?->toDateTimeString(),
+                'quota' => $quotaStatus,
+            ];
+
+        } catch (\Exception $e) {
+            $result['status'] = 'error';
+            $result['message'] = $e->getMessage();
+        }
+
+        return $result;
+    }
+
+    /**
+     * 检查服务配额状态
+     *
+     * @param ThirdPartyService $service
+     * @return array
+     */
+    public static function checkServiceQuota(ThirdPartyService $service): array
+    {
+        $quotas = $service->quotas()->active()->get();
+        
+        $result = [
+            'available' => true,
+            'quotas' => [],
+        ];
+
+        foreach ($quotas as $quota) {
+            $quotaInfo = [
+                'type' => $quota->type,
+                'limit' => $quota->limit_value,
+                'used' => $quota->used_value,
+                'remaining' => $quota->getRemainingQuota(),
+                'percentage' => $quota->getUsagePercentage(),
+                'exceeded' => $quota->isExceeded(),
+                'near_threshold' => $quota->isNearThreshold(),
+            ];
+
+            $result['quotas'][] = $quotaInfo;
+
+            if ($quota->isExceeded()) {
+                $result['available'] = false;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * 生成唯一的服务代码
+     *
+     * @param string $name
+     * @param string $provider
+     * @return string
+     */
+    protected static function generateUniqueCode(string $name, string $provider): string
+    {
+        $code = strtolower($provider . '_' . str_replace([' ', '-', '.'], '_', $name));
+        $code = preg_replace('/[^a-z0-9_]/', '', $code);
+        
+        // 确保代码唯一
+        $counter = 1;
+        $originalCode = $code;
+        while (ThirdPartyService::where('code', $code)->exists()) {
+            $code = $originalCode . '_' . $counter;
+            $counter++;
+        }
+        
+        return $code;
+    }
+
+    /**
+     * 获取服务统计信息
+     *
+     * @return array
+     */
+    public static function getServiceStats(): array
+    {
+        $total = ThirdPartyService::count();
+        $active = ThirdPartyService::where('status', SERVICE_STATUS::ACTIVE->value)->count();
+        $inactive = ThirdPartyService::where('status', SERVICE_STATUS::INACTIVE->value)->count();
+        $error = ThirdPartyService::where('status', SERVICE_STATUS::ERROR->value)->count();
+
+        $typeStats = ThirdPartyService::selectRaw('type, COUNT(*) as count')
+            ->groupBy('type')
+            ->pluck('count', 'type')
+            ->toArray();
+
+        return [
+            'total' => $total,
+            'active' => $active,
+            'inactive' => $inactive,
+            'error' => $error,
+            'by_type' => $typeStats,
+            'health_rate' => $total > 0 ? round(($active / $total) * 100, 2) : 0,
+        ];
+    }
+}

+ 4 - 0
app/Module/ThirdParty/Providers/ThirdPartyServiceProvider.php

@@ -80,6 +80,10 @@ class ThirdPartyServiceProvider extends ServiceProvider
         $this->app->singleton('thirdparty.quota', function () {
             return new \App\Module\ThirdParty\Services\QuotaService();
         });
+
+        // 注册验证器
+        $this->app->singleton(\App\Module\ThirdParty\Validators\ServiceValidator::class);
+        $this->app->singleton(\App\Module\ThirdParty\Validators\CredentialValidator::class);
     }
 
     /**

+ 21 - 0
app/Module/ThirdParty/Repositorys/ThirdPartyCredentialRepository.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Module\ThirdParty\Repositorys;
+
+use App\Module\ThirdParty\Models\ThirdPartyCredential;
+use Dcat\Admin\Repositories\EloquentRepository;
+
+/**
+ * 第三方服务认证凭证数据仓库
+ * 
+ * 专门用于后台管理的数据访问层
+ */
+class ThirdPartyCredentialRepository extends EloquentRepository
+{
+    /**
+     * 模型类名
+     *
+     * @var string
+     */
+    protected $eloquentClass = ThirdPartyCredential::class;
+}

+ 21 - 0
app/Module/ThirdParty/Repositorys/ThirdPartyLogRepository.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Module\ThirdParty\Repositorys;
+
+use App\Module\ThirdParty\Models\ThirdPartyLog;
+use Dcat\Admin\Repositories\EloquentRepository;
+
+/**
+ * 第三方服务调用日志数据仓库
+ * 
+ * 专门用于后台管理的数据访问层
+ */
+class ThirdPartyLogRepository extends EloquentRepository
+{
+    /**
+     * 模型类名
+     *
+     * @var string
+     */
+    protected $eloquentClass = ThirdPartyLog::class;
+}

+ 21 - 0
app/Module/ThirdParty/Repositorys/ThirdPartyMonitorRepository.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Module\ThirdParty\Repositorys;
+
+use App\Module\ThirdParty\Models\ThirdPartyMonitor;
+use Dcat\Admin\Repositories\EloquentRepository;
+
+/**
+ * 第三方服务监控记录数据仓库
+ * 
+ * 专门用于后台管理的数据访问层
+ */
+class ThirdPartyMonitorRepository extends EloquentRepository
+{
+    /**
+     * 模型类名
+     *
+     * @var string
+     */
+    protected $eloquentClass = ThirdPartyMonitor::class;
+}

+ 21 - 0
app/Module/ThirdParty/Repositorys/ThirdPartyQuotaRepository.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Module\ThirdParty\Repositorys;
+
+use App\Module\ThirdParty\Models\ThirdPartyQuota;
+use Dcat\Admin\Repositories\EloquentRepository;
+
+/**
+ * 第三方服务配额管理数据仓库
+ * 
+ * 专门用于后台管理的数据访问层
+ */
+class ThirdPartyQuotaRepository extends EloquentRepository
+{
+    /**
+     * 模型类名
+     *
+     * @var string
+     */
+    protected $eloquentClass = ThirdPartyQuota::class;
+}

+ 21 - 0
app/Module/ThirdParty/Repositorys/ThirdPartyServiceRepository.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Module\ThirdParty\Repositorys;
+
+use App\Module\ThirdParty\Models\ThirdPartyService;
+use Dcat\Admin\Repositories\EloquentRepository;
+
+/**
+ * 第三方服务数据仓库
+ * 
+ * 专门用于后台管理的数据访问层
+ */
+class ThirdPartyServiceRepository extends EloquentRepository
+{
+    /**
+     * 模型类名
+     *
+     * @var string
+     */
+    protected $eloquentClass = ThirdPartyService::class;
+}

+ 311 - 0
app/Module/ThirdParty/Services/CredentialService.php

@@ -0,0 +1,311 @@
+<?php
+
+namespace App\Module\ThirdParty\Services;
+
+use App\Module\ThirdParty\Logics\CredentialLogic;
+use App\Module\ThirdParty\Models\ThirdPartyCredential;
+use Illuminate\Support\Collection;
+
+/**
+ * 第三方服务认证凭证服务类
+ */
+class CredentialService
+{
+    /**
+     * 创建新的认证凭证
+     *
+     * @param array $data
+     * @return ThirdPartyCredential
+     * @throws \Exception
+     */
+    public static function create(array $data): ThirdPartyCredential
+    {
+        return CredentialLogic::createCredential($data);
+    }
+
+    /**
+     * 更新认证凭证
+     *
+     * @param int $credentialId
+     * @param array $data
+     * @return bool
+     * @throws \Exception
+     */
+    public static function update(int $credentialId, array $data): bool
+    {
+        $credential = ThirdPartyCredential::findOrFail($credentialId);
+        return CredentialLogic::updateCredential($credential, $data);
+    }
+
+    /**
+     * 删除认证凭证
+     *
+     * @param int $credentialId
+     * @return bool
+     * @throws \Exception
+     */
+    public static function delete(int $credentialId): bool
+    {
+        $credential = ThirdPartyCredential::findOrFail($credentialId);
+        return CredentialLogic::deleteCredential($credential);
+    }
+
+    /**
+     * 激活/停用凭证
+     *
+     * @param int $credentialId
+     * @param bool $active
+     * @return bool
+     * @throws \Exception
+     */
+    public static function toggleStatus(int $credentialId, bool $active): bool
+    {
+        $credential = ThirdPartyCredential::findOrFail($credentialId);
+        return CredentialLogic::toggleCredentialStatus($credential, $active);
+    }
+
+    /**
+     * 获取凭证详情
+     *
+     * @param int $credentialId
+     * @return ThirdPartyCredential
+     */
+    public static function getById(int $credentialId): ThirdPartyCredential
+    {
+        return ThirdPartyCredential::with('service')->findOrFail($credentialId);
+    }
+
+    /**
+     * 获取凭证列表
+     *
+     * @param array $filters
+     * @param array $options
+     * @return Collection
+     */
+    public static function getList(array $filters = [], array $options = []): Collection
+    {
+        return CredentialLogic::getCredentialList($filters, $options);
+    }
+
+    /**
+     * 获取服务的活跃凭证
+     *
+     * @param int $serviceId
+     * @param string $environment
+     * @return ThirdPartyCredential|null
+     */
+    public static function getActiveCredential(int $serviceId, string $environment = 'production'): ?ThirdPartyCredential
+    {
+        return ThirdPartyCredential::where('service_id', $serviceId)
+            ->where('environment', $environment)
+            ->where('is_active', true)
+            ->where(function ($query) {
+                $query->whereNull('expires_at')
+                    ->orWhere('expires_at', '>', now());
+            })
+            ->first();
+    }
+
+    /**
+     * 获取即将过期的凭证
+     *
+     * @param int $days
+     * @return Collection
+     */
+    public static function getExpiringCredentials(int $days = 7): Collection
+    {
+        return CredentialLogic::getExpiringCredentials($days);
+    }
+
+    /**
+     * 获取未使用的凭证
+     *
+     * @param int $days
+     * @return Collection
+     */
+    public static function getUnusedCredentials(int $days = 30): Collection
+    {
+        return CredentialLogic::getUnusedCredentials($days);
+    }
+
+    /**
+     * 测试凭证连接
+     *
+     * @param int $credentialId
+     * @return array
+     */
+    public static function testCredential(int $credentialId): array
+    {
+        $credential = ThirdPartyCredential::findOrFail($credentialId);
+        return CredentialLogic::testCredential($credential);
+    }
+
+    /**
+     * 轮换凭证
+     *
+     * @param int $credentialId
+     * @param array $newCredentials
+     * @return ThirdPartyCredential
+     * @throws \Exception
+     */
+    public static function rotateCredential(int $credentialId, array $newCredentials): ThirdPartyCredential
+    {
+        $credential = ThirdPartyCredential::findOrFail($credentialId);
+        return CredentialLogic::rotateCredential($credential, $newCredentials);
+    }
+
+    /**
+     * 批量更新凭证状态
+     *
+     * @param array $credentialIds
+     * @param bool $active
+     * @return array
+     */
+    public static function batchToggleStatus(array $credentialIds, bool $active): array
+    {
+        $results = [
+            'success' => [],
+            'failed' => [],
+        ];
+
+        foreach ($credentialIds as $credentialId) {
+            try {
+                static::toggleStatus($credentialId, $active);
+                $results['success'][] = $credentialId;
+            } catch (\Exception $e) {
+                $results['failed'][] = [
+                    'id' => $credentialId,
+                    'error' => $e->getMessage(),
+                ];
+            }
+        }
+
+        return $results;
+    }
+
+    /**
+     * 批量删除凭证
+     *
+     * @param array $credentialIds
+     * @return array
+     */
+    public static function batchDelete(array $credentialIds): array
+    {
+        $results = [
+            'success' => [],
+            'failed' => [],
+        ];
+
+        foreach ($credentialIds as $credentialId) {
+            try {
+                static::delete($credentialId);
+                $results['success'][] = $credentialId;
+            } catch (\Exception $e) {
+                $results['failed'][] = [
+                    'id' => $credentialId,
+                    'error' => $e->getMessage(),
+                ];
+            }
+        }
+
+        return $results;
+    }
+
+    /**
+     * 获取凭证统计信息
+     *
+     * @return array
+     */
+    public static function getStats(): array
+    {
+        return CredentialLogic::getCredentialStats();
+    }
+
+    /**
+     * 验证凭证数据
+     *
+     * @param string $authType
+     * @param array $credentials
+     * @return array
+     */
+    public static function validateCredentialData(string $authType, array $credentials): array
+    {
+        $authTypeEnum = \App\Module\ThirdParty\Enums\AUTH_TYPE::from($authType);
+        return CredentialLogic::validateCredentialData($authTypeEnum, $credentials);
+    }
+
+    /**
+     * 获取认证类型的必需字段
+     *
+     * @param string $authType
+     * @return array
+     */
+    public static function getRequiredFields(string $authType): array
+    {
+        $authTypeEnum = \App\Module\ThirdParty\Enums\AUTH_TYPE::from($authType);
+        return $authTypeEnum->getRequiredFields();
+    }
+
+    /**
+     * 清理过期凭证
+     *
+     * @param int $days 过期天数
+     * @return int 清理数量
+     */
+    public static function cleanupExpiredCredentials(int $days = 30): int
+    {
+        $expiredCredentials = ThirdPartyCredential::where('is_active', false)
+            ->where('expires_at', '<', now()->subDays($days))
+            ->get();
+
+        $count = 0;
+        foreach ($expiredCredentials as $credential) {
+            try {
+                $credential->delete();
+                $count++;
+            } catch (\Exception $e) {
+                // 记录错误但继续处理其他凭证
+                \Log::warning("清理过期凭证失败: {$credential->id}, 错误: {$e->getMessage()}");
+            }
+        }
+
+        return $count;
+    }
+
+    /**
+     * 导出凭证配置(不包含敏感信息)
+     *
+     * @param array $credentialIds
+     * @return array
+     */
+    public static function exportCredentials(array $credentialIds = []): array
+    {
+        $query = ThirdPartyCredential::with('service');
+        
+        if (!empty($credentialIds)) {
+            $query->whereIn('id', $credentialIds);
+        }
+
+        $credentials = $query->get();
+        $exported = [];
+
+        foreach ($credentials as $credential) {
+            $exported[] = [
+                'id' => $credential->id,
+                'service_name' => $credential->service->name,
+                'service_code' => $credential->service->code,
+                'name' => $credential->name,
+                'type' => $credential->type,
+                'environment' => $credential->environment,
+                'is_active' => $credential->is_active,
+                'expires_at' => $credential->expires_at?->toDateTimeString(),
+                'usage_count' => $credential->usage_count,
+                'last_used_at' => $credential->last_used_at?->toDateTimeString(),
+                'created_at' => $credential->created_at->toDateTimeString(),
+                // 不导出敏感的凭证信息
+            ];
+        }
+
+        return $exported;
+    }
+}

+ 317 - 0
app/Module/ThirdParty/Services/LogService.php

@@ -0,0 +1,317 @@
+<?php
+
+namespace App\Module\ThirdParty\Services;
+
+use App\Module\ThirdParty\Models\ThirdPartyLog;
+use App\Module\ThirdParty\Enums\LOG_LEVEL;
+use Illuminate\Support\Collection;
+
+/**
+ * 第三方服务日志服务类
+ */
+class LogService
+{
+    /**
+     * 创建日志记录
+     *
+     * @param array $data
+     * @return ThirdPartyLog
+     */
+    public static function create(array $data): ThirdPartyLog
+    {
+        return ThirdPartyLog::createLog($data);
+    }
+
+    /**
+     * 获取日志列表
+     *
+     * @param array $filters
+     * @param array $options
+     * @return Collection
+     */
+    public static function getList(array $filters = [], array $options = []): Collection
+    {
+        $query = ThirdPartyLog::with('service', 'credential');
+
+        // 应用过滤条件
+        if (isset($filters['service_id'])) {
+            $query->where('service_id', $filters['service_id']);
+        }
+
+        if (isset($filters['credential_id'])) {
+            $query->where('credential_id', $filters['credential_id']);
+        }
+
+        if (isset($filters['level'])) {
+            $query->where('level', $filters['level']);
+        }
+
+        if (isset($filters['method'])) {
+            $query->where('method', $filters['method']);
+        }
+
+        if (isset($filters['status_code'])) {
+            $query->where('response_status', $filters['status_code']);
+        }
+
+        if (isset($filters['start_date'])) {
+            $query->where('created_at', '>=', $filters['start_date']);
+        }
+
+        if (isset($filters['end_date'])) {
+            $query->where('created_at', '<=', $filters['end_date']);
+        }
+
+        if (isset($filters['search'])) {
+            $search = $filters['search'];
+            $query->where(function ($q) use ($search) {
+                $q->where('url', 'like', "%{$search}%")
+                  ->orWhere('request_id', 'like', "%{$search}%")
+                  ->orWhere('error_message', 'like', "%{$search}%");
+            });
+        }
+
+        // 只查询错误日志
+        if (isset($filters['errors_only']) && $filters['errors_only']) {
+            $query->errors();
+        }
+
+        // 应用排序
+        $sortBy = $options['sort_by'] ?? 'created_at';
+        $sortOrder = $options['sort_order'] ?? 'desc';
+        $query->orderBy($sortBy, $sortOrder);
+
+        // 应用限制
+        if (isset($options['limit'])) {
+            $query->limit($options['limit']);
+        }
+
+        return $query->get();
+    }
+
+    /**
+     * 获取日志详情
+     *
+     * @param int $logId
+     * @return ThirdPartyLog
+     */
+    public static function getById(int $logId): ThirdPartyLog
+    {
+        return ThirdPartyLog::with('service', 'credential')->findOrFail($logId);
+    }
+
+    /**
+     * 获取日志统计信息
+     *
+     * @param array $filters
+     * @return array
+     */
+    public static function getStats(array $filters = []): array
+    {
+        $query = ThirdPartyLog::query();
+
+        // 应用过滤条件
+        if (isset($filters['service_id'])) {
+            $query->where('service_id', $filters['service_id']);
+        }
+
+        if (isset($filters['start_date'])) {
+            $query->where('created_at', '>=', $filters['start_date']);
+        }
+
+        if (isset($filters['end_date'])) {
+            $query->where('created_at', '<=', $filters['end_date']);
+        }
+
+        $total = $query->count();
+        $successful = $query->whereBetween('response_status', [200, 299])->count();
+        $errors = $query->where('response_status', '>=', 400)->count();
+
+        // 按级别统计
+        $levelStats = $query->selectRaw('level, COUNT(*) as count')
+            ->groupBy('level')
+            ->pluck('count', 'level')
+            ->toArray();
+
+        // 按状态码统计
+        $statusStats = $query->selectRaw('response_status, COUNT(*) as count')
+            ->whereNotNull('response_status')
+            ->groupBy('response_status')
+            ->pluck('count', 'response_status')
+            ->toArray();
+
+        // 平均响应时间
+        $avgResponseTime = $query->whereNotNull('response_time')
+            ->avg('response_time');
+
+        return [
+            'total' => $total,
+            'successful' => $successful,
+            'errors' => $errors,
+            'success_rate' => $total > 0 ? round(($successful / $total) * 100, 2) : 0,
+            'avg_response_time' => $avgResponseTime ? round($avgResponseTime, 2) : null,
+            'by_level' => $levelStats,
+            'by_status' => $statusStats,
+        ];
+    }
+
+    /**
+     * 获取错误日志
+     *
+     * @param array $filters
+     * @param int $limit
+     * @return Collection
+     */
+    public static function getErrorLogs(array $filters = [], int $limit = 100): Collection
+    {
+        $filters['errors_only'] = true;
+        $options = ['limit' => $limit];
+        
+        return static::getList($filters, $options);
+    }
+
+    /**
+     * 获取慢请求日志
+     *
+     * @param int $threshold 响应时间阈值(毫秒)
+     * @param array $filters
+     * @param int $limit
+     * @return Collection
+     */
+    public static function getSlowLogs(int $threshold = 5000, array $filters = [], int $limit = 100): Collection
+    {
+        $query = ThirdPartyLog::with('service', 'credential')
+            ->where('response_time', '>', $threshold);
+
+        // 应用其他过滤条件
+        if (isset($filters['service_id'])) {
+            $query->where('service_id', $filters['service_id']);
+        }
+
+        if (isset($filters['start_date'])) {
+            $query->where('created_at', '>=', $filters['start_date']);
+        }
+
+        if (isset($filters['end_date'])) {
+            $query->where('created_at', '<=', $filters['end_date']);
+        }
+
+        return $query->orderBy('response_time', 'desc')
+            ->limit($limit)
+            ->get();
+    }
+
+    /**
+     * 清理旧日志
+     *
+     * @param int $days 保留天数
+     * @return int 删除数量
+     */
+    public static function cleanup(int $days = 30): int
+    {
+        return ThirdPartyLog::where('created_at', '<', now()->subDays($days))->delete();
+    }
+
+    /**
+     * 导出日志
+     *
+     * @param array $filters
+     * @param string $format
+     * @return array
+     */
+    public static function export(array $filters = [], string $format = 'array'): array
+    {
+        $logs = static::getList($filters);
+        $exported = [];
+
+        foreach ($logs as $log) {
+            $exported[] = [
+                'id' => $log->id,
+                'service_name' => $log->service->name,
+                'service_code' => $log->service->code,
+                'request_id' => $log->request_id,
+                'method' => $log->method,
+                'url' => $log->url,
+                'response_status' => $log->response_status,
+                'response_time' => $log->response_time,
+                'level' => $log->level,
+                'error_message' => $log->error_message,
+                'user_id' => $log->user_id,
+                'ip_address' => $log->ip_address,
+                'created_at' => $log->created_at->toDateTimeString(),
+            ];
+        }
+
+        return $exported;
+    }
+
+    /**
+     * 获取请求追踪信息
+     *
+     * @param string $requestId
+     * @return ThirdPartyLog|null
+     */
+    public static function getByRequestId(string $requestId): ?ThirdPartyLog
+    {
+        return ThirdPartyLog::with('service', 'credential')
+            ->where('request_id', $requestId)
+            ->first();
+    }
+
+    /**
+     * 获取用户的调用日志
+     *
+     * @param int $userId
+     * @param array $options
+     * @return Collection
+     */
+    public static function getUserLogs(int $userId, array $options = []): Collection
+    {
+        $query = ThirdPartyLog::with('service', 'credential')
+            ->where('user_id', $userId);
+
+        $limit = $options['limit'] ?? 50;
+        $query->limit($limit);
+
+        return $query->orderBy('created_at', 'desc')->get();
+    }
+
+    /**
+     * 记录API调用
+     *
+     * @param array $data
+     * @return ThirdPartyLog
+     */
+    public static function logApiCall(array $data): ThirdPartyLog
+    {
+        // 过滤敏感信息
+        if (isset($data['headers'])) {
+            $data['headers'] = static::filterSensitiveData($data['headers']);
+        }
+
+        if (isset($data['params'])) {
+            $data['params'] = static::filterSensitiveData($data['params']);
+        }
+
+        return static::create($data);
+    }
+
+    /**
+     * 过滤敏感数据
+     *
+     * @param array $data
+     * @return array
+     */
+    protected static function filterSensitiveData(array $data): array
+    {
+        $sensitiveFields = config('thirdparty.logging.sensitive_fields', []);
+        
+        foreach ($sensitiveFields as $field) {
+            if (isset($data[$field])) {
+                $data[$field] = '***';
+            }
+        }
+        
+        return $data;
+    }
+}

+ 426 - 0
app/Module/ThirdParty/Services/MonitorService.php

@@ -0,0 +1,426 @@
+<?php
+
+namespace App\Module\ThirdParty\Services;
+
+use App\Module\ThirdParty\Models\ThirdPartyService;
+use App\Module\ThirdParty\Models\ThirdPartyMonitor;
+use App\Module\ThirdParty\Logics\ServiceLogic;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Http;
+
+/**
+ * 第三方服务监控服务类
+ */
+class MonitorService
+{
+    /**
+     * 执行服务健康检查
+     *
+     * @param int|null $serviceId 服务ID,为null时检查所有服务
+     * @return array
+     */
+    public static function performHealthCheck(?int $serviceId = null): array
+    {
+        $results = [];
+        
+        if ($serviceId) {
+            $services = [ThirdPartyService::findOrFail($serviceId)];
+        } else {
+            $services = ThirdPartyService::where('status', 'ACTIVE')
+                ->whereNotNull('health_check_url')
+                ->get();
+        }
+
+        foreach ($services as $service) {
+            $result = static::checkSingleService($service);
+            $results[] = $result;
+            
+            // 记录监控结果
+            static::recordMonitorResult($service, 'health', $result);
+            
+            // 更新服务健康状态
+            $service->updateHealthStatus($result['status']);
+        }
+
+        return $results;
+    }
+
+    /**
+     * 检查单个服务
+     *
+     * @param ThirdPartyService $service
+     * @return array
+     */
+    protected static function checkSingleService(ThirdPartyService $service): array
+    {
+        $result = [
+            'service_id' => $service->id,
+            'service_code' => $service->code,
+            'service_name' => $service->name,
+            'status' => ThirdPartyMonitor::STATUS_UNKNOWN,
+            'response_time' => null,
+            'status_code' => null,
+            'error_message' => null,
+            'details' => [],
+        ];
+
+        try {
+            // 检查服务基本状态
+            $healthCheck = ServiceLogic::checkServiceHealth($service);
+            if ($healthCheck['status'] !== 'healthy') {
+                $result['status'] = ThirdPartyMonitor::STATUS_ERROR;
+                $result['error_message'] = $healthCheck['message'];
+                $result['details'] = $healthCheck['details'];
+                return $result;
+            }
+
+            // 如果有健康检查URL,执行HTTP检查
+            if ($service->health_check_url) {
+                $httpResult = static::performHttpCheck($service);
+                $result = array_merge($result, $httpResult);
+            } else {
+                // 没有健康检查URL,基于服务状态判断
+                $result['status'] = ThirdPartyMonitor::STATUS_SUCCESS;
+                $result['details']['message'] = '服务状态正常,无HTTP健康检查';
+            }
+
+        } catch (\Exception $e) {
+            $result['status'] = ThirdPartyMonitor::STATUS_ERROR;
+            $result['error_message'] = $e->getMessage();
+        }
+
+        return $result;
+    }
+
+    /**
+     * 执行HTTP健康检查
+     *
+     * @param ThirdPartyService $service
+     * @return array
+     */
+    protected static function performHttpCheck(ThirdPartyService $service): array
+    {
+        $result = [
+            'status' => ThirdPartyMonitor::STATUS_UNKNOWN,
+            'response_time' => null,
+            'status_code' => null,
+            'error_message' => null,
+            'details' => [],
+        ];
+
+        $startTime = microtime(true);
+
+        try {
+            $timeout = config('thirdparty.monitoring.health_check.timeout', 10);
+            
+            $response = Http::timeout($timeout)
+                ->get($service->health_check_url);
+
+            $responseTime = (int)((microtime(true) - $startTime) * 1000);
+            $result['response_time'] = $responseTime;
+            $result['status_code'] = $response->status();
+
+            // 判断响应状态
+            if ($response->successful()) {
+                $result['status'] = ThirdPartyMonitor::STATUS_SUCCESS;
+                
+                // 检查响应时间
+                $slowThreshold = config('thirdparty.monitoring.performance.slow_threshold', 2000);
+                if ($responseTime > $slowThreshold) {
+                    $result['status'] = ThirdPartyMonitor::STATUS_WARNING;
+                    $result['details']['warning'] = "响应时间 {$responseTime}ms 超过阈值 {$slowThreshold}ms";
+                }
+            } else {
+                $result['status'] = ThirdPartyMonitor::STATUS_ERROR;
+                $result['error_message'] = "HTTP状态码: {$response->status()}";
+            }
+
+            $result['details']['response_body'] = $response->body();
+
+        } catch (\Illuminate\Http\Client\ConnectionException $e) {
+            $responseTime = (int)((microtime(true) - $startTime) * 1000);
+            $result['response_time'] = $responseTime;
+            $result['status'] = ThirdPartyMonitor::STATUS_TIMEOUT;
+            $result['error_message'] = '连接超时: ' . $e->getMessage();
+        } catch (\Exception $e) {
+            $responseTime = (int)((microtime(true) - $startTime) * 1000);
+            $result['response_time'] = $responseTime;
+            $result['status'] = ThirdPartyMonitor::STATUS_ERROR;
+            $result['error_message'] = $e->getMessage();
+        }
+
+        return $result;
+    }
+
+    /**
+     * 记录监控结果
+     *
+     * @param ThirdPartyService $service
+     * @param string $checkType
+     * @param array $result
+     * @return ThirdPartyMonitor
+     */
+    protected static function recordMonitorResult(ThirdPartyService $service, string $checkType, array $result): ThirdPartyMonitor
+    {
+        return ThirdPartyMonitor::createMonitor([
+            'service_id' => $service->id,
+            'check_type' => $checkType,
+            'status' => $result['status'],
+            'response_time' => $result['response_time'],
+            'status_code' => $result['status_code'],
+            'error_message' => $result['error_message'],
+            'details' => $result['details'] ?? [],
+        ]);
+    }
+
+    /**
+     * 获取服务监控历史
+     *
+     * @param int $serviceId
+     * @param array $options
+     * @return Collection
+     */
+    public static function getServiceMonitorHistory(int $serviceId, array $options = []): Collection
+    {
+        $query = ThirdPartyMonitor::where('service_id', $serviceId);
+
+        // 时间范围过滤
+        if (isset($options['start_date'])) {
+            $query->where('checked_at', '>=', $options['start_date']);
+        }
+
+        if (isset($options['end_date'])) {
+            $query->where('checked_at', '<=', $options['end_date']);
+        }
+
+        // 检查类型过滤
+        if (isset($options['check_type'])) {
+            $query->where('check_type', $options['check_type']);
+        }
+
+        // 状态过滤
+        if (isset($options['status'])) {
+            $query->where('status', $options['status']);
+        }
+
+        // 排序和限制
+        $limit = $options['limit'] ?? 100;
+        $query->orderBy('checked_at', 'desc')->limit($limit);
+
+        return $query->get();
+    }
+
+    /**
+     * 获取监控统计信息
+     *
+     * @param int|null $serviceId
+     * @param array $options
+     * @return array
+     */
+    public static function getMonitorStats(?int $serviceId = null, array $options = []): array
+    {
+        $query = ThirdPartyMonitor::query();
+
+        if ($serviceId) {
+            $query->where('service_id', $serviceId);
+        }
+
+        // 时间范围
+        $startDate = $options['start_date'] ?? now()->subDays(7);
+        $endDate = $options['end_date'] ?? now();
+        $query->whereBetween('checked_at', [$startDate, $endDate]);
+
+        $total = $query->count();
+        $successful = $query->where('status', ThirdPartyMonitor::STATUS_SUCCESS)->count();
+        $warnings = $query->where('status', ThirdPartyMonitor::STATUS_WARNING)->count();
+        $errors = $query->whereIn('status', [
+            ThirdPartyMonitor::STATUS_ERROR,
+            ThirdPartyMonitor::STATUS_TIMEOUT
+        ])->count();
+
+        // 平均响应时间
+        $avgResponseTime = $query->whereNotNull('response_time')
+            ->avg('response_time');
+
+        // 可用性计算
+        $availability = $total > 0 ? round(($successful / $total) * 100, 2) : 0;
+
+        // 按状态统计
+        $statusStats = $query->selectRaw('status, COUNT(*) as count')
+            ->groupBy('status')
+            ->pluck('count', 'status')
+            ->toArray();
+
+        // 按检查类型统计
+        $typeStats = $query->selectRaw('check_type, COUNT(*) as count')
+            ->groupBy('check_type')
+            ->pluck('count', 'check_type')
+            ->toArray();
+
+        return [
+            'total_checks' => $total,
+            'successful' => $successful,
+            'warnings' => $warnings,
+            'errors' => $errors,
+            'availability' => $availability,
+            'avg_response_time' => $avgResponseTime ? round($avgResponseTime, 2) : null,
+            'by_status' => $statusStats,
+            'by_type' => $typeStats,
+            'period' => [
+                'start' => $startDate->toDateTimeString(),
+                'end' => $endDate->toDateTimeString(),
+            ],
+        ];
+    }
+
+    /**
+     * 获取服务可用性报告
+     *
+     * @param int $serviceId
+     * @param int $days
+     * @return array
+     */
+    public static function getAvailabilityReport(int $serviceId, int $days = 30): array
+    {
+        $service = ThirdPartyService::findOrFail($serviceId);
+        $startDate = now()->subDays($days);
+
+        $monitors = ThirdPartyMonitor::where('service_id', $serviceId)
+            ->where('checked_at', '>=', $startDate)
+            ->orderBy('checked_at')
+            ->get();
+
+        $totalChecks = $monitors->count();
+        $successfulChecks = $monitors->where('status', ThirdPartyMonitor::STATUS_SUCCESS)->count();
+        $availability = $totalChecks > 0 ? round(($successfulChecks / $totalChecks) * 100, 2) : 0;
+
+        // 按天统计
+        $dailyStats = $monitors->groupBy(function ($monitor) {
+            return $monitor->checked_at->format('Y-m-d');
+        })->map(function ($dayMonitors) {
+            $total = $dayMonitors->count();
+            $successful = $dayMonitors->where('status', ThirdPartyMonitor::STATUS_SUCCESS)->count();
+            $avgResponseTime = $dayMonitors->whereNotNull('response_time')->avg('response_time');
+
+            return [
+                'total_checks' => $total,
+                'successful_checks' => $successful,
+                'availability' => $total > 0 ? round(($successful / $total) * 100, 2) : 0,
+                'avg_response_time' => $avgResponseTime ? round($avgResponseTime, 2) : null,
+            ];
+        });
+
+        // 故障时间段
+        $downtimes = [];
+        $currentDowntime = null;
+
+        foreach ($monitors as $monitor) {
+            if ($monitor->status !== ThirdPartyMonitor::STATUS_SUCCESS) {
+                if (!$currentDowntime) {
+                    $currentDowntime = [
+                        'start' => $monitor->checked_at,
+                        'end' => $monitor->checked_at,
+                        'duration' => 0,
+                        'reason' => $monitor->error_message,
+                    ];
+                } else {
+                    $currentDowntime['end'] = $monitor->checked_at;
+                }
+            } else {
+                if ($currentDowntime) {
+                    $currentDowntime['duration'] = $currentDowntime['end']->diffInMinutes($currentDowntime['start']);
+                    $downtimes[] = $currentDowntime;
+                    $currentDowntime = null;
+                }
+            }
+        }
+
+        // 如果最后还有未结束的故障
+        if ($currentDowntime) {
+            $currentDowntime['end'] = now();
+            $currentDowntime['duration'] = $currentDowntime['end']->diffInMinutes($currentDowntime['start']);
+            $downtimes[] = $currentDowntime;
+        }
+
+        return [
+            'service' => [
+                'id' => $service->id,
+                'name' => $service->name,
+                'code' => $service->code,
+            ],
+            'period' => [
+                'days' => $days,
+                'start' => $startDate->toDateTimeString(),
+                'end' => now()->toDateTimeString(),
+            ],
+            'summary' => [
+                'total_checks' => $totalChecks,
+                'successful_checks' => $successfulChecks,
+                'availability' => $availability,
+                'downtime_count' => count($downtimes),
+                'total_downtime_minutes' => array_sum(array_column($downtimes, 'duration')),
+            ],
+            'daily_stats' => $dailyStats,
+            'downtimes' => $downtimes,
+        ];
+    }
+
+    /**
+     * 清理旧的监控记录
+     *
+     * @param int $days
+     * @return int
+     */
+    public static function cleanupOldMonitorRecords(int $days = 90): int
+    {
+        return ThirdPartyMonitor::where('checked_at', '<', now()->subDays($days))->delete();
+    }
+
+    /**
+     * 获取需要关注的服务
+     *
+     * @return array
+     */
+    public static function getServicesNeedingAttention(): array
+    {
+        $services = ThirdPartyService::where('status', 'ACTIVE')->get();
+        $needingAttention = [];
+
+        foreach ($services as $service) {
+            $issues = [];
+
+            // 检查最近的监控记录
+            $recentMonitor = ThirdPartyMonitor::where('service_id', $service->id)
+                ->orderBy('checked_at', 'desc')
+                ->first();
+
+            if (!$recentMonitor) {
+                $issues[] = '缺少监控数据';
+            } elseif ($recentMonitor->status !== ThirdPartyMonitor::STATUS_SUCCESS) {
+                $issues[] = '最近检查失败: ' . $recentMonitor->error_message;
+            }
+
+            // 检查健康检查间隔
+            if ($service->needsHealthCheck()) {
+                $issues[] = '需要健康检查';
+            }
+
+            // 检查凭证状态
+            $credential = $service->getActiveCredential();
+            if (!$credential) {
+                $issues[] = '缺少活跃凭证';
+            } elseif ($credential->isExpiringSoon()) {
+                $issues[] = '凭证即将过期';
+            }
+
+            if (!empty($issues)) {
+                $needingAttention[] = [
+                    'service' => $service,
+                    'issues' => $issues,
+                ];
+            }
+        }
+
+        return $needingAttention;
+    }
+}

+ 405 - 0
app/Module/ThirdParty/Services/QuotaService.php

@@ -0,0 +1,405 @@
+<?php
+
+namespace App\Module\ThirdParty\Services;
+
+use App\Module\ThirdParty\Models\ThirdPartyQuota;
+use App\Module\ThirdParty\Models\ThirdPartyService;
+use App\Module\ThirdParty\Enums\QUOTA_TYPE;
+use Illuminate\Support\Collection;
+
+/**
+ * 第三方服务配额管理服务类
+ */
+class QuotaService
+{
+    /**
+     * 创建配额
+     *
+     * @param array $data
+     * @return ThirdPartyQuota
+     * @throws \Exception
+     */
+    public static function create(array $data): ThirdPartyQuota
+    {
+        // 验证服务是否存在
+        $service = ThirdPartyService::find($data['service_id']);
+        if (!$service) {
+            throw new \Exception("服务不存在: {$data['service_id']}");
+        }
+
+        // 验证配额类型
+        if (!QUOTA_TYPE::tryFrom($data['type'])) {
+            throw new \Exception("无效的配额类型: {$data['type']}");
+        }
+
+        // 检查是否已存在相同类型的配额
+        $existing = ThirdPartyQuota::where('service_id', $data['service_id'])
+            ->where('type', $data['type'])
+            ->first();
+
+        if ($existing) {
+            throw new \Exception("服务已存在 {$data['type']} 类型的配额");
+        }
+
+        return ThirdPartyQuota::createQuota($data);
+    }
+
+    /**
+     * 更新配额
+     *
+     * @param int $quotaId
+     * @param array $data
+     * @return bool
+     * @throws \Exception
+     */
+    public static function update(int $quotaId, array $data): bool
+    {
+        $quota = ThirdPartyQuota::findOrFail($quotaId);
+
+        // 如果更新配额类型,验证唯一性
+        if (isset($data['type']) && $data['type'] !== $quota->type) {
+            if (!QUOTA_TYPE::tryFrom($data['type'])) {
+                throw new \Exception("无效的配额类型: {$data['type']}");
+            }
+
+            $existing = ThirdPartyQuota::where('service_id', $quota->service_id)
+                ->where('type', $data['type'])
+                ->where('id', '!=', $quotaId)
+                ->first();
+
+            if ($existing) {
+                throw new \Exception("服务已存在 {$data['type']} 类型的配额");
+            }
+        }
+
+        return $quota->update($data);
+    }
+
+    /**
+     * 删除配额
+     *
+     * @param int $quotaId
+     * @return bool
+     */
+    public static function delete(int $quotaId): bool
+    {
+        $quota = ThirdPartyQuota::findOrFail($quotaId);
+        return $quota->delete();
+    }
+
+    /**
+     * 获取配额详情
+     *
+     * @param int $quotaId
+     * @return ThirdPartyQuota
+     */
+    public static function getById(int $quotaId): ThirdPartyQuota
+    {
+        return ThirdPartyQuota::with('service')->findOrFail($quotaId);
+    }
+
+    /**
+     * 获取配额列表
+     *
+     * @param array $filters
+     * @param array $options
+     * @return Collection
+     */
+    public static function getList(array $filters = [], array $options = []): Collection
+    {
+        $query = ThirdPartyQuota::with('service');
+
+        // 应用过滤条件
+        if (isset($filters['service_id'])) {
+            $query->where('service_id', $filters['service_id']);
+        }
+
+        if (isset($filters['type'])) {
+            $query->where('type', $filters['type']);
+        }
+
+        if (isset($filters['is_active'])) {
+            $query->where('is_active', $filters['is_active']);
+        }
+
+        if (isset($filters['is_exceeded'])) {
+            $query->where('is_exceeded', $filters['is_exceeded']);
+        }
+
+        if (isset($filters['near_threshold'])) {
+            if ($filters['near_threshold']) {
+                $query->nearThreshold();
+            }
+        }
+
+        // 应用排序
+        $sortBy = $options['sort_by'] ?? 'created_at';
+        $sortOrder = $options['sort_order'] ?? 'desc';
+        $query->orderBy($sortBy, $sortOrder);
+
+        return $query->get();
+    }
+
+    /**
+     * 检查配额是否可用
+     *
+     * @param int $serviceId
+     * @param int $amount
+     * @return bool
+     */
+    public static function canUse(int $serviceId, int $amount = 1): bool
+    {
+        $quotas = ThirdPartyQuota::where('service_id', $serviceId)
+            ->where('is_active', true)
+            ->get();
+
+        foreach ($quotas as $quota) {
+            if (!$quota->canUse($amount)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * 使用配额
+     *
+     * @param int $serviceId
+     * @param int $amount
+     * @return bool
+     */
+    public static function use(int $serviceId, int $amount = 1): bool
+    {
+        $quotas = ThirdPartyQuota::where('service_id', $serviceId)
+            ->where('is_active', true)
+            ->get();
+
+        foreach ($quotas as $quota) {
+            $quota->incrementUsage($amount);
+        }
+
+        return true;
+    }
+
+    /**
+     * 重置配额
+     *
+     * @param int $quotaId
+     * @return bool
+     */
+    public static function reset(int $quotaId): bool
+    {
+        $quota = ThirdPartyQuota::findOrFail($quotaId);
+        return $quota->resetQuota();
+    }
+
+    /**
+     * 批量重置配额
+     *
+     * @param array $quotaIds
+     * @return array
+     */
+    public static function batchReset(array $quotaIds): array
+    {
+        $results = [
+            'success' => [],
+            'failed' => [],
+        ];
+
+        foreach ($quotaIds as $quotaId) {
+            try {
+                static::reset($quotaId);
+                $results['success'][] = $quotaId;
+            } catch (\Exception $e) {
+                $results['failed'][] = [
+                    'id' => $quotaId,
+                    'error' => $e->getMessage(),
+                ];
+            }
+        }
+
+        return $results;
+    }
+
+    /**
+     * 获取需要重置的配额
+     *
+     * @return Collection
+     */
+    public static function getQuotasNeedingReset(): Collection
+    {
+        return ThirdPartyQuota::where('is_active', true)
+            ->get()
+            ->filter(function ($quota) {
+                return $quota->needsReset();
+            });
+    }
+
+    /**
+     * 自动重置配额
+     *
+     * @return int 重置数量
+     */
+    public static function autoReset(): int
+    {
+        $quotas = static::getQuotasNeedingReset();
+        $resetCount = 0;
+
+        foreach ($quotas as $quota) {
+            try {
+                $quota->resetQuota();
+                $resetCount++;
+            } catch (\Exception $e) {
+                \Log::warning("自动重置配额失败: {$quota->id}, 错误: {$e->getMessage()}");
+            }
+        }
+
+        return $resetCount;
+    }
+
+    /**
+     * 获取超限的配额
+     *
+     * @return Collection
+     */
+    public static function getExceededQuotas(): Collection
+    {
+        return ThirdPartyQuota::with('service')
+            ->where('is_active', true)
+            ->where('is_exceeded', true)
+            ->get();
+    }
+
+    /**
+     * 获取接近阈值的配额
+     *
+     * @return Collection
+     */
+    public static function getNearThresholdQuotas(): Collection
+    {
+        return ThirdPartyQuota::with('service')
+            ->where('is_active', true)
+            ->nearThreshold()
+            ->get();
+    }
+
+    /**
+     * 获取配额统计信息
+     *
+     * @param int|null $serviceId
+     * @return array
+     */
+    public static function getStats(?int $serviceId = null): array
+    {
+        $query = ThirdPartyQuota::query();
+
+        if ($serviceId) {
+            $query->where('service_id', $serviceId);
+        }
+
+        $total = $query->count();
+        $active = $query->where('is_active', true)->count();
+        $exceeded = $query->where('is_exceeded', true)->count();
+        $nearThreshold = $query->nearThreshold()->count();
+
+        // 按类型统计
+        $typeStats = $query->selectRaw('type, COUNT(*) as count')
+            ->groupBy('type')
+            ->pluck('count', 'type')
+            ->toArray();
+
+        // 使用率统计
+        $usageStats = $query->where('is_active', true)
+            ->get()
+            ->map(function ($quota) {
+                return [
+                    'service_name' => $quota->service->name,
+                    'type' => $quota->type,
+                    'usage_percentage' => $quota->getUsagePercentage(),
+                    'status' => $quota->getStatus(),
+                ];
+            })
+            ->toArray();
+
+        return [
+            'total' => $total,
+            'active' => $active,
+            'exceeded' => $exceeded,
+            'near_threshold' => $nearThreshold,
+            'by_type' => $typeStats,
+            'usage_stats' => $usageStats,
+            'health_rate' => $total > 0 ? round((($active - $exceeded) / $total) * 100, 2) : 0,
+        ];
+    }
+
+    /**
+     * 创建默认配额
+     *
+     * @param int $serviceId
+     * @return array
+     */
+    public static function createDefaultQuotas(int $serviceId): array
+    {
+        $service = ThirdPartyService::findOrFail($serviceId);
+        $defaultLimits = config('thirdparty.quota.default_limits', []);
+        $created = [];
+
+        foreach ($defaultLimits as $type => $limit) {
+            try {
+                $quota = static::create([
+                    'service_id' => $serviceId,
+                    'type' => $type,
+                    'limit_value' => $limit,
+                    'is_active' => true,
+                ]);
+                $created[] = $quota;
+            } catch (\Exception $e) {
+                // 如果配额已存在,跳过
+                if (!str_contains($e->getMessage(), '已存在')) {
+                    throw $e;
+                }
+            }
+        }
+
+        return $created;
+    }
+
+    /**
+     * 导出配额配置
+     *
+     * @param array $quotaIds
+     * @return array
+     */
+    public static function export(array $quotaIds = []): array
+    {
+        $query = ThirdPartyQuota::with('service');
+        
+        if (!empty($quotaIds)) {
+            $query->whereIn('id', $quotaIds);
+        }
+
+        $quotas = $query->get();
+        $exported = [];
+
+        foreach ($quotas as $quota) {
+            $exported[] = [
+                'id' => $quota->id,
+                'service_name' => $quota->service->name,
+                'service_code' => $quota->service->code,
+                'type' => $quota->type,
+                'type_label' => $quota->getTypeLabel(),
+                'limit_value' => $quota->limit_value,
+                'used_value' => $quota->used_value,
+                'usage_percentage' => $quota->getUsagePercentage(),
+                'is_active' => $quota->is_active,
+                'alert_threshold' => $quota->alert_threshold,
+                'is_exceeded' => $quota->is_exceeded,
+                'status' => $quota->getStatusLabel(),
+                'created_at' => $quota->created_at->toDateTimeString(),
+            ];
+        }
+
+        return $exported;
+    }
+}

+ 162 - 0
app/Module/ThirdParty/Validations/ServiceValidation.php

@@ -0,0 +1,162 @@
+<?php
+
+namespace App\Module\ThirdParty\Validations;
+
+use App\Module\ThirdParty\Validators\ServiceValidator;
+
+/**
+ * 第三方服务验证规则类
+ */
+class ServiceValidation
+{
+    /**
+     * 服务验证器实例
+     *
+     * @var ServiceValidator
+     */
+    protected ServiceValidator $validator;
+
+    /**
+     * 构造函数
+     */
+    public function __construct()
+    {
+        $this->validator = new ServiceValidator();
+    }
+
+    /**
+     * 验证创建服务
+     *
+     * @param array $data
+     * @return void
+     * @throws \Exception
+     */
+    public function validateCreate(array $data): void
+    {
+        if (!$this->validator->validateCreate($data)) {
+            $this->throwValidationException();
+        }
+    }
+
+    /**
+     * 验证更新服务
+     *
+     * @param array $data
+     * @param int $serviceId
+     * @return void
+     * @throws \Exception
+     */
+    public function validateUpdate(array $data, int $serviceId): void
+    {
+        if (!$this->validator->validateUpdate($data, $serviceId)) {
+            $this->throwValidationException();
+        }
+    }
+
+    /**
+     * 验证状态更新
+     *
+     * @param string $status
+     * @param int $serviceId
+     * @return void
+     * @throws \Exception
+     */
+    public function validateStatusUpdate(string $status, int $serviceId): void
+    {
+        if (!$this->validator->validateStatusUpdate($status, $serviceId)) {
+            $this->throwValidationException();
+        }
+    }
+
+    /**
+     * 验证删除服务
+     *
+     * @param int $serviceId
+     * @return void
+     * @throws \Exception
+     */
+    public function validateDelete(int $serviceId): void
+    {
+        if (!$this->validator->validateDelete($serviceId)) {
+            $this->throwValidationException();
+        }
+    }
+
+    /**
+     * 抛出验证异常
+     *
+     * @return void
+     * @throws \Exception
+     */
+    protected function throwValidationException(): void
+    {
+        $errors = $this->validator->getErrors();
+        $firstError = $this->validator->getFirstError();
+        
+        throw new \Exception($firstError ?: '验证失败');
+    }
+
+    /**
+     * 获取验证器
+     *
+     * @return ServiceValidator
+     */
+    public function getValidator(): ServiceValidator
+    {
+        return $this->validator;
+    }
+
+    /**
+     * 静态验证创建服务
+     *
+     * @param array $data
+     * @return void
+     * @throws \Exception
+     */
+    public static function create(array $data): void
+    {
+        $validation = new static();
+        $validation->validateCreate($data);
+    }
+
+    /**
+     * 静态验证更新服务
+     *
+     * @param array $data
+     * @param int $serviceId
+     * @return void
+     * @throws \Exception
+     */
+    public static function update(array $data, int $serviceId): void
+    {
+        $validation = new static();
+        $validation->validateUpdate($data, $serviceId);
+    }
+
+    /**
+     * 静态验证状态更新
+     *
+     * @param string $status
+     * @param int $serviceId
+     * @return void
+     * @throws \Exception
+     */
+    public static function statusUpdate(string $status, int $serviceId): void
+    {
+        $validation = new static();
+        $validation->validateStatusUpdate($status, $serviceId);
+    }
+
+    /**
+     * 静态验证删除
+     *
+     * @param int $serviceId
+     * @return void
+     * @throws \Exception
+     */
+    public static function delete(int $serviceId): void
+    {
+        $validation = new static();
+        $validation->validateDelete($serviceId);
+    }
+}

+ 386 - 0
app/Module/ThirdParty/Validators/CredentialValidator.php

@@ -0,0 +1,386 @@
+<?php
+
+namespace App\Module\ThirdParty\Validators;
+
+use App\Module\ThirdParty\Models\ThirdPartyService;
+use App\Module\ThirdParty\Models\ThirdPartyCredential;
+use App\Module\ThirdParty\Enums\AUTH_TYPE;
+
+/**
+ * 第三方服务认证凭证验证器
+ */
+class CredentialValidator
+{
+    /**
+     * 错误信息
+     *
+     * @var array
+     */
+    protected array $errors = [];
+
+    /**
+     * 验证创建凭证数据
+     *
+     * @param array $data
+     * @return bool
+     */
+    public function validateCreate(array $data): bool
+    {
+        $this->errors = [];
+
+        // 验证必填字段
+        $this->validateRequired($data, [
+            'service_id' => '服务ID',
+            'name' => '凭证名称',
+            'type' => '认证类型',
+            'credentials' => '凭证信息',
+        ]);
+
+        // 验证字段格式
+        $this->validateCredentialData($data);
+
+        // 验证服务和认证类型匹配
+        if (isset($data['service_id']) && isset($data['type'])) {
+            $this->validateServiceAuthType($data['service_id'], $data['type']);
+        }
+
+        // 验证环境下的唯一性
+        if (isset($data['service_id']) && isset($data['environment']) && ($data['is_active'] ?? true)) {
+            $this->validateEnvironmentUnique($data['service_id'], $data['environment']);
+        }
+
+        return empty($this->errors);
+    }
+
+    /**
+     * 验证更新凭证数据
+     *
+     * @param array $data
+     * @param int $credentialId
+     * @return bool
+     */
+    public function validateUpdate(array $data, int $credentialId): bool
+    {
+        $this->errors = [];
+
+        // 验证字段格式
+        $this->validateCredentialData($data);
+
+        $credential = ThirdPartyCredential::find($credentialId);
+        if (!$credential) {
+            $this->addError('credential', '凭证不存在');
+            return false;
+        }
+
+        // 验证服务和认证类型匹配
+        if (isset($data['type'])) {
+            $this->validateServiceAuthType($credential->service_id, $data['type']);
+        }
+
+        // 验证环境下的唯一性
+        if (isset($data['is_active']) && $data['is_active']) {
+            $environment = $data['environment'] ?? $credential->environment;
+            $this->validateEnvironmentUnique($credential->service_id, $environment, $credentialId);
+        }
+
+        return empty($this->errors);
+    }
+
+    /**
+     * 验证删除凭证
+     *
+     * @param int $credentialId
+     * @return bool
+     */
+    public function validateDelete(int $credentialId): bool
+    {
+        $this->errors = [];
+
+        $credential = ThirdPartyCredential::find($credentialId);
+        if (!$credential) {
+            $this->addError('credential', '凭证不存在');
+            return false;
+        }
+
+        // 检查凭证状态
+        if ($credential->is_active) {
+            $this->addError('status', '激活状态的凭证无法删除,请先停用凭证');
+        }
+
+        // 检查最近使用记录
+        if ($credential->last_used_at && $credential->last_used_at->gt(now()->subHours(1))) {
+            $this->addError('usage', '凭证在1小时内有使用记录,无法删除');
+        }
+
+        return empty($this->errors);
+    }
+
+    /**
+     * 验证凭证激活
+     *
+     * @param int $credentialId
+     * @return bool
+     */
+    public function validateActivation(int $credentialId): bool
+    {
+        $this->errors = [];
+
+        $credential = ThirdPartyCredential::find($credentialId);
+        if (!$credential) {
+            $this->addError('credential', '凭证不存在');
+            return false;
+        }
+
+        // 检查凭证是否过期
+        if ($credential->isExpired()) {
+            $this->addError('expired', '凭证已过期,无法激活');
+        }
+
+        // 检查同环境下是否已有其他激活的凭证
+        $existingActive = ThirdPartyCredential::where('service_id', $credential->service_id)
+            ->where('environment', $credential->environment)
+            ->where('is_active', true)
+            ->where('id', '!=', $credentialId)
+            ->exists();
+
+        if ($existingActive) {
+            $this->addError('environment', "环境 {$credential->environment} 下已有其他激活的凭证,请先停用其他凭证");
+        }
+
+        return empty($this->errors);
+    }
+
+    /**
+     * 验证凭证数据
+     *
+     * @param array $data
+     * @return void
+     */
+    protected function validateCredentialData(array $data): void
+    {
+        // 验证认证类型
+        if (isset($data['type']) && !AUTH_TYPE::tryFrom($data['type'])) {
+            $this->addError('type', '不支持的认证类型');
+        }
+
+        // 验证名称长度
+        if (isset($data['name'])) {
+            if (strlen($data['name']) < 2) {
+                $this->addError('name', '凭证名称至少需要2个字符');
+            }
+            if (strlen($data['name']) > 100) {
+                $this->addError('name', '凭证名称不能超过100个字符');
+            }
+        }
+
+        // 验证环境
+        if (isset($data['environment'])) {
+            $validEnvironments = ['production', 'staging', 'development', 'testing'];
+            if (!in_array($data['environment'], $validEnvironments)) {
+                $this->addError('environment', '无效的环境类型');
+            }
+        }
+
+        // 验证过期时间
+        if (isset($data['expires_at']) && !empty($data['expires_at'])) {
+            try {
+                $expiresAt = new \DateTime($data['expires_at']);
+                if ($expiresAt <= now()) {
+                    $this->addError('expires_at', '过期时间必须在未来');
+                }
+            } catch (\Exception $e) {
+                $this->addError('expires_at', '过期时间格式无效');
+            }
+        }
+
+        // 验证凭证信息
+        if (isset($data['credentials']) && isset($data['type'])) {
+            $this->validateCredentialsData($data['type'], $data['credentials']);
+        }
+    }
+
+    /**
+     * 验证凭证信息数据
+     *
+     * @param string $authType
+     * @param array $credentials
+     * @return void
+     */
+    protected function validateCredentialsData(string $authType, array $credentials): void
+    {
+        $authTypeEnum = AUTH_TYPE::tryFrom($authType);
+        if (!$authTypeEnum) {
+            return;
+        }
+
+        $requiredFields = $authTypeEnum->getRequiredFields();
+        $missingFields = [];
+
+        foreach ($requiredFields as $field) {
+            if (empty($credentials[$field])) {
+                $missingFields[] = $field;
+            }
+        }
+
+        if (!empty($missingFields)) {
+            $this->addError('credentials', '凭证配置不完整,缺少字段: ' . implode(', ', $missingFields));
+        }
+
+        // 验证特定字段格式
+        $this->validateSpecificCredentialFields($authTypeEnum, $credentials);
+    }
+
+    /**
+     * 验证特定凭证字段格式
+     *
+     * @param AUTH_TYPE $authType
+     * @param array $credentials
+     * @return void
+     */
+    protected function validateSpecificCredentialFields(AUTH_TYPE $authType, array $credentials): void
+    {
+        switch ($authType) {
+            case AUTH_TYPE::API_KEY:
+                if (isset($credentials['api_key']) && strlen($credentials['api_key']) < 10) {
+                    $this->addError('credentials', 'API Key长度至少需要10个字符');
+                }
+                break;
+
+            case AUTH_TYPE::OAUTH2:
+                if (isset($credentials['client_id']) && strlen($credentials['client_id']) < 5) {
+                    $this->addError('credentials', 'Client ID长度至少需要5个字符');
+                }
+                if (isset($credentials['client_secret']) && strlen($credentials['client_secret']) < 10) {
+                    $this->addError('credentials', 'Client Secret长度至少需要10个字符');
+                }
+                break;
+
+            case AUTH_TYPE::JWT:
+                if (isset($credentials['jwt_token'])) {
+                    $parts = explode('.', $credentials['jwt_token']);
+                    if (count($parts) !== 3) {
+                        $this->addError('credentials', 'JWT Token格式无效');
+                    }
+                }
+                break;
+
+            case AUTH_TYPE::BASIC:
+                if (isset($credentials['username']) && strlen($credentials['username']) < 2) {
+                    $this->addError('credentials', '用户名长度至少需要2个字符');
+                }
+                if (isset($credentials['password']) && strlen($credentials['password']) < 6) {
+                    $this->addError('credentials', '密码长度至少需要6个字符');
+                }
+                break;
+        }
+    }
+
+    /**
+     * 验证服务和认证类型匹配
+     *
+     * @param int $serviceId
+     * @param string $authType
+     * @return void
+     */
+    protected function validateServiceAuthType(int $serviceId, string $authType): void
+    {
+        $service = ThirdPartyService::find($serviceId);
+        if (!$service) {
+            $this->addError('service_id', '服务不存在');
+            return;
+        }
+
+        if ($authType !== $service->auth_type) {
+            $this->addError('type', "认证类型 {$authType} 与服务要求的 {$service->auth_type} 不匹配");
+        }
+    }
+
+    /**
+     * 验证环境下的唯一性
+     *
+     * @param int $serviceId
+     * @param string $environment
+     * @param int|null $excludeId
+     * @return void
+     */
+    protected function validateEnvironmentUnique(int $serviceId, string $environment, ?int $excludeId = null): void
+    {
+        $query = ThirdPartyCredential::where('service_id', $serviceId)
+            ->where('environment', $environment)
+            ->where('is_active', true);
+
+        if ($excludeId) {
+            $query->where('id', '!=', $excludeId);
+        }
+
+        if ($query->exists()) {
+            $this->addError('environment', "环境 {$environment} 下已有激活的凭证,请先停用现有凭证");
+        }
+    }
+
+    /**
+     * 验证必填字段
+     *
+     * @param array $data
+     * @param array $required
+     * @return void
+     */
+    protected function validateRequired(array $data, array $required): void
+    {
+        foreach ($required as $field => $label) {
+            if (!isset($data[$field]) || empty($data[$field])) {
+                $this->addError($field, "{$label}不能为空");
+            }
+        }
+    }
+
+    /**
+     * 添加错误信息
+     *
+     * @param string $field
+     * @param string $message
+     * @return void
+     */
+    protected function addError(string $field, string $message): void
+    {
+        if (!isset($this->errors[$field])) {
+            $this->errors[$field] = [];
+        }
+        $this->errors[$field][] = $message;
+    }
+
+    /**
+     * 获取错误信息
+     *
+     * @return array
+     */
+    public function getErrors(): array
+    {
+        return $this->errors;
+    }
+
+    /**
+     * 获取第一个错误信息
+     *
+     * @return string|null
+     */
+    public function getFirstError(): ?string
+    {
+        if (empty($this->errors)) {
+            return null;
+        }
+
+        $firstField = array_key_first($this->errors);
+        return $this->errors[$firstField][0] ?? null;
+    }
+
+    /**
+     * 检查是否有错误
+     *
+     * @return bool
+     */
+    public function hasErrors(): bool
+    {
+        return !empty($this->errors);
+    }
+}

+ 395 - 0
app/Module/ThirdParty/Validators/ServiceValidator.php

@@ -0,0 +1,395 @@
+<?php
+
+namespace App\Module\ThirdParty\Validators;
+
+use App\Module\ThirdParty\Models\ThirdPartyService;
+use App\Module\ThirdParty\Enums\SERVICE_TYPE;
+use App\Module\ThirdParty\Enums\AUTH_TYPE;
+use App\Module\ThirdParty\Enums\SERVICE_STATUS;
+
+/**
+ * 第三方服务验证器
+ */
+class ServiceValidator
+{
+    /**
+     * 错误信息
+     *
+     * @var array
+     */
+    protected array $errors = [];
+
+    /**
+     * 验证创建服务数据
+     *
+     * @param array $data
+     * @return bool
+     */
+    public function validateCreate(array $data): bool
+    {
+        $this->errors = [];
+
+        // 验证必填字段
+        $this->validateRequired($data, [
+            'name' => '服务名称',
+            'type' => '服务类型',
+            'provider' => '服务提供商',
+            'auth_type' => '认证类型',
+        ]);
+
+        // 验证字段格式
+        $this->validateServiceData($data);
+
+        // 验证代码唯一性
+        if (isset($data['code'])) {
+            $this->validateCodeUnique($data['code']);
+        }
+
+        return empty($this->errors);
+    }
+
+    /**
+     * 验证更新服务数据
+     *
+     * @param array $data
+     * @param int $serviceId
+     * @return bool
+     */
+    public function validateUpdate(array $data, int $serviceId): bool
+    {
+        $this->errors = [];
+
+        // 验证字段格式
+        $this->validateServiceData($data);
+
+        // 验证代码唯一性(排除当前服务)
+        if (isset($data['code'])) {
+            $this->validateCodeUnique($data['code'], $serviceId);
+        }
+
+        return empty($this->errors);
+    }
+
+    /**
+     * 验证服务状态更新
+     *
+     * @param string $status
+     * @param int $serviceId
+     * @return bool
+     */
+    public function validateStatusUpdate(string $status, int $serviceId): bool
+    {
+        $this->errors = [];
+
+        // 验证状态值
+        if (!SERVICE_STATUS::tryFrom($status)) {
+            $this->addError('status', '无效的服务状态');
+            return false;
+        }
+
+        $service = ThirdPartyService::find($serviceId);
+        if (!$service) {
+            $this->addError('service', '服务不存在');
+            return false;
+        }
+
+        $newStatus = SERVICE_STATUS::from($status);
+        $currentStatus = SERVICE_STATUS::from($service->status);
+
+        // 验证状态转换
+        if (!$currentStatus->canTransitionTo($newStatus)) {
+            $this->addError('status', "不能从状态 {$currentStatus->getLabel()} 转换到 {$newStatus->getLabel()}");
+        }
+
+        // 如果要激活服务,检查前置条件
+        if ($newStatus === SERVICE_STATUS::ACTIVE) {
+            $this->validateActivationRequirements($service);
+        }
+
+        return empty($this->errors);
+    }
+
+    /**
+     * 验证服务删除
+     *
+     * @param int $serviceId
+     * @return bool
+     */
+    public function validateDelete(int $serviceId): bool
+    {
+        $this->errors = [];
+
+        $service = ThirdPartyService::find($serviceId);
+        if (!$service) {
+            $this->addError('service', '服务不存在');
+            return false;
+        }
+
+        // 检查服务状态
+        if ($service->status === SERVICE_STATUS::ACTIVE->value) {
+            $this->addError('status', '活跃状态的服务无法删除,请先停用服务');
+        }
+
+        // 检查是否有活跃的凭证
+        $activeCredentials = $service->credentials()->where('is_active', true)->count();
+        if ($activeCredentials > 0) {
+            $this->addError('credentials', "服务有 {$activeCredentials} 个活跃凭证,无法删除");
+        }
+
+        // 检查是否有最近的调用记录
+        $recentLogs = $service->logs()->where('created_at', '>', now()->subHours(1))->count();
+        if ($recentLogs > 0) {
+            $this->addError('logs', '服务在1小时内有调用记录,无法删除');
+        }
+
+        return empty($this->errors);
+    }
+
+    /**
+     * 验证服务数据
+     *
+     * @param array $data
+     * @return void
+     */
+    protected function validateServiceData(array $data): void
+    {
+        // 验证服务类型
+        if (isset($data['type']) && !SERVICE_TYPE::tryFrom($data['type'])) {
+            $this->addError('type', '不支持的服务类型');
+        }
+
+        // 验证认证类型
+        if (isset($data['auth_type']) && !AUTH_TYPE::tryFrom($data['auth_type'])) {
+            $this->addError('auth_type', '不支持的认证类型');
+        }
+
+        // 验证服务状态
+        if (isset($data['status']) && !SERVICE_STATUS::tryFrom($data['status'])) {
+            $this->addError('status', '无效的服务状态');
+        }
+
+        // 验证名称长度
+        if (isset($data['name'])) {
+            if (strlen($data['name']) < 2) {
+                $this->addError('name', '服务名称至少需要2个字符');
+            }
+            if (strlen($data['name']) > 100) {
+                $this->addError('name', '服务名称不能超过100个字符');
+            }
+        }
+
+        // 验证代码格式
+        if (isset($data['code'])) {
+            if (!preg_match('/^[a-z0-9_]+$/', $data['code'])) {
+                $this->addError('code', '服务代码只能包含小写字母、数字和下划线');
+            }
+            if (strlen($data['code']) < 3) {
+                $this->addError('code', '服务代码至少需要3个字符');
+            }
+            if (strlen($data['code']) > 50) {
+                $this->addError('code', '服务代码不能超过50个字符');
+            }
+        }
+
+        // 验证提供商
+        if (isset($data['provider'])) {
+            if (strlen($data['provider']) < 2) {
+                $this->addError('provider', '服务提供商至少需要2个字符');
+            }
+            if (strlen($data['provider']) > 50) {
+                $this->addError('provider', '服务提供商不能超过50个字符');
+            }
+        }
+
+        // 验证URL格式
+        if (isset($data['base_url']) && !empty($data['base_url'])) {
+            if (!filter_var($data['base_url'], FILTER_VALIDATE_URL)) {
+                $this->addError('base_url', '基础URL格式无效');
+            }
+        }
+
+        if (isset($data['health_check_url']) && !empty($data['health_check_url'])) {
+            if (!filter_var($data['health_check_url'], FILTER_VALIDATE_URL)) {
+                $this->addError('health_check_url', '健康检查URL格式无效');
+            }
+        }
+
+        if (isset($data['webhook_url']) && !empty($data['webhook_url'])) {
+            if (!filter_var($data['webhook_url'], FILTER_VALIDATE_URL)) {
+                $this->addError('webhook_url', 'Webhook URL格式无效');
+            }
+        }
+
+        // 验证数值范围
+        if (isset($data['timeout'])) {
+            if (!is_numeric($data['timeout']) || $data['timeout'] < 1 || $data['timeout'] > 300) {
+                $this->addError('timeout', '超时时间必须在1-300秒之间');
+            }
+        }
+
+        if (isset($data['retry_times'])) {
+            if (!is_numeric($data['retry_times']) || $data['retry_times'] < 0 || $data['retry_times'] > 10) {
+                $this->addError('retry_times', '重试次数必须在0-10次之间');
+            }
+        }
+
+        if (isset($data['retry_delay'])) {
+            if (!is_numeric($data['retry_delay']) || $data['retry_delay'] < 100 || $data['retry_delay'] > 60000) {
+                $this->addError('retry_delay', '重试延迟必须在100-60000毫秒之间');
+            }
+        }
+
+        if (isset($data['priority'])) {
+            if (!is_numeric($data['priority']) || $data['priority'] < 0 || $data['priority'] > 999) {
+                $this->addError('priority', '优先级必须在0-999之间');
+            }
+        }
+
+        if (isset($data['health_check_interval'])) {
+            if (!is_numeric($data['health_check_interval']) || $data['health_check_interval'] < 60 || $data['health_check_interval'] > 86400) {
+                $this->addError('health_check_interval', '健康检查间隔必须在60-86400秒之间');
+            }
+        }
+
+        // 验证JSON格式
+        if (isset($data['config']) && !empty($data['config'])) {
+            if (is_string($data['config'])) {
+                json_decode($data['config']);
+                if (json_last_error() !== JSON_ERROR_NONE) {
+                    $this->addError('config', '配置信息必须是有效的JSON格式');
+                }
+            }
+        }
+
+        if (isset($data['headers']) && !empty($data['headers'])) {
+            if (is_string($data['headers'])) {
+                json_decode($data['headers']);
+                if (json_last_error() !== JSON_ERROR_NONE) {
+                    $this->addError('headers', '请求头必须是有效的JSON格式');
+                }
+            }
+        }
+
+        if (isset($data['params']) && !empty($data['params'])) {
+            if (is_string($data['params'])) {
+                json_decode($data['params']);
+                if (json_last_error() !== JSON_ERROR_NONE) {
+                    $this->addError('params', '默认参数必须是有效的JSON格式');
+                }
+            }
+        }
+    }
+
+    /**
+     * 验证激活服务的前置条件
+     *
+     * @param ThirdPartyService $service
+     * @return void
+     */
+    protected function validateActivationRequirements(ThirdPartyService $service): void
+    {
+        // 检查是否有可用的凭证
+        $hasActiveCredential = $service->credentials()
+            ->where('is_active', true)
+            ->where(function ($query) {
+                $query->whereNull('expires_at')
+                    ->orWhere('expires_at', '>', now());
+            })
+            ->exists();
+
+        if (!$hasActiveCredential) {
+            $this->addError('credentials', '服务没有可用的认证凭证,无法激活');
+        }
+
+        // 检查基础配置
+        if (empty($service->base_url)) {
+            $this->addError('base_url', '服务缺少基础URL配置,无法激活');
+        }
+    }
+
+    /**
+     * 验证代码唯一性
+     *
+     * @param string $code
+     * @param int|null $excludeId
+     * @return void
+     */
+    protected function validateCodeUnique(string $code, ?int $excludeId = null): void
+    {
+        $query = ThirdPartyService::where('code', $code);
+        
+        if ($excludeId) {
+            $query->where('id', '!=', $excludeId);
+        }
+
+        if ($query->exists()) {
+            $this->addError('code', '服务代码已存在');
+        }
+    }
+
+    /**
+     * 验证必填字段
+     *
+     * @param array $data
+     * @param array $required
+     * @return void
+     */
+    protected function validateRequired(array $data, array $required): void
+    {
+        foreach ($required as $field => $label) {
+            if (!isset($data[$field]) || empty($data[$field])) {
+                $this->addError($field, "{$label}不能为空");
+            }
+        }
+    }
+
+    /**
+     * 添加错误信息
+     *
+     * @param string $field
+     * @param string $message
+     * @return void
+     */
+    protected function addError(string $field, string $message): void
+    {
+        if (!isset($this->errors[$field])) {
+            $this->errors[$field] = [];
+        }
+        $this->errors[$field][] = $message;
+    }
+
+    /**
+     * 获取错误信息
+     *
+     * @return array
+     */
+    public function getErrors(): array
+    {
+        return $this->errors;
+    }
+
+    /**
+     * 获取第一个错误信息
+     *
+     * @return string|null
+     */
+    public function getFirstError(): ?string
+    {
+        if (empty($this->errors)) {
+            return null;
+        }
+
+        $firstField = array_key_first($this->errors);
+        return $this->errors[$firstField][0] ?? null;
+    }
+
+    /**
+     * 检查是否有错误
+     *
+     * @return bool
+     */
+    public function hasErrors(): bool
+    {
+        return !empty($this->errors);
+    }
+}

+ 12 - 0
app/PlugIn/Uraus/readme.md

@@ -0,0 +1,12 @@
+# ueaus
+
+1. 获取用户信息 ,用户 ID,昵称 
+2. 获取用户上级关系链表 ,各个等级的用户id
+3. 获取下级人数;参数 直推/团队(3代) ;返回: 人数
+
+
+## webhook (得有重试机制)
+1. 注册通知,传入: uid, 三级 上级;返回: 处理成功
+2. 出包, 参:用户ID,usdt数量 ;返回: 成功/失败 
+3. 入包, 参数:用户ID,usdt数量 ;返回: 成功/失败 
+4. 入包检查,参数:用户ID,usdt数量 ;返回: 是否允许,钻石余额 ,本金总数,手续费总数,所需总数