初始化项目
This commit is contained in:
608
docs/README_LOG.md
Normal file
608
docs/README_LOG.md
Normal file
@@ -0,0 +1,608 @@
|
||||
# 系统操作日志模块文档
|
||||
|
||||
## 概述
|
||||
|
||||
系统操作日志模块用于记录后台管理系统的所有操作请求,包括用户操作、API 调用、错误信息等,方便管理员进行系统监控、审计和问题排查。
|
||||
|
||||
## 技术特性
|
||||
|
||||
- **自动记录**: 通过中间件自动记录所有请求,无需手动调用
|
||||
- **详细信息**: 记录用户信息、请求参数、响应结果、执行时间等
|
||||
- **敏感信息保护**: 自动过滤密码等敏感信息
|
||||
- **性能优化**: 不影响业务响应速度
|
||||
- **多维度查询**: 支持按用户、模块、操作、状态、时间等多维度筛选
|
||||
- **数据导出**: 支持导出日志数据为 Excel 文件
|
||||
- **批量操作**: 支持批量删除和定期清理
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
### system_logs 表
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| id | bigint | 主键 ID |
|
||||
| user_id | bigint | 用户 ID |
|
||||
| username | varchar(100) | 用户名 |
|
||||
| module | varchar(50) | 模块名称 |
|
||||
| action | varchar(100) | 操作名称 |
|
||||
| method | varchar(10) | 请求方法 (GET/POST/PUT/DELETE) |
|
||||
| url | text | 请求 URL |
|
||||
| ip | varchar(45) | 客户端 IP 地址 |
|
||||
| user_agent | text | 用户代理 |
|
||||
| params | json | 请求参数 |
|
||||
| result | text | 响应结果(仅错误时记录) |
|
||||
| status_code | int | HTTP 状态码 |
|
||||
| status | varchar(20) | 状态 (success/error) |
|
||||
| error_message | text | 错误信息 |
|
||||
| execution_time | int | 执行时间(毫秒) |
|
||||
| created_at | timestamp | 创建时间 |
|
||||
| updated_at | timestamp | 更新时间 |
|
||||
|
||||
## 核心组件
|
||||
|
||||
### 1. 中间件 (Middleware)
|
||||
|
||||
**LogRequestMiddleware**
|
||||
|
||||
位置: `app/Http/Middleware/LogRequestMiddleware.php`
|
||||
|
||||
功能:
|
||||
- 自动拦截所有经过的请求
|
||||
- 记录请求和响应信息
|
||||
- 计算请求执行时间
|
||||
- 提取用户信息和操作详情
|
||||
- 过滤敏感参数
|
||||
- 处理异常情况
|
||||
|
||||
使用方式:
|
||||
```php
|
||||
// 在路由中应用
|
||||
Route::middleware(['log.request'])->group(function () {
|
||||
// 需要记录日志的路由
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 服务层 (Service)
|
||||
|
||||
**LogService**
|
||||
|
||||
位置: `app/Services/System/LogService.php`
|
||||
|
||||
主要方法:
|
||||
- `create(array $data)`: 创建日志记录
|
||||
- `getList(array $params)`: 获取日志列表(分页)
|
||||
- `getListQuery(array $params)`: 获取日志查询构建器
|
||||
- `getById(int $id)`: 根据 ID 获取日志详情
|
||||
- `delete(int $id)`: 删除单条日志
|
||||
- `batchDelete(array $ids)`: 批量删除日志
|
||||
- `clearLogs(string $days)`: 清理指定天数前的日志
|
||||
- `getStatistics(array $params)`: 获取日志统计信息
|
||||
|
||||
### 3. 控制器 (Controller)
|
||||
|
||||
**Log Controller**
|
||||
|
||||
位置: `app/Http/Controllers/System/Admin/Log.php`
|
||||
|
||||
接口列表:
|
||||
- `GET /admin/logs`: 获取日志列表
|
||||
- `GET /admin/logs/{id}`: 获取日志详情
|
||||
- `GET /admin/logs/statistics`: 获取日志统计
|
||||
- `POST /admin/logs/export`: 导出日志
|
||||
- `DELETE /admin/logs/{id}`: 删除单条日志
|
||||
- `POST /admin/logs/batch-delete`: 批量删除日志
|
||||
- `POST /admin/logs/clear`: 清理历史日志
|
||||
|
||||
### 4. 请求验证 (Request Validation)
|
||||
|
||||
**LogRequest**
|
||||
|
||||
位置: `app/Http/Requests/LogRequest.php`
|
||||
|
||||
验证规则:
|
||||
- `user_id`: 用户 ID(可选)
|
||||
- `username`: 用户名(模糊查询,可选)
|
||||
- `module`: 模块名称(可选)
|
||||
- `action`: 操作名称(可选)
|
||||
- `status`: 状态(success/error,可选)
|
||||
- `start_date`: 开始日期(可选)
|
||||
- `end_date`: 结束日期(可选)
|
||||
- `ip`: IP 地址(可选)
|
||||
- `page`: 页码(默认 1)
|
||||
- `page_size`: 每页数量(默认 20,最大 100)
|
||||
|
||||
## API 接口文档
|
||||
|
||||
### 1. 获取日志列表
|
||||
|
||||
**接口**: `GET /admin/logs`
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"user_id": 1,
|
||||
"username": "admin",
|
||||
"module": "users",
|
||||
"action": "创建 users",
|
||||
"status": "success",
|
||||
"start_date": "2024-01-01",
|
||||
"end_date": "2024-12-31",
|
||||
"ip": "192.168.1.1",
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 1,
|
||||
"username": "admin",
|
||||
"module": "users",
|
||||
"action": "创建 users",
|
||||
"method": "POST",
|
||||
"url": "http://example.com/admin/users",
|
||||
"ip": "192.168.1.1",
|
||||
"user_agent": "Mozilla/5.0...",
|
||||
"params": {
|
||||
"name": "test",
|
||||
"email": "test@example.com"
|
||||
},
|
||||
"result": null,
|
||||
"status_code": 200,
|
||||
"status": "success",
|
||||
"error_message": null,
|
||||
"execution_time": 125,
|
||||
"created_at": "2024-01-01 12:00:00",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"name": "管理员",
|
||||
"username": "admin"
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取日志详情
|
||||
|
||||
**接口**: `GET /admin/logs/{id}`
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"user_id": 1,
|
||||
"username": "admin",
|
||||
"module": "users",
|
||||
"action": "创建 users",
|
||||
"method": "POST",
|
||||
"url": "http://example.com/admin/users",
|
||||
"ip": "192.168.1.1",
|
||||
"user_agent": "Mozilla/5.0...",
|
||||
"params": {
|
||||
"name": "test",
|
||||
"email": "test@example.com"
|
||||
},
|
||||
"result": null,
|
||||
"status_code": 200,
|
||||
"status": "success",
|
||||
"error_message": null,
|
||||
"execution_time": 125,
|
||||
"created_at": "2024-01-01 12:00:00",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"name": "管理员",
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"created_at": "2024-01-01 10:00:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 获取日志统计
|
||||
|
||||
**接口**: `GET /admin/logs/statistics`
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"start_date": "2024-01-01",
|
||||
"end_date": "2024-12-31"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"total": 1000,
|
||||
"success": 950,
|
||||
"error": 50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 导出日志
|
||||
|
||||
**接口**: `POST /admin/logs/export`
|
||||
|
||||
**请求参数**: 与获取日志列表相同的查询参数
|
||||
|
||||
**响应**: Excel 文件下载
|
||||
|
||||
文件名格式: `系统操作日志_YYYYMMDDHHmmss.xlsx`
|
||||
|
||||
包含字段:
|
||||
- ID
|
||||
- 用户名
|
||||
- 模块
|
||||
- 操作
|
||||
- 请求方法
|
||||
- URL
|
||||
- IP 地址
|
||||
- 状态码
|
||||
- 状态
|
||||
- 错误信息
|
||||
- 执行时间(ms)
|
||||
- 创建时间
|
||||
|
||||
### 5. 删除单条日志
|
||||
|
||||
**接口**: `DELETE /admin/logs/{id}`
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "删除成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 批量删除日志
|
||||
|
||||
**接口**: `POST /admin/logs/batch-delete`
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"ids": [1, 2, 3, 4, 5]
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "批量删除成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 清理历史日志
|
||||
|
||||
**接口**: `POST /admin/logs/clear`
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"days": 30
|
||||
}
|
||||
```
|
||||
|
||||
**说明**: 清理指定天数前的所有日志记录,默认清理 30 天前的数据。
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "清理成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 日志记录规则
|
||||
|
||||
### 1. 自动记录的请求
|
||||
|
||||
所有经过 `log.request` 中间件的请求都会被自动记录,包括:
|
||||
- 用户管理操作
|
||||
- 角色管理操作
|
||||
- 权限管理操作
|
||||
- 部门管理操作
|
||||
- 系统配置操作
|
||||
- 其他所有后台管理操作
|
||||
|
||||
### 2. 不记录的请求
|
||||
|
||||
- 登录接口 (`POST /admin/auth/login`)
|
||||
- 健康检查接口 (`GET /up`)
|
||||
- 其他明确排除的路由
|
||||
|
||||
### 3. 敏感信息过滤
|
||||
|
||||
以下字段会被自动过滤,记录为 `******`:
|
||||
- `password`
|
||||
- `password_confirmation`
|
||||
- `token`
|
||||
- `secret`
|
||||
- `key`
|
||||
|
||||
### 4. 错误日志处理
|
||||
|
||||
- 成功请求 (HTTP 状态码 < 400): `status` = `success`
|
||||
- 失败请求 (HTTP 状态码 >= 400): `status` = `error`
|
||||
- 错误时记录响应内容和错误消息
|
||||
- 同时写入 Laravel 日志文件 (`storage/logs/laravel.log`)
|
||||
|
||||
## 模块和操作名称解析
|
||||
|
||||
### 模块名称
|
||||
|
||||
从 URL 路径中解析,例如:
|
||||
- `/admin/users` → 模块: `users`
|
||||
- `/admin/roles` → 模块: `roles`
|
||||
- `/admin/configs` → 模块: `configs`
|
||||
|
||||
### 操作名称
|
||||
|
||||
根据 HTTP 方法和资源名称生成:
|
||||
- `GET /admin/users` → 操作: `查询 users`
|
||||
- `POST /admin/users` → 操作: `创建 users`
|
||||
- `PUT /admin/users/1` → 操作: `更新 users`
|
||||
- `DELETE /admin/users/1` → 操作: `删除 users`
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 定期清理日志
|
||||
|
||||
建议使用 Laravel 任务调度器定期清理历史日志:
|
||||
|
||||
```php
|
||||
// app/Console/Kernel.php
|
||||
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// 每天凌晨 2 点清理 90 天前的日志
|
||||
$schedule->call(function () {
|
||||
app(LogService::class)->clearLogs(90);
|
||||
})->dailyAt('02:00');
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 数据库索引
|
||||
|
||||
确保以下字段有索引:
|
||||
- `user_id`
|
||||
- `username`
|
||||
- `module`
|
||||
- `status`
|
||||
- `created_at`
|
||||
|
||||
### 3. 分页查询
|
||||
|
||||
列表查询必须使用分页,避免一次加载过多数据。
|
||||
|
||||
### 4. 异步记录
|
||||
|
||||
日志记录操作应放在请求处理后,不影响响应速度。
|
||||
|
||||
## 前端集成示例
|
||||
|
||||
### Vue3 + Ant Design Vue
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<a-card title="操作日志">
|
||||
<!-- 搜索表单 -->
|
||||
<a-form layout="inline" :model="searchParams">
|
||||
<a-form-item label="用户名">
|
||||
<a-input v-model:value="searchParams.username" placeholder="请输入用户名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="模块">
|
||||
<a-input v-model:value="searchParams.module" placeholder="请输入模块名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model:value="searchParams.status" placeholder="请选择状态">
|
||||
<a-select-option value="success">成功</a-select-option>
|
||||
<a-select-option value="error">失败</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">查询</a-button>
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
<a-button @click="handleExport">导出</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="logs"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="record.status === 'success' ? 'green' : 'red'">
|
||||
{{ record.status === 'success' ? '成功' : '失败' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<a-button type="link" @click="handleView(record)">查看</a-button>
|
||||
<a-button type="link" danger @click="handleDelete(record.id)">删除</a-button>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const logs = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
const searchParams = reactive({
|
||||
username: '',
|
||||
module: '',
|
||||
status: null,
|
||||
page: 1,
|
||||
page_size: 20
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
total: 0,
|
||||
current: 1,
|
||||
pageSize: 20
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ title: 'ID', dataIndex: 'id', width: 80 },
|
||||
{ title: '用户名', dataIndex: 'username', width: 120 },
|
||||
{ title: '模块', dataIndex: 'module', width: 100 },
|
||||
{ title: '操作', dataIndex: 'action', width: 150 },
|
||||
{ title: '请求方法', dataIndex: 'method', width: 100 },
|
||||
{ title: 'IP 地址', dataIndex: 'ip', width: 150 },
|
||||
{ title: '状态', dataIndex: 'status', slots: { customRender: 'status' }, width: 100 },
|
||||
{ title: '执行时间', dataIndex: 'execution_time', width: 100 },
|
||||
{ title: '创建时间', dataIndex: 'created_at', width: 180 },
|
||||
{ title: '操作', slots: { customRender: 'action' }, width: 150, fixed: 'right' }
|
||||
]
|
||||
|
||||
// 获取日志列表
|
||||
const fetchLogs = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await request.get('/admin/logs', { params: searchParams })
|
||||
logs.value = res.data.list
|
||||
pagination.total = res.data.total
|
||||
pagination.current = res.data.page
|
||||
pagination.pageSize = res.data.page_size
|
||||
} catch (error) {
|
||||
message.error('获取日志失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 查询
|
||||
const handleSearch = () => {
|
||||
searchParams.page = 1
|
||||
fetchLogs()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchParams.username = ''
|
||||
searchParams.module = ''
|
||||
searchParams.status = null
|
||||
searchParams.page = 1
|
||||
fetchLogs()
|
||||
}
|
||||
|
||||
// 导出
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
const res = await request.post('/admin/logs/export', searchParams, {
|
||||
responseType: 'blob'
|
||||
})
|
||||
const url = window.URL.createObjectURL(new Blob([res]))
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `操作日志_${new Date().getTime()}.xlsx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
message.success('导出成功')
|
||||
} catch (error) {
|
||||
message.error('导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = (record) => {
|
||||
// 打开详情对话框
|
||||
console.log('查看日志', record)
|
||||
}
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (id) => {
|
||||
try {
|
||||
await request.delete(`/admin/logs/${id}`)
|
||||
message.success('删除成功')
|
||||
fetchLogs()
|
||||
} catch (error) {
|
||||
message.error('删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 表格分页变化
|
||||
const handleTableChange = (pag) => {
|
||||
searchParams.page = pag.current
|
||||
searchParams.page_size = pag.pageSize
|
||||
fetchLogs()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchLogs()
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **权限控制**: 日志管理接口需要相应的权限才能访问
|
||||
2. **数据安全**: 敏感信息已自动过滤,但仍需注意日志数据的安全存储
|
||||
3. **性能影响**: 虽然日志记录不影响响应速度,但大量日志会增加数据库负载
|
||||
4. **定期备份**: 重要日志数据建议定期备份
|
||||
5. **日志分析**: 可结合 BI 工具对日志数据进行深度分析
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么某些请求没有被记录?
|
||||
|
||||
A: 检查路由是否应用了 `log.request` 中间件,或者在中间件中是否被排除了。
|
||||
|
||||
### Q2: 日志数据过多怎么办?
|
||||
|
||||
A: 使用 `clearLogs` 方法定期清理历史日志,或设置任务调度器自动清理。
|
||||
|
||||
### Q3: 如何自定义日志记录规则?
|
||||
|
||||
A: 修改 `LogRequestMiddleware` 中的 `parseModule` 和 `parseAction` 方法。
|
||||
|
||||
### Q4: 日志记录会影响性能吗?
|
||||
|
||||
A: 日志记录在请求处理后执行,不影响响应速度。但大量日志会增加数据库写入压力。
|
||||
|
||||
### Q5: 如何查看完整的请求参数?
|
||||
|
||||
A: 在日志详情接口中,`params` 字段包含了完整的请求参数(敏感信息已过滤)。
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0 (2024-01-01)
|
||||
- 初始版本
|
||||
- 实现基础日志记录功能
|
||||
- 支持多维度查询和筛选
|
||||
- 支持数据导出
|
||||
- 支持批量删除和清理
|
||||
Reference in New Issue
Block a user