Skip to content

Commit ec20847

Browse files
author
xboard
committed
refactor: replace database logging with file logging and admin audit log
1 parent 6efedce commit ec20847

File tree

12 files changed

+122
-369
lines changed

12 files changed

+122
-369
lines changed

app/Console/Commands/ExportV2Log.php

Lines changed: 0 additions & 53 deletions
This file was deleted.

app/Console/Commands/ResetLog.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace App\Console\Commands;
44

5-
use App\Models\Log;
5+
use App\Models\AdminAuditLog;
66
use App\Models\StatServer;
77
use App\Models\StatUser;
88
use Illuminate\Console\Command;
@@ -43,6 +43,6 @@ public function handle()
4343
{
4444
StatUser::where('record_at', '<', strtotime('-2 month', time()))->delete();
4545
StatServer::where('record_at', '<', strtotime('-2 month', time()))->delete();
46-
Log::where('created_at', '<', strtotime('-1 month', time()))->delete();
46+
AdminAuditLog::where('created_at', '<', strtotime('-3 month', time()))->delete();
4747
}
4848
}

app/Http/Controllers/V2/Admin/SystemController.php

Lines changed: 15 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace App\Http\Controllers\V2\Admin;
44

55
use App\Http\Controllers\Controller;
6-
use App\Models\Log as LogModel;
6+
use App\Models\AdminAuditLog;
77
use App\Utils\CacheKey;
88
use Illuminate\Http\Request;
99
use Illuminate\Support\Facades\Cache;
@@ -23,37 +23,10 @@ public function getSystemStatus()
2323
'schedule' => $this->getScheduleStatus(),
2424
'horizon' => $this->getHorizonStatus(),
2525
'schedule_last_runtime' => Cache::get(CacheKey::get('SCHEDULE_LAST_CHECK_AT', null)),
26-
'logs' => $this->getLogStatistics()
2726
];
2827
return $this->success($data);
2928
}
3029

31-
/**
32-
* 获取日志统计信息
33-
*
34-
* @return array 各级别日志的数量统计
35-
*/
36-
protected function getLogStatistics(): array
37-
{
38-
// 初始化日志统计数组
39-
$statistics = [
40-
'info' => 0,
41-
'warning' => 0,
42-
'error' => 0,
43-
'total' => 0
44-
];
45-
46-
if (class_exists(LogModel::class) && LogModel::count() > 0) {
47-
$statistics['info'] = LogModel::where('level', 'INFO')->count();
48-
$statistics['warning'] = LogModel::where('level', 'WARNING')->count();
49-
$statistics['error'] = LogModel::where('level', 'ERROR')->count();
50-
$statistics['total'] = LogModel::count();
51-
52-
return $statistics;
53-
}
54-
return $statistics;
55-
}
56-
5730
public function getQueueWorkload(WorkloadRepository $workload)
5831
{
5932
return $this->success(collect($workload->get())->sortBy('name')->values()->toArray());
@@ -125,34 +98,26 @@ protected function totalPausedMasters()
12598
})->count();
12699
}
127100

