Browse Source

feat:后台-异常管理

刘学玺 3 months ago
parent
commit
daddf54ee9

+ 80 - 0
app/Admin/Controllers/SystemExceptionController.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Services\SystemExceptionService;
+use Slowlyo\OwlAdmin\Controllers\AdminController;
+
+/**
+ * 系统异常管理
+ */
+class SystemExceptionController extends AdminController
+{
+    protected string $serviceName = SystemExceptionService::class;
+
+    public function list()
+    {
+        $crud = $this->baseCRUD()
+            ->filterTogglable(false)
+            ->headerToolbar([
+                amis()->Button()->label('批量AI分析')->actionType('ajax')->api('POST:/admin-api/system-exceptions/batch-analyze'),
+                ...$this->baseHeaderToolBar()
+            ])
+            ->columns([
+                amis()->TableColumn('id', 'ID')->sortable(),
+                amis()->TableColumn('type', '异常类型'),
+                amis()->TableColumn('message', '异常信息')->type('text'),
+                amis()->TableColumn('file', '文件路径'),
+                amis()->TableColumn('line', '行号'),
+                amis()->TableColumn('status', '状态')
+                    ->type('status')
+                    ->labels([
+                        '未处理' => 'danger',
+                        '处理中' => 'warning',
+                        '已处理' => 'success',
+                        '忽略' => 'default'
+                    ]),
+                amis()->TableColumn('solution', 'AI解决方案')->type('text'),
+                amis()->TableColumn('handler', '处理人'),
+                amis()->TableColumn('handled_at', '处理时间')->type('datetime'),
+                amis()->TableColumn('created_at', '创建时间')->type('datetime')->sortable(),
+                amis()->Operation()->label('操作')->buttons([
+                    amis()->Button()
+                        ->label('AI分析')
+                        ->level('primary')
+                        ->actionType('ajax')
+                        ->api('POST:/admin-api/system-exceptions/${id}/analyze'),
+                    amis()->Button()
+                        ->label('标记处理')
+                        ->level('success')
+                        ->actionType('ajax')
+                        ->api('POST:/admin-api/system-exceptions/${id}/mark-handled'),
+                    amis()->Button()
+                        ->label('忽略')
+                        ->level('default')
+                        ->actionType('ajax')
+                        ->api('POST:/admin-api/system-exceptions/${id}/ignore'),
+                ])
+            ]);
+
+        return $this->baseList($crud);
+    }
+
+    public function detail()
+    {
+        return $this->baseDetail()->body([
+            amis()->TextControl('id', 'ID')->static(),
+            amis()->TextControl('type', '异常类型')->static(),
+            amis()->TextareaControl('message', '异常信息')->static(),
+            amis()->TextControl('file', '文件路径')->static(),
+            amis()->TextControl('line', '行号')->static(),
+            amis()->TextareaControl('trace', '堆栈信息')->static(),
+            amis()->JsonControl('request_data', '请求数据')->static(),
+            amis()->TextControl('status', '状态')->static(),
+            amis()->TextareaControl('solution', 'AI解决方案')->static(),
+            amis()->TextControl('handler', '处理人')->static(),
+            amis()->TextControl('handled_at', '处理时间')->static(),
+            amis()->TextControl('created_at', '创建时间')->static(),
+        ]);
+    }
+}

+ 24 - 1
app/Exceptions/Handler.php

@@ -5,6 +5,7 @@ namespace App\Exceptions;
 use Exception;
 use Throwable;
 use Illuminate\Support\Facades\Log;
+use App\Support\AsyncExceptionRecorder;
 use Illuminate\Auth\AuthenticationException;
 use Illuminate\Session\TokenMismatchException;
 use Illuminate\Validation\ValidationException;
