diff --git a/app/Console/Commands/ExportV2Log.php b/app/Console/Commands/ExportV2Log.php deleted file mode 100644 index 1bc1fa3..0000000 --- a/app/Console/Commands/ExportV2Log.php +++ /dev/null @@ -1,53 +0,0 @@ -argument('days'); - $date = Carbon::now()->subDays((float) $days)->startOfDay(); - - $logs = DB::table('v2_log') - ->where('created_at', '>=', $date->timestamp) - ->get(); - - $fileName = "v2_logs_" . Carbon::now()->format('Y_m_d_His') . ".csv"; - $handle = fopen(storage_path("logs/$fileName"), 'w'); - - // 根据您的表结构 - fputcsv($handle, ['Level', 'ID', 'Title', 'Host', 'URI', 'Method', 'Data', 'IP', 'Context', 'Created At', 'Updated At']); - - foreach ($logs as $log) { - fputcsv($handle, [ - $log->level, - $log->id, - $log->title, - $log->host, - $log->uri, - $log->method, - $log->data, - $log->ip, - $log->context, - Carbon::createFromTimestamp($log->created_at)->toDateTimeString(), - Carbon::createFromTimestamp($log->updated_at)->toDateTimeString() - ]); - } - - fclose($handle); - $this->info("日志成功导出到: " . storage_path("logs/$fileName")); - } -} diff --git a/app/Console/Commands/ResetLog.php b/app/Console/Commands/ResetLog.php index 83eb631..89ddfb3 100644 --- a/app/Console/Commands/ResetLog.php +++ b/app/Console/Commands/ResetLog.php @@ -2,7 +2,7 @@ namespace App\Console\Commands; -use App\Models\Log; +use App\Models\AdminAuditLog; use App\Models\StatServer; use App\Models\StatUser; use Illuminate\Console\Command; @@ -43,6 +43,6 @@ class ResetLog extends Command { StatUser::where('record_at', '<', strtotime('-2 month', time()))->delete(); StatServer::where('record_at', '<', strtotime('-2 month', time()))->delete(); - Log::where('created_at', '<', strtotime('-1 month', time()))->delete(); + AdminAuditLog::where('created_at', '<', strtotime('-3 month', time()))->delete(); } } diff --git a/app/Http/Controllers/V2/Admin/SystemController.php b/app/Http/Controllers/V2/Admin/SystemController.php index 244cd02..2ba35b5 100644 --- a/app/Http/Controllers/V2/Admin/SystemController.php +++ b/app/Http/Controllers/V2/Admin/SystemController.php @@ -3,7 +3,7 @@ namespace App\Http\Controllers\V2\Admin; use App\Http\Controllers\Controller; -use App\Models\Log as LogModel; +use App\Models\AdminAuditLog; use App\Utils\CacheKey; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; @@ -23,37 +23,10 @@ class SystemController extends Controller 'schedule' => $this->getScheduleStatus(), 'horizon' => $this->getHorizonStatus(), 'schedule_last_runtime' => Cache::get(CacheKey::get('SCHEDULE_LAST_CHECK_AT', null)), - 'logs' => $this->getLogStatistics() ]; return $this->success($data); } - /** - * 获取日志统计信息 - * - * @return array 各级别日志的数量统计 - */ - protected function getLogStatistics(): array - { - // 初始化日志统计数组 - $statistics = [ - 'info' => 0, - 'warning' => 0, - 'error' => 0, - 'total' => 0 - ]; - - if (class_exists(LogModel::class) && LogModel::count() > 0) { - $statistics['info'] = LogModel::where('level', 'INFO')->count(); - $statistics['warning'] = LogModel::where('level', 'WARNING')->count(); - $statistics['error'] = LogModel::where('level', 'ERROR')->count(); - $statistics['total'] = LogModel::count(); - - return $statistics; - } - return $statistics; - } - public function getQueueWorkload(WorkloadRepository $workload) { return $this->success(collect($workload->get())->sortBy('name')->values()->toArray()); @@ -125,34 +98,26 @@ class SystemController extends Controller })->count(); } - public function getSystemLog(Request $request) + public function getAuditLog(Request $request) { - $current = $request->input('current') ? $request->input('current') : 1; - $pageSize = $request->input('page_size') >= 10 ? $request->input('page_size') : 10; - $level = $request->input('level'); - $keyword = $request->input('keyword'); + $current = max(1, (int) $request->input('current', 1)); + $pageSize = max(10, (int) $request->input('page_size', 10)); - $builder = LogModel::orderBy('created_at', 'DESC') - ->when($level, function ($query) use ($level) { - return $query->where('level', strtoupper($level)); - }) - ->when($keyword, function ($query) use ($keyword) { - return $query->where(function ($q) use ($keyword) { - $q->where('data', 'like', '%' . $keyword . '%') - ->orWhere('context', 'like', '%' . $keyword . '%') - ->orWhere('title', 'like', '%' . $keyword . '%') - ->orWhere('uri', 'like', '%' . $keyword . '%'); + $builder = AdminAuditLog::with('admin:id,email') + ->orderBy('id', 'DESC') + ->when($request->input('action'), fn($q, $v) => $q->where('action', $v)) + ->when($request->input('admin_id'), fn($q, $v) => $q->where('admin_id', $v)) + ->when($request->input('keyword'), function ($q, $keyword) { + $q->where(function ($q) use ($keyword) { + $q->where('uri', 'like', '%' . $keyword . '%') + ->orWhere('request_data', 'like', '%' . $keyword . '%'); }); }); $total = $builder->count(); - $res = $builder->forPage($current, $pageSize) - ->get(); + $res = $builder->forPage($current, $pageSize)->get(); - return response([ - 'data' => $res, - 'total' => $total - ]); + return response(['data' => $res, 'total' => $total]); } public function getHorizonFailedJobs(Request $request, JobRepository $jobRepository) @@ -176,125 +141,4 @@ class SystemController extends Controller ]); } - /** - * 清除系统日志 - * - * @param Request $request - * @return \Illuminate\Http\JsonResponse - */ - public function clearSystemLog(Request $request) - { - $request->validate([ - 'days' => 'integer|min:0|max:365', - 'level' => 'string|in:info,warning,error,all', - 'limit' => 'integer|min:100|max:10000' - ], [ - 'days.required' => '请指定要清除多少天前的日志', - 'days.integer' => '天数必须为整数', - 'days.min' => '天数不能少于1天', - 'days.max' => '天数不能超过365天', - 'level.in' => '日志级别只能是:info、warning、error、all', - 'limit.min' => '单次清除数量不能少于100条', - 'limit.max' => '单次清除数量不能超过10000条' - ]); - - $days = $request->input('days', 30); // 默认清除30天前的日志 - $level = $request->input('level', 'all'); // 默认清除所有级别 - $limit = $request->input('limit', 1000); // 默认单次清除1000条 - - try { - $cutoffDate = now()->subDays($days); - - // 构建查询条件 - $query = LogModel::where('created_at', '<', $cutoffDate->timestamp); - - if ($level !== 'all') { - $query->where('level', strtoupper($level)); - } - - // 获取要删除的记录数量 - $totalCount = $query->count(); - - if ($totalCount === 0) { - return $this->success([ - 'message' => '没有找到符合条件的日志记录', - 'deleted_count' => 0, - 'total_count' => $totalCount - ]); - } - - // 分批删除,避免单次删除过多数据 - $deletedCount = 0; - $batchSize = min($limit, 1000); // 每批最多1000条 - - while ($deletedCount < $limit && $deletedCount < $totalCount) { - $remainingLimit = min($batchSize, $limit - $deletedCount); - - $batchQuery = LogModel::where('created_at', '<', $cutoffDate->timestamp); - if ($level !== 'all') { - $batchQuery->where('level', strtoupper($level)); - } - - $idsToDelete = $batchQuery->limit($remainingLimit)->pluck('id'); - - if ($idsToDelete->isEmpty()) { - break; - } - - $batchDeleted = LogModel::whereIn('id', $idsToDelete)->delete(); - $deletedCount += $batchDeleted; - - // 避免长时间占用数据库连接 - if ($deletedCount < $limit && $deletedCount < $totalCount) { - usleep(100000); // 暂停0.1秒 - } - } - - return $this->success([ - 'message' => '日志清除完成', - 'deleted_count' => $deletedCount, - 'total_count' => $totalCount, - 'remaining_count' => max(0, $totalCount - $deletedCount) - ]); - - } catch (\Exception $e) { - return $this->fail(ResponseEnum::HTTP_ERROR, null, '清除日志失败:' . $e->getMessage()); - } - } - - /** - * 获取日志清除统计信息 - * - * @param Request $request - * @return \Illuminate\Http\JsonResponse - */ - public function getLogClearStats(Request $request) - { - $days = $request->input('days', 30); - $level = $request->input('level', 'all'); - - try { - $cutoffDate = now()->subDays($days); - - $query = LogModel::where('created_at', '<', $cutoffDate->timestamp); - if ($level !== 'all') { - $query->where('level', strtoupper($level)); - } - - $stats = [ - 'days' => $days, - 'level' => $level, - 'cutoff_date' => $cutoffDate->format(format: 'Y-m-d H:i:s'), - 'total_logs' => LogModel::count(), - 'logs_to_clear' => $query->count(), - 'oldest_log' => LogModel::orderBy('created_at', 'asc')->first(), - 'newest_log' => LogModel::orderBy('created_at', 'desc')->first(), - ]; - - return $this->success($stats); - - } catch (\Exception $e) { - return $this->fail(ResponseEnum::HTTP_ERROR, null, '获取统计信息失败:' . $e->getMessage()); - } - } } diff --git a/app/Http/Middleware/RequestLog.php b/app/Http/Middleware/RequestLog.php index c1244cc..62dded3 100755 --- a/app/Http/Middleware/RequestLog.php +++ b/app/Http/Middleware/RequestLog.php @@ -2,23 +2,59 @@ namespace App\Http\Middleware; +use App\Models\AdminAuditLog; use Closure; class RequestLog { - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - */ + private const SENSITIVE_KEYS = ['password', 'token', 'secret', 'key', 'api_key']; + public function handle($request, Closure $next) { - if ($request->method() === 'POST') { - $path = $request->path(); - info("POST {$path}"); - }; - return $next($request); + if ($request->method() !== 'POST') { + return $next($request); + } + + $response = $next($request); + + try { + $admin = $request->user(); + if (!$admin || !$admin->is_admin) { + return $response; + } + + $action = $this->resolveAction($request->path()); + $data = collect($request->all())->except(self::SENSITIVE_KEYS)->toArray(); + + AdminAuditLog::insert([ + 'admin_id' => $admin->id, + 'action' => $action, + 'method' => $request->method(), + 'uri' => $request->getRequestUri(), + 'request_data' => json_encode($data, JSON_UNESCAPED_UNICODE), + 'ip' => $request->getClientIp(), + 'created_at' => time(), + 'updated_at' => time(), + ]); + } catch (\Throwable $e) { + \Log::warning('Audit log write failed: ' . $e->getMessage()); + } + + return $response; + } + + private function resolveAction(string $path): string + { + // api/v2/{secure_path}/user/update → user.update + $path = preg_replace('#^api/v[12]/[^/]+/#', '', $path); + // gift-card/create-template → gift_card.create_template + $path = str_replace('-', '_', $path); + // user/update → user.update, server/manage/sort → server_manage.sort + $segments = explode('/', $path); + $method = array_pop($segments); + $resource = implode('_', $segments); + + return $resource . '.' . $method; } } + diff --git a/app/Http/Routes/V2/AdminRoute.php b/app/Http/Routes/V2/AdminRoute.php index cbb240b..5b11660 100644 --- a/app/Http/Routes/V2/AdminRoute.php +++ b/app/Http/Routes/V2/AdminRoute.php @@ -218,10 +218,8 @@ class AdminRoute $router->get('/getQueueStats', [SystemController::class, 'getQueueStats']); $router->get('/getQueueWorkload', [SystemController::class, 'getQueueWorkload']); $router->get('/getQueueMasters', '\\Laravel\\Horizon\\Http\\Controllers\\MasterSupervisorController@index'); - $router->get('/getSystemLog', [SystemController::class, 'getSystemLog']); $router->get('/getHorizonFailedJobs', [SystemController::class, 'getHorizonFailedJobs']); - $router->post('/clearSystemLog', [SystemController::class, 'clearSystemLog']); - $router->get('/getLogClearStats', [SystemController::class, 'getLogClearStats']); + $router->any('/getAuditLog', [SystemController::class, 'getAuditLog']); }); // Update diff --git a/app/Logging/MysqlLogger.php b/app/Logging/MysqlLogger.php deleted file mode 100644 index 95b8185..0000000 --- a/app/Logging/MysqlLogger.php +++ /dev/null @@ -1,11 +0,0 @@ -pushHandler(new MysqlLoggerHandler()); - }); - } -} diff --git a/app/Logging/MysqlLoggerHandler.php b/app/Logging/MysqlLoggerHandler.php deleted file mode 100644 index eb0b09e..0000000 --- a/app/Logging/MysqlLoggerHandler.php +++ /dev/null @@ -1,45 +0,0 @@ -toArray(); - try { - if (isset($record['context']['exception']) && is_object($record['context']['exception'])) { - $record['context']['exception'] = (array)$record['context']['exception']; - } - - $record['request_data'] = request()->all(); - - $log = [ - 'title' => $record['message'], - 'level' => $record['level_name'], - 'host' => $record['extra']['request_host'] ?? request()->getSchemeAndHttpHost(), - 'uri' => $record['extra']['request_uri'] ?? request()->getRequestUri(), - 'method' => $record['extra']['request_method'] ?? request()->getMethod(), - 'ip' => request()->getClientIp(), - 'data' => json_encode($record['request_data']), - 'context' => json_encode($record['context']), - 'created_at' => $record['datetime']->getTimestamp(), - 'updated_at' => $record['datetime']->getTimestamp(), - ]; - - LogModel::insert($log); - } catch (\Exception $e) { - // Log::channel('daily')->error($e->getMessage().$e->getFile().$e->getTraceAsString()); - } - } -} diff --git a/app/Models/AdminAuditLog.php b/app/Models/AdminAuditLog.php new file mode 100644 index 0000000..9e9404c --- /dev/null +++ b/app/Models/AdminAuditLog.php @@ -0,0 +1,21 @@ + 'timestamp', + 'updated_at' => 'timestamp', + ]; + + public function admin() + { + return $this->belongsTo(User::class, 'admin_id'); + } +} diff --git a/app/Models/Log.php b/app/Models/Log.php deleted file mode 100644 index ffdbba2..0000000 --- a/app/Models/Log.php +++ /dev/null @@ -1,17 +0,0 @@ - 'timestamp', - 'updated_at' => 'timestamp' - ]; -} diff --git a/config/logging.php b/config/logging.php index 6f7a0fb..7ec1268 100755 --- a/config/logging.php +++ b/config/logging.php @@ -5,40 +5,9 @@ use Monolog\Handler\SyslogUdpHandler; return [ - /* - |-------------------------------------------------------------------------- - | Default Log Channel - |-------------------------------------------------------------------------- - | - | This option defines the default log channel that gets used when writing - | messages to the logs. The name specified in this option should match - | one of the channels defined in the "channels" configuration array. - | - */ - - 'default' => 'mysql', - - /* - |-------------------------------------------------------------------------- - | Log Channels - |-------------------------------------------------------------------------- - | - | Here you may configure the log channels for your application. Out of - | the box, Laravel uses the Monolog PHP logging library. This gives - | you a variety of powerful log handlers / formatters to utilize. - | - | Available Drivers: "single", "daily", "slack", "syslog", - | "errorlog", "monolog", - | "custom", "stack" - | - */ + 'default' => env('LOG_CHANNEL', 'daily'), 'channels' => [ - 'mysql' => [ - 'driver' => 'custom', - 'via' => App\Logging\MysqlLogger::class, - ], - 'stack' => [ 'driver' => 'stack', 'channels' => ['daily'], @@ -54,36 +23,19 @@ return [ 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, ], - 'slack' => [ - 'driver' => 'slack', - 'url' => env('LOG_SLACK_WEBHOOK_URL'), - 'username' => 'Laravel Log', - 'emoji' => ':boom:', - 'level' => 'critical', - ], - - 'papertrail' => [ - 'driver' => 'monolog', - 'level' => 'debug', - 'handler' => SyslogUdpHandler::class, - 'handler_with' => [ - 'host' => env('PAPERTRAIL_URL'), - 'port' => env('PAPERTRAIL_PORT'), - ], - ], - 'stderr' => [ 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, 'formatter' => env('LOG_STDERR_FORMATTER'), 'with' => [ @@ -93,12 +45,12 @@ return [ 'syslog' => [ 'driver' => 'syslog', - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), ], 'errorlog' => [ 'driver' => 'errorlog', - 'level' => 'debug', + 'level' => env('LOG_LEVEL', 'debug'), ], 'deprecations' => [ diff --git a/database/migrations/2026_03_11_000001_replace_v2_log_with_admin_audit_log.php b/database/migrations/2026_03_11_000001_replace_v2_log_with_admin_audit_log.php new file mode 100644 index 0000000..7f7b587 --- /dev/null +++ b/database/migrations/2026_03_11_000001_replace_v2_log_with_admin_audit_log.php @@ -0,0 +1,28 @@ +id(); + $table->unsignedBigInteger('admin_id')->index(); + $table->string('action', 64)->index()->comment('Action identifier e.g. user.update'); + $table->string('method', 10); + $table->string('uri', 512); + $table->text('request_data')->nullable(); + $table->string('ip', 128)->nullable(); + $table->unsignedInteger('created_at'); + $table->unsignedInteger('updated_at'); + }); + } + + public function down(): void + { + Schema::dropIfExists('v2_admin_audit_log'); + } +}; diff --git a/public/assets/admin b/public/assets/admin index 01bd0b3..86a5f20 160000 --- a/public/assets/admin +++ b/public/assets/admin @@ -1 +1 @@ -Subproject commit 01bd0b3749dadfac6df521b28caa30985b4cb0eb +Subproject commit 86a5f20d8aba58e2686ea65faabd4f9bafe370dd