128-
public function getSystemLog(Request $request)
101+
public function getAuditLog(Request $request)
129102
{
130-
$current = $request->input('current') ? $request->input('current') : 1;
131-
$pageSize = $request->input('page_size') >= 10 ? $request->input('page_size') : 10;
132-
$level = $request->input('level');
133-
$keyword = $request->input('keyword');
134-
135-
$builder = LogModel::orderBy('created_at', 'DESC')
136-
->when($level, function ($query) use ($level) {
137-
return $query->where('level', strtoupper($level));
138-
})
139-
->when($keyword, function ($query) use ($keyword) {
140-
return $query->where(function ($q) use ($keyword) {
141-
$q->where('data', 'like', '%' . $keyword . '%')
142-
->orWhere('context', 'like', '%' . $keyword . '%')
143-
->orWhere('title', 'like', '%' . $keyword . '%')
144-
->orWhere('uri', 'like', '%' . $keyword . '%');
103+
$current = max(1, (int) $request->input('current', 1));
104+
$pageSize = max(10, (int) $request->input('page_size', 10));
105+
106+
$builder = AdminAuditLog::with('admin:id,email')
107+
->orderBy('id', 'DESC')
108+
->when($request->input('action'), fn($q, $v) => $q->where('action', $v))
109+
->when($request->input('admin_id'), fn($q, $v) => $q->where('admin_id', $v))
110+
->when($request->input('keyword'), function ($q, $keyword) {
111+
$q->where(function ($q) use ($keyword) {
112+
$q->where('uri', 'like', '%' . $keyword . '%')
113+
->orWhere('request_data', 'like', '%' . $keyword . '%');
145114
});
146115
});
147116

148117
$total = $builder->count();
149-
$res = $builder->forPage($current, $pageSize)
150-
->get();
118+
$res = $builder->forPage($current, $pageSize)->get();
151119

152-
return response([
153-
'data' => $res,
154-
'total' => $total
155-
]);
120+
return response(['data' => $res, 'total' => $total]);
156121
}
157122

158123
public function getHorizonFailedJobs(Request $request, JobRepository $jobRepository)
@@ -176,125 +141,4 @@ public function getHorizonFailedJobs(Request $request, JobRepository $jobReposit
176141
]);
177142
}
178143

179-
/**
180-
* 清除系统日志
181-
*
182-
* @param Request $request
183-
* @return \Illuminate\Http\JsonResponse
184-
*/
185-
public function clearSystemLog(Request $request)
186-
{
187-
$request->validate([
188-
'days' => 'integer|min:0|max:365',
189-
'level' => 'string|in:info,warning,error,all',
190-
'limit' => 'integer|min:100|max:10000'
191-
], [
192-
'days.required' => '请指定要清除多少天前的日志',
193-
'days.integer' => '天数必须为整数',
194-
'days.min' => '天数不能少于1天',
195-
'days.max' => '天数不能超过365天',
196-
'level.in' => '日志级别只能是:info、warning、error、all',
197-
'limit.min' => '单次清除数量不能少于100条',
198-
'limit.max' => '单次清除数量不能超过10000条'
199-
]);
200-
201-
$days = $request->input('days', 30); // 默认清除30天前的日志
202-
$level = $request->input('level', 'all'); // 默认清除所有级别
203-
$limit = $request->input('limit', 1000); // 默认单次清除1000条
204-
205-
try {
206-
$cutoffDate = now()->subDays($days);
207-
208-
// 构建查询条件
209-
$query = LogModel::where('created_at', '<', $cutoffDate->timestamp);
210-
211-
if ($level !== 'all') {
212-
$query->where('level', strtoupper($level));
213-
}
214-
215-
// 获取要删除的记录数量
216-
$totalCount = $query->count();
217-
218-
if ($totalCount === 0) {
219-
return $this->success([
220-
'message' => '没有找到符合条件的日志记录',
221-
'deleted_count' => 0,
222-
'total_count' => $totalCount
223-
]);
224-
}
225-
226-
// 分批删除,避免单次删除过多数据
227-
$deletedCount = 0;
228-
$batchSize = min($limit, 1000); // 每批最多1000条
229-
230-
while ($deletedCount < $limit && $deletedCount < $totalCount) {
231-
$remainingLimit = min($batchSize, $limit - $deletedCount);
232-
233-
$batchQuery = LogModel::where('created_at', '<', $cutoffDate->timestamp);
234-
if ($level !== 'all') {
235-
$batchQuery->where('level', strtoupper($level));
236-
}
237-
238-
$idsToDelete = $batchQuery->limit($remainingLimit)->pluck('id');
239-
240-
if ($idsToDelete->isEmpty()) {
241-
break;
242-
}
243-
244-
$batchDeleted = LogModel::whereIn('id', $idsToDelete)->delete();
245-
$deletedCount += $batchDeleted;
246-
247-
// 避免长时间占用数据库连接
248-
if ($deletedCount < $limit && $deletedCount < $totalCount) {
249-
usleep(100000); // 暂停0.1秒
250-
}
251-
}
252-
253-
return $this->success([
254-
'message' => '日志清除完成',
255-
'deleted_count' => $deletedCount,
256-
'total_count' => $totalCount,
257-
'remaining_count' => max(0, $totalCount - $deletedCount)
258-
]);
259-
260-
} catch (\Exception $e) {
261-
return $this->fail(ResponseEnum::HTTP_ERROR, null, '清除日志失败:' . $e->getMessage());
262-
}
263-
}
264-
265-
/**
266-
* 获取日志清除统计信息
267-
*
268-
* @param Request $request
269-
* @return \Illuminate\Http\JsonResponse
270-
*/
271-
public function getLogClearStats(Request $request)
272-
{
273-
$days = $request->input('days', 30);
274-
$level = $request->input('level', 'all');
275-
276-
try {
277-
$cutoffDate = now()->subDays($days);
278-
279-
$query = LogModel::where('created_at', '<', $cutoffDate->timestamp);
280-
if ($level !== 'all') {
281-
$query->where('level', strtoupper($level));
282-
}
283-
284-
$stats = [
285-
'days' => $days,
286-
'level' => $level,
287-
'cutoff_date' => $cutoffDate->format(format: 'Y-m-d H:i:s'),
288-
'total_logs' => LogModel::count(),
289-
'logs_to_clear' => $query->count(),
290-
'oldest_log' => LogModel::orderBy('created_at', 'asc')->first(),
291-
'newest_log' => LogModel::orderBy('created_at', 'desc')->first(),
292-
];
293-
294-
return $this->success($stats);
295-
296-
} catch (\Exception $e) {
297-
return $this->fail(ResponseEnum::HTTP_ERROR, null, '获取统计信息失败:' . $e->getMessage());
298-
}
299-
}
300144
}

app/Http/Middleware/RequestLog.php

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,59 @@
22

33
namespace App\Http\Middleware;
44

5+
use App\Models\AdminAuditLog;
56
use Closure;
67

78
class RequestLog
89
{
9-
/**
10-
* Handle an incoming request.
11-
*
12-
* @param \Illuminate\Http\Request $request
13-
* @param \Closure $next
14-
* @return mixed
15-
*/
10+
private const SENSITIVE_KEYS = ['password', 'token', 'secret', 'key', 'api_key'];
11+
1612
public function handle($request, Closure $next)
1713
{
18-
if ($request->method() === 'POST') {
19-
$path = $request->path();
20-
info("POST {$path}");
21-
};
22-
return $next($request);
14+
if ($request->method() !== 'POST') {
15+
return $next($request);
16+
}
17+
18+
$response = $next($request);
19+
20+
try {
21+
$admin = $request->user();
22+
if (!$admin || !$admin->is_admin) {
23+
return $response;
24+
}
25+
26+
$action = $this->resolveAction($request->path());
27+
$data = collect($request->all())->except(self::SENSITIVE_KEYS)->toArray();
28+
29+
AdminAuditLog::insert([
30+
'admin_id' => $admin->id,
31+
'action' => $action,
32+
'method' => $request->method(),
33+
'uri' => $request->getRequestUri(),
34+
'request_data' => json_encode($data, JSON_UNESCAPED_UNICODE),
35+
'ip' => $request->getClientIp(),
36+
'created_at' => time(),
37+
'updated_at' => time(),
38+
]);
39+
} catch (\Throwable $e) {
40+
\Log::warning('Audit log write failed: ' . $e->getMessage());
41+
}
42+
43+
return $response;
44+
}
45+
46+
private function resolveAction(string $path): string
47+
{
48+
// api/v2/{secure_path}/user/update → user.update
49+
$path = preg_replace('#^api/v[12]/[^/]+/#', '', $path);
50+
// gift-card/create-template → gift_card.create_template
51+
$path = str_replace('-', '_', $path);
52+
// user/update → user.update, server/manage/sort → server_manage.sort
53+
$segments = explode('/', $path);
54+
$method = array_pop($segments);
55+
$resource = implode('_', $segments);
56+
57+
return $resource . '.' . $method;
2358
}
2459
}
60+

app/Http/Routes/V2/AdminRoute.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,8 @@ public function map(Registrar $router)
218218
$router->get('/getQueueStats', [SystemController::class, 'getQueueStats']);
219219
$router->get('/getQueueWorkload', [SystemController::class, 'getQueueWorkload']);
220220
$router->get('/getQueueMasters', '\\Laravel\\Horizon\\Http\\Controllers\\MasterSupervisorController@index');
221-
$router->get('/getSystemLog', [SystemController::class, 'getSystemLog']);
222221
$router->get('/getHorizonFailedJobs', [SystemController::class, 'getHorizonFailedJobs']);
223-
$router->post('/clearSystemLog', [SystemController::class, 'clearSystemLog']);
224-
$router->get('/getLogClearStats', [SystemController::class, 'getLogClearStats']);
222+
$router->any('/getAuditLog', [SystemController::class, 'getAuditLog']);
225223
});
226224

227225
// Update

app/Logging/MysqlLogger.php

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)