@@ -65,8 +66,30 @@ class Handler extends ExceptionHandler
      */
     public function register(): void
     {
-        // 记录所有异常
+        // 记录所有异常到数据库(异步)
         $this->reportable(function (Throwable $e) {
+            // 预处理请求数据,确保可以被 JSON 序列化
+            $requestData = [
+                'url' => request()->fullUrl(),
+                'method' => request()->method(),
+                'params' => json_decode(json_encode(request()->all(), JSON_INVALID_UTF8_SUBSTITUTE), true) ?? [],
+                'headers' => array_map(function ($item) {
+                    return is_array($item) ? implode(', ', $item) : $item;
+                }, request()->headers->all()),
+            ];
+
+            // 异步分发任务
+            AsyncExceptionRecorder::dispatch(
+                get_class($e),
+                $e->getMessage(),
+                $e->getFile(),
+                $e->getLine(),
+                $e->getTraceAsString(),
+                $requestData,  // 使用处理过的数据
+                auth()->id()
+            );
+
+            // 记录日志
             if ($e instanceof ValidationException) {
                 $this->logException($e, 'info');
             } else {

+ 166 - 0
app/Models/SystemException.php

@@ -0,0 +1,166 @@
+<?php
+
+namespace App\Models;
+
+use Slowlyo\OwlAdmin\Models\AdminUser;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+
+/**
+ * 系统异常记录模型
+ */
+class SystemException extends Model
+{
+    use SoftDeletes;
+
+    protected $table = 'system_exceptions';
+
+    protected $guarded = [];
+
+    protected $casts = [
+        'request_data' => 'json',
+        'handled_at' => 'datetime',
+        'status' => 'integer',
+    ];
+
+    /**
+     * 状态常量
+     */
+    const STATUS_PENDING = 0;    // 未处理
+    const STATUS_PROCESSING = 1;  // 处理中
+    const STATUS_HANDLED = 2;     // 已处理
+    const STATUS_IGNORED = 3;     // 已忽略
+
+    /**
+     * 获取状态文本
+     *
+     * @return string
+     */
+    public function getStatusTextAttribute(): string
+    {
+        return match ($this->status) {
+            self::STATUS_PENDING => '未处理',
+            self::STATUS_PROCESSING => '处理中',
+            self::STATUS_HANDLED => '已处理',
+            self::STATUS_IGNORED => '已忽略',
+            default => '未知状态',
+        };
+    }
+
+    /**
+     * 获取处理人关联
+     */
+    public function handlerUser()
+    {
+        return $this->belongsTo(AdminUser::class, 'handler', 'name');
+    }
+
+    /**
+     * 获取相关用户
+     */
+    public function user()
+    {
+        return $this->belongsTo(MemberUser::class, 'user_id');
+    }
+
+    /**
+     * 创建异常记录
+     *
+     * @param \Throwable $exception 异常对象
+     * @param array $requestData 请求数据
+     * @param int|null $userId 用户ID
+     * @return static
+     */
+    public static function createFromException(\Throwable $exception, array $requestData = [], ?int $userId = null): static
+    {
+        return static::create([
+            'type' => get_class($exception),
+            'message' => $exception->getMessage(),
+            'file' => $exception->getFile(),
+            'line' => $exception->getLine(),
+            'trace' => $exception->getTraceAsString(),
+            'request_data' => $requestData,
+            'user_id' => $userId,
+            'status' => self::STATUS_PENDING,
+        ]);
+    }
+
+    /**
+     * 标记为处理中
+     *
+     * @return bool
+     */
+    public function markAsProcessing(): bool
+    {
+        return $this->update(['status' => self::STATUS_PROCESSING]);
+    }
+
+    /**
+     * 标记为已处理
+     *
+     * @param string $handler 处理人
+     * @return bool
+     */
+    public function markAsHandled(string $handler): bool
+    {
+        return $this->update([
+            'status' => self::STATUS_HANDLED,
+            'handler' => $handler,
+            'handled_at' => now(),
+        ]);
+    }
+
+    /**
+     * 标记为已忽略
+     *
+     * @param string $handler 处理人
+     * @return bool
+     */
+    public function markAsIgnored(string $handler): bool
+    {
+        return $this->update([
+            'status' => self::STATUS_IGNORED,
+            'handler' => $handler,
+            'handled_at' => now(),
+        ]);
+    }
+
+    /**
+     * 更新AI解决方案
+     *
+     * @param string $solution AI提供的解决方案
+     * @return bool
+     */
+    public function updateSolution(string $solution): bool
+    {
+        return $this->update(['solution' => $solution]);
+    }
+
+    /**
+     * 获取未处理的异常
+     *
+     * @param int $limit 限制数量
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function getPending(int $limit = 10)
+    {
+        return static::where('status', self::STATUS_PENDING)
+            ->latest()
+            ->limit($limit)
+            ->get();
+    }
+
+    /**
+     * 获取处理中的异常
+     *
+     * @param int $limit 限制数量
+     * @return \Illuminate\Database\Eloquent\Collection
+     */
+    public static function getProcessing(int $limit = 10)
+    {
+        return static::where('status', self::STATUS_PROCESSING)
+            ->latest()
+            ->limit($limit)
+            ->get();
+    }
+}

+ 107 - 0
app/Services/SystemExceptionService.php

@@ -0,0 +1,107 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\SystemException;
+use Illuminate\Support\Facades\Http;
+use Slowlyo\OwlAdmin\Services\AdminService;
+
+class SystemExceptionService extends AdminService
+{
+    protected string $modelName = SystemException::class;
+
+    /**
+     * 使用 AI 分析异常
+     */
+    public function analyze($id)
+    {
+        $exception = $this->query()->findOrFail($id);
+
+        // 调用免费的 AI API (这里以 ChatGPT 为例)
+        $response = Http::post('https://api.openai.com/v1/chat/completions', [
+            'model' => 'gpt-3.5-turbo',
+            'messages' => [
+                [
+                    'role' => 'system',
+                    'content' => '你是一个PHP专家,请分析这个异常并提供解决方案。'
+                ],
+                [
+                    'role' => 'user',
+                    'content' => "异常类型: {$exception->type}\n异常信息: {$exception->message}\n文件: {$exception->file}:{$exception->line}\n堆栈信息: {$exception->trace}"
+                ]
+            ]
+        ], [
+            'Authorization' => 'Bearer ' . config('services.openai.api_key')
+        ]);
+
+        if ($response->successful()) {
+            $solution = $response->json('choices.0.message.content');
+
+            $exception->update([
+                'status' => 1, // 处理中
+                'solution' => $solution
+            ]);
+
+            return [
+                'status' => true,
+                'message' => 'AI分析完成'
+            ];
+        }
+
+        return [
+            'status' => false,
+            'message' => 'AI分析失败: ' . $response->body()
+        ];
+    }
+
+    /**
+     * 批量AI分析
+     */
+    public function batchAnalyze()
+    {
+        $exceptions = $this->query()->where('status', 0)->limit(10)->get();
+
+        foreach ($exceptions as $exception) {
+            $this->analyze($exception->id);
+        }
+
+        return [
+            'status' => true,
+            'message' => '批量分析任务已提交'
+        ];
+    }
+
+    /**
+     * 标记为已处理
+     */
+    public function markHandled($id)
+    {
+        $this->query()->findOrFail($id)->update([
+            'status' => 2,
+            'handler' => auth()->user()->name,
+            'handled_at' => now()
+        ]);
+
+        return [
+            'status' => true,
+            'message' => '已标记为处理完成'
+        ];
+    }
+
+    /**
+     * 标记为忽略
+     */
+    public function ignore($id)
+    {
+        $this->query()->findOrFail($id)->update([
+            'status' => 3,
+            'handler' => auth()->user()->name,
+            'handled_at' => now()
+        ]);
+
+        return [
+            'status' => true,
+            'message' => '已标记为忽略'
+        ];
+    }
+}

+ 41 - 0
app/Support/AsyncExceptionRecorder.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Support;
+
+use App\Models\SystemException;
+
+class AsyncExceptionRecorder
+{
+    public function __construct(
+        private string $type,
+        private string $message,
+        private string $file,
+        private int $line,
+        private string $trace,
+        private array $requestData,
+        private ?int $userId
+    ) {}
+
+    public static function dispatch(...$args): self
+    {
+        $instance = new static(...$args);
+        register_shutdown_function(function () use ($instance) {
+            $instance->handle();
+        });
+        return $instance;
+    }
+
+    public function handle(): void
+    {
+        SystemException::create([
+            'type' => $this->type,
+            'message' => $this->message,
+            'file' => $this->file,
+            'line' => $this->line,
+            'trace' => $this->trace,
+            'request_data' => $this->requestData,
+            'user_id' => $this->userId,
+            'status' => SystemException::STATUS_PENDING,
+        ]);
+    }
+}

+ 2 - 0
routes/admin.php

@@ -111,6 +111,8 @@ Route::group([
     $router->resource('user_feedbacks', \App\Admin\Controllers\UserFeedbackController::class);
     // 不良行为
     $router->resource('violation_records', \App\Admin\Controllers\ViolationRecordController::class);
+    // 异常管理
+    $router->resource('system_exceptions', \App\Admin\Controllers\SystemExceptionController::class);
     // ProjectCate
     $router->get('_project/cate', [\Slowlyo\OwlAdmin\Controllers\AdminApiController::class, 'index']);