格式化代码,websocket功能完善

This commit is contained in:
2026-02-18 21:50:05 +08:00
parent 6543e2ccdd
commit b6c133952b
101 changed files with 15829 additions and 10739 deletions

View File

@@ -1,415 +0,0 @@
# 字典缓存更新机制
## 概述
本文档说明前后端字典缓存的更新逻辑,确保在字典分类和字典项的增删改等操作后,前端字典缓存能够自动更新。
## 技术实现
### 1. 后端实现
#### 1.1 DictionaryService 更新
`app/Services/System/DictionaryService.php` 中添加了 WebSocket 通知功能:
**依赖注入:**
```php
protected $webSocketService;
public function __construct(WebSocketService $webSocketService)
{
$this->webSocketService = $webSocketService;
}
```
**通知方法:**
1. **字典分类更新通知** (`notifyDictionaryUpdate`)
- 触发时机:创建、更新、删除、批量删除、批量更新状态
- 消息类型:`dictionary_update`
2. **字典项更新通知** (`notifyDictionaryItemUpdate`)
- 触发时机:创建、更新、删除、批量删除、批量更新状态
- 消息类型:`dictionary_item_update`
**修改的方法列表:**
- `create()` - 创建字典分类后发送通知
- `update()` - 更新字典分类后发送通知
- `delete()` - 删除字典分类后发送通知
- `batchDelete()` - 批量删除字典分类后发送通知
- `batchUpdateStatus()` - 批量更新状态后发送通知
- `createItem()` - 创建字典项后发送通知
- `updateItem()` - 更新字典项后发送通知
- `deleteItem()` - 删除字典项后发送通知
- `batchDeleteItems()` - 批量删除字典项后发送通知
- `batchUpdateItemsStatus()` - 批量更新字典项状态后发送通知
#### 1.2 WebSocket 消息格式
**字典分类更新消息:**
```json
{
"type": "dictionary_update",
"data": {
"action": "create|update|delete|batch_delete|batch_update_status",
"resource_type": "dictionary",
"data": {
// 字典分类数据
},
"timestamp": 1234567890
}
}
```
**字典项更新消息:**
```json
{
"type": "dictionary_item_update",
"data": {
"action": "create|update|delete|batch_delete|batch_update_status",
"resource_type": "dictionary_item",
"data": {
// 字典项数据
},
"timestamp": 1234567890
}
}
```
### 2. 前端实现
#### 2.1 WebSocket Composable
创建了 `resources/admin/src/composables/useWebSocket.js` 来处理 WebSocket 连接和消息监听:
**主要功能:**
1. **初始化 WebSocket 连接**
- 检查用户登录状态
- 验证用户信息完整性
- 建立连接并注册消息处理器
2. **消息处理器**
- `handleDictionaryUpdate` - 处理字典分类更新
- `handleDictionaryItemUpdate` - 处理字典项更新
3. **缓存刷新**
- 接收到更新通知后,自动刷新字典缓存
- 显示成功提示消息
#### 2.2 App.vue 集成
`resources/admin/src/App.vue` 中集成了 WebSocket
**生命周期钩子:**
```javascript
onMounted(async () => {
// ... 其他初始化代码
// 初始化 WebSocket 连接
if (userStore.isLoggedIn()) {
initWebSocket()
}
})
onUnmounted(() => {
// 关闭 WebSocket 连接
closeWebSocket()
})
```
## 工作流程
### 完整流程图
```
用户操作(增删改字典)
后端 Controller 调用 Service
Service 执行数据库操作
Service 清理后端缓存Redis
Service 发送 WebSocket 广播通知
WebSocket 推送消息到所有在线客户端
前端接收 WebSocket 消息
触发相应的消息处理器
刷新前端字典缓存
显示成功提示
```
### 详细步骤
1. **用户操作**
- 管理员在后台管理界面进行字典分类或字典项的增删改操作
- 例如:创建新字典分类、修改字典项、批量删除等
2. **后端处理**
- 接收请求并验证数据
- 执行数据库操作INSERT/UPDATE/DELETE
- 清理 Redis 缓存(`DictionaryService::clearCache()`
- 通过 WebSocket 广播更新通知
3. **WebSocket 通知**
- 服务端向所有连接的 WebSocket 客户端广播消息
- 消息包含操作类型、资源类型和更新的数据
4. **前端接收**
- App.vue 在 onMounted 时初始化 WebSocket 连接
- 注册消息处理器监听 `dictionary_update``dictionary_item_update` 事件
- 接收到消息后调用对应的处理器
5. **缓存刷新**
- 处理器调用 `dictionaryStore.refresh(true)` 强制刷新缓存
- 从后端 API 重新加载所有字典数据
- 更新 Pinia store 中的字典数据
- 持久化到本地存储
6. **用户反馈**
- 显示 "字典数据已更新" 的成功提示
- 页面上的字典数据自动更新,无需手动刷新
## 使用示例
### 示例 1创建新字典分类
```php
// 后端代码
$dictionary = $dictionaryService->create([
'name' => '订单状态',
'code' => 'order_status',
'description' => '订单状态字典',
'value_type' => 'string',
'sort' => 1,
'status' => true
]);
// 自动触发:
// 1. 清理 Redis 缓存
// 2. 广播 WebSocket 消息
```
前端自动刷新缓存并显示提示。
### 示例 2更新字典项
```php
// 后端代码
$item = $dictionaryService->updateItem(1, [
'label' => '已付款',
'value' => 'paid',
'sort' => 2
]);
// 自动触发:
// 1. 清理对应字典的 Redis 缓存
// 2. 广播 WebSocket 消息
```
前端自动刷新缓存并显示提示。
### 示例 3批量操作
```php
// 后端代码
$dictionaryService->batchUpdateStatus([1, 2, 3], false);
// 自动触发:
// 1. 清理所有相关字典的 Redis 缓存
// 2. 广播 WebSocket 消息(包含批量更新的 ID
```
前端自动刷新缓存并显示提示。
## 注意事项
### 1. WebSocket 连接
- WebSocket 仅在用户登录后建立连接
- 连接失败会自动重试(最多 5 次)
- 页面卸载时会自动关闭连接
### 2. 缓存一致性
- 后端缓存使用 RedisTTL 为 3600 秒1 小时)
- 前端缓存使用 Pinia + 本地存储持久化
- WebSocket 通知确保前后端缓存同步更新
### 3. 错误处理
- WebSocket 连接失败不影响页面正常使用
- 缓存刷新失败会在控制台输出错误日志
- 不会阻塞用户操作
### 4. 性能考虑
- 批量操作会一次性清理相关缓存
- WebSocket 广播向所有在线用户推送
- 前端刷新时会重新加载所有字典数据
## 扩展建议
### 1. 细粒度缓存更新
当前实现是全量刷新,未来可以优化为增量更新:
```javascript
// 只更新受影响的字典
async function handleDictionaryUpdate(data) {
const { action, data: dictData } = data
if (action === 'update' && dictData.code) {
// 只更新特定的字典
await dictionaryStore.getDictionary(dictData.code, true)
} else {
// 全量刷新
await dictionaryStore.refresh(true)
}
}
```
### 2. 权限控制
可以只向有权限的用户发送通知:
```php
// 后端只向有字典管理权限的用户发送
$adminUserIds = User::whereHas('roles', function($query) {
$query->where('name', 'admin');
})->pluck('id')->toArray();
$this->webSocketService->sendToUsers($adminUserIds, $message);
```
### 3. 消息队列
对于高并发场景,可以使用消息队列异步发送 WebSocket 通知:
```php
// 使用 Laravel 队列
UpdateDictionaryCacheJob::dispatch($action, $data);
```
## 测试建议
### 1. 单元测试
测试后端 WebSocket 通知是否正确发送:
```php
public function testDictionaryUpdateSendsWebSocketNotification()
{
$this->mockWebSocketService();
$dictionary = DictionaryService::create([
'name' => 'Test',
'code' => 'test'
]);
// 验证 WebSocket 广播被调用
}
```
### 2. 集成测试
1. 启动后端服务Laravel-S
2. 启动前端开发服务器
3. 在浏览器中登录系统
4. 打开开发者工具的 Network -> WS 标签查看 WebSocket 消息
5. 执行字典增删改操作
6. 验证:
- WebSocket 消息是否正确接收
- 缓存是否自动刷新
- 页面数据是否更新
- 提示消息是否显示
### 3. 并发测试
1. 打开多个浏览器窗口并登录
2. 在一个窗口中进行字典操作
3. 验证所有窗口的缓存是否同步更新
## 故障排查
### 问题 1前端未收到 WebSocket 消息
**可能原因:**
- WebSocket 服务未启动
- 网络连接问题
- 用户未登录
- Laravel-S 未运行(使用普通 PHP 运行时)
**解决方法:**
1. 检查 Laravel-S 服务是否启动:`php bin/laravels status`
2. 检查浏览器控制台是否有 WebSocket 错误
3. 确认用户已登录且有 token
4. 确认是否在 Laravel-S 环境下运行WebSocket 通知仅在 Laravel-S 环境下有效)
**注意:**
- WebSocket 通知功能依赖于 Laravel-S (Swoole) 环境
- 在普通 PHP 环境下运行时WebSocket 通知会优雅降级(不发送通知,但不影响功能)
- 仍需手动刷新页面或使用 API 轮询来获取最新数据
### 问题 2后端 WebSocket 通知发送失败
**可能原因:**
- Laravel-S 未运行
- Swoole 服务器未启动
- WebSocket 服务实例获取失败
**解决方法:**
1. 确认在 Laravel-S 环境下运行:`php bin/laravels start`
2. 检查 Laravel-S 配置文件 `config/laravels.php`
3. 查看后端日志:`tail -f storage/logs/laravel.log`
**注意:**
- 如果未在 Laravel-S 环境下运行,后端会记录警告日志,但不会报错
- 字典数据仍会正常更新到数据库和 Redis 缓存
- 只是前端不会收到自动更新通知
### 问题 3缓存未更新
**可能原因:**
- WebSocket 消息处理失败
- API 请求失败
- 前端未连接 WebSocket
**解决方法:**
1. 查看浏览器控制台错误日志
2. 检查网络请求是否成功
3. 手动刷新页面验证 API 是否正常
4. 确认 WebSocket 连接状态(浏览器开发者工具 Network -> WS
### 问题 3通知频繁弹出
**可能原因:**
- 批量操作触发了多次通知
**解决方法:**
1. 优化后端批量操作,只发送一次通知
2. 前端添加防抖/节流逻辑
## 总结
通过 WebSocket 实现的字典缓存自动更新机制,确保了前后端数据的一致性,提升了用户体验。用户无需手动刷新页面即可获取最新的字典数据。
### 优势
- ✅ 实时更新,无需手动刷新
- ✅ 多端同步,所有在线用户自动更新
- ✅ 操作透明,用户有明确的反馈
- ✅ 易于扩展,可应用于其他数据类型
### 限制
- 需要稳定的 WebSocket 连接
- 当前实现为全量刷新,可以优化为增量更新
- 依赖后端服务Laravel-S正常运行

View File

@@ -1,337 +0,0 @@
# 日志模块实现总结
## 实现概述
本次优化完善了后端日志模块,实现了自动化的请求日志记录功能,所有后台管理 API 请求都会被自动记录到数据库中。
## 实现内容
### 1. 新增文件
#### 中间件
- **app/Http/Middleware/LogRequestMiddleware.php**
- 自动拦截所有经过的请求
- 记录请求和响应信息
- 计算请求执行时间
- 提取用户信息和操作详情
- 自动过滤敏感参数密码、token等
- 获取客户端真实 IP支持代理
#### 请求验证
- **app/Http/Requests/LogRequest.php**
- 统一的请求参数验证
- 支持列表查询、批量删除、清理等操作的参数验证
- 自定义错误消息
- 自动设置默认值
#### 文档
- **docs/README_LOG.md**
- 完整的模块文档
- API 接口说明
- 数据库表结构
- 使用示例
- 前端集成代码
- 常见问题解答
### 2. 修改文件
#### 控制器
- **app/Http/Controllers/System/Admin/Log.php**
- 添加 `export` 方法:支持导出日志数据为 Excel
- 使用 `LogRequest` 进行参数验证
- 优化响应格式
#### 服务层
- **app/Services/System/LogService.php**
- 添加 `getListQuery` 方法:提供查询构建器(用于导出等场景)
- 新增 `buildQuery` 方法:统一的查询构建逻辑
- 代码重构,减少重复代码
#### 路由配置
- **routes/admin.php**
- 添加 `POST /admin/logs/export` 导出路由
- 在所有需要认证的路由组中应用 `log.request` 中间件
#### 中间件配置
- **bootstrap/app.php**
- 注册 `log.request` 中间件别名
- 创建 `admin.log` 中间件组
## 功能特性
### 自动日志记录
- ✅ 所有后台管理 API 请求自动记录
- ✅ 记录用户信息ID、用户名
- ✅ 记录请求信息方法、URL、参数
- ✅ 记录响应信息(状态码、执行时间)
- ✅ 记录客户端信息IP、User-Agent
- ✅ 错误请求记录详细错误信息
### 敏感信息保护
- ✅ 自动过滤密码字段
- ✅ 自动过滤 token 字段
- ✅ 自动过滤 secret 字段
- ✅ 自动过滤 key 字段
### 日志管理功能
- ✅ 多维度查询用户、模块、操作、状态、时间、IP
- ✅ 分页查询
- ✅ 日志详情查看
- ✅ 日志统计(总数、成功数、失败数)
- ✅ 单条删除
- ✅ 批量删除
- ✅ 定期清理(按天数)
- ✅ 导出为 Excel
### 性能优化
- ✅ 日志记录在请求处理后执行
- ✅ 不影响业务响应速度
- ✅ 异常处理,记录失败不影响业务
- ✅ 支持分页查询,避免一次性加载过多数据
## API 接口列表
| 接口 | 方法 | 说明 |
|------|------|------|
| `/admin/logs` | GET | 获取日志列表 |
| `/admin/logs/{id}` | GET | 获取日志详情 |
| `/admin/logs/statistics` | GET | 获取日志统计 |
| `/admin/logs/export` | POST | 导出日志Excel |
| `/admin/logs/{id}` | DELETE | 删除单条日志 |
| `/admin/logs/batch-delete` | POST | 批量删除日志 |
| `/admin/logs/clear` | POST | 清理历史日志 |
## 数据库表结构
### system_logs 表
已存在的表结构,包含以下字段:
- id: 主键
- user_id: 用户 ID
- username: 用户名
- module: 模块名称
- action: 操作名称
- method: 请求方法
- url: 请求 URL
- ip: 客户端 IP
- user_agent: 用户代理
- params: 请求参数JSON
- result: 响应结果
- status_code: HTTP 状态码
- status: 状态success/error
- error_message: 错误信息
- execution_time: 执行时间(毫秒)
- created_at: 创建时间
- updated_at: 更新时间
## 中间件应用范围
### 已应用的路由
- ✅ 所有 `/admin/*` 路由(除登录接口)
- ✅ 认证相关(登出、刷新、个人信息、修改密码)
- ✅ 用户管理
- ✅ 角色管理
- ✅ 权限管理
- ✅ 部门管理
- ✅ 在线用户管理
- ✅ 系统配置管理
- ✅ 数据字典管理
- ✅ 任务管理
- ✅ 城市数据管理
- ✅ 文件上传管理
### 未应用的路由
- ❌ 登录接口(`POST /admin/auth/login`
- ❌ 健康检查接口(`GET /up`
## 使用示例
### 后端使用
中间件会自动记录所有请求,无需手动调用:
```php
// 任何经过 log.request 中间件的请求都会被自动记录
Route::middleware(['auth.check:admin', 'log.request'])->group(function () {
Route::apiResource('users', UserController::class);
// 其他路由...
});
```
### 前端调用示例
```javascript
// 获取日志列表
const response = await request.get('/admin/logs', {
params: {
username: 'admin',
module: 'users',
status: 'success',
page: 1,
page_size: 20
}
})
// 导出日志
await request.post('/admin/logs/export', {
username: 'admin',
status: 'error'
}, {
responseType: 'blob'
})
// 批量删除
await request.post('/admin/logs/batch-delete', {
ids: [1, 2, 3, 4, 5]
})
// 清理历史日志
await request.post('/admin/logs/clear', {
days: 30
})
```
## 日志记录示例
### 成功请求日志
```json
{
"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",
"password": "******"
},
"result": null,
"status_code": 200,
"status": "success",
"error_message": null,
"execution_time": 125,
"created_at": "2024-01-01 12:00:00"
}
```
### 失败请求日志
```json
{
"id": 2,
"user_id": 1,
"username": "admin",
"module": "users",
"action": "删除 users",
"method": "DELETE",
"url": "http://example.com/admin/users/999",
"ip": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"params": {},
"result": "{\"code\":404,\"message\":\"用户不存在\"}",
"status_code": 404,
"status": "error",
"error_message": "用户不存在",
"execution_time": 45,
"created_at": "2024-01-01 12:01:00"
}
```
## 注意事项
### 1. 性能考虑
- 日志记录在请求处理后执行,不影响响应速度
- 大量日志会增加数据库写入压力
- 建议定期清理历史日志
### 2. 数据安全
- 敏感信息已自动过滤
- 日志数据应妥善保管
- 建议定期备份重要日志
### 3. 权限控制
- 日志管理接口需要相应权限
- 建议只允许管理员查看和操作日志
### 4. 数据库优化
- 确保查询字段有索引
- 使用分页查询避免加载过多数据
- 定期清理历史日志
## 后续优化建议
### 1. 异步队列
考虑使用 Laravel 队列异步处理日志记录,进一步减少对响应时间的影响。
### 2. 日志归档
实现日志归档功能,将历史日志移动到归档表或文件存储。
### 3. 日志分析
集成日志分析工具,提供可视化仪表盘和趋势分析。
### 4. 定时清理
配置 Laravel 任务调度器,自动清理指定天数前的日志:
```php
// app/Console/Kernel.php
$schedule->call(function () {
app(LogService::class)->clearLogs(90);
})->dailyAt('02:00');
```
### 5. 日志级别
增加日志级别info、warning、error、critical便于分类管理。
## 测试建议
### 功能测试
1. 测试各种请求是否被正确记录
2. 测试敏感信息是否被正确过滤
3. 测试日志查询和筛选功能
4. 测试日志导出功能
5. 测试批量删除和清理功能
### 性能测试
1. 测试日志记录对响应时间的影响
2. 测试大量日志数据的查询性能
3. 测试并发写入的性能
### 边界测试
1. 测试异常情况下的日志记录
2. 测试超长参数的处理
3. 测试特殊字符的处理
## 文件清单
### 新增文件
```
app/Http/Middleware/LogRequestMiddleware.php
app/Http/Requests/LogRequest.php
docs/README_LOG.md
docs/LOG_IMPLEMENTATION_SUMMARY.md
```
### 修改文件
```
app/Http/Controllers/System/Admin/Log.php
app/Services/System/LogService.php
routes/admin.php
bootstrap/app.php
```
## 总结
本次日志模块优化完善实现了:
- ✅ 全自动化的请求日志记录
- ✅ 完善的日志管理功能
- ✅ 敏感信息保护
- ✅ 多维度查询和筛选
- ✅ 数据导出功能
- ✅ 批量操作支持
- ✅ 完整的文档说明
日志模块现已完全集成到项目中,所有后台管理 API 请求都会被自动记录,管理员可以通过日志管理功能进行系统监控、审计和问题排查。

1500
docs/README_LARAVERS.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,608 +0,0 @@
# 系统操作日志模块文档
## 概述
系统操作日志模块用于记录后台管理系统的所有操作请求包括用户操作、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)
- 初始版本
- 实现基础日志记录功能
- 支持多维度查询和筛选
- 支持数据导出
- 支持批量删除和清理

View File

@@ -44,6 +44,8 @@ app/Http/Controllers/System/
- Laravel 11
- Redis 缓存
- Intervention Image (图像处理)
- Laravel-S / Swoole (WebSocket 通知)
- WebSocket (实时消息推送)
## 数据库表结构
@@ -84,15 +86,18 @@ app/Http/Controllers/System/
- `username`: 用户名
- `module`: 模块
- `action`: 操作
- `method`: 请求方法
- `method`: 请求方法 (GET/POST/PUT/DELETE)
- `url`: 请求URL
- `ip`: IP地址
- `user_agent`: 用户代理
- `request_data`: 请求数JSON
- `response_data`: 响应数据JSON
- `duration`: 执行时间(毫秒)
- `status_code`: 状态
- `params`: 请求JSON
- `result`: 响应结果(仅错误时记录
- `status_code`: HTTP状态码
- `status`: 状态 (success/error)
- `error_message`: 错误信息
- `execution_time`: 执行时间(毫秒)
- `created_at`: 创建时间
- `updated_at`: 更新时间
### system_tasks (任务表)
- `id`: 主键
@@ -197,34 +202,220 @@ app/Http/Controllers/System/
### 操作日志管理
操作日志模块通过中间件自动记录所有后台管理 API 请求,实现全自动化的日志记录功能。
#### 中间件说明
**LogRequestMiddleware**
位置: `app/Http/Middleware/LogRequestMiddleware.php`
功能:
- 自动拦截所有经过的请求
- 记录请求和响应信息
- 计算请求执行时间
- 提取用户信息和操作详情
- 过滤敏感参数password、token、secret、key
- 获取客户端真实 IP支持代理
- 异常处理,记录失败不影响业务
使用方式:
```php
// 在路由中应用
Route::middleware(['auth.check:admin', 'log.request'])->group(function () {
// 需要记录日志的路由
});
```
#### 日志记录规则
**自动记录的请求:**
所有经过 `log.request` 中间件的请求都会被自动记录,包括:
- 用户管理操作
- 角色管理操作
- 权限管理操作
- 部门管理操作
- 系统配置操作
- 其他所有后台管理操作
**不记录的请求:**
- 登录接口 (`POST /admin/auth/login`)
- 健康检查接口 (`GET /up`)
- 其他明确排除的路由
**敏感信息过滤:**
以下字段会被自动过滤,记录为 `******`:
- `password`
- `password_confirmation`
- `token`
- `secret`
- `key`
**错误日志处理:**
- 成功请求 (HTTP 状态码 < 400): `status` = `success`
- 失败请求 (HTTP 状态码 >= 400): `status` = `error`
- 错误时记录响应内容和错误消息
- 同时写入 Laravel 日志文件 (`storage/logs/laravel.log`)
#### 获取日志列表
- **接口**: `GET /admin/logs`
- **参数**:
- `page`, `page_size`
- `keyword`: 搜索关键词(用户名/模块/操作)
- `module`: 模块
- `action`: 操作
- `user_id`: 用户ID
- `start_date`: 开始日期
- `end_date`: 结束日期
- `order_by`, `order_direction`
```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"
}
],
"total": 100,
"page": 1,
"page_size": 20
}
}
```
#### 获取日志详情
- **接口**: `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"
}
}
}
```
#### 删除日志
#### 获取日志统计
- **接口**: `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
}
}
```
#### 导出日志
- **接口**: `POST /admin/logs/export`
- **参数**: 与获取日志列表相同的查询参数
- **响应**: Excel 文件下载
- **文件名格式**: `系统操作日志_YYYYMMDDHHmmss.xlsx`
- **包含字段**:
- ID
- 用户名
- 模块
- 操作
- 请求方法
- URL
- IP 地址
- 状态码
- 状态
- 错误信息
- 执行时间(ms)
- 创建时间
#### 删除单条日志
- **接口**: `DELETE /admin/logs/{id}`
- **响应示例**:
```json
{
"code": 200,
"message": "删除成功",
"data": null
}
```
#### 批量删除日志
- **接口**: `POST /admin/logs/batch-delete`
- **参数**:
```json
{
"ids": [1, 2, 3]
"ids": [1, 2, 3, 4, 5]
}
```
- **响应示例**:
```json
{
"code": 200,
"message": "批量删除成功",
"data": null
}
```
#### 清理日志
#### 清理历史日志
- **接口**: `POST /admin/logs/clear`
- **参数**:
```json
@@ -232,39 +423,129 @@ app/Http/Controllers/System/
"days": 30
}
```
- **说明**: 删除指定天数前的日志记录
#### 获取日志统计
- **接口**: `GET /admin/logs/statistics`
- **参数**:
- `start_date`: 开始日期
- `end_date`: 结束日期
- **返回**:
- **说明**: 清理指定天数前的所有日志记录,默认清理 30 天前的数据
- **响应示例**:
```json
{
"code": 200,
"message": "success",
"data": {
"total_count": 1000,
"module_stats": [
{
"module": "user",
"count": 500
}
],
"user_stats": [
{
"user_id": 1,
"username": "admin",
"count": 800
}
]
}
"message": "清理成功",
"data": null
}
```
### 数据字典管理
数据字典模块提供了完整的字典管理功能,包括字典分类和字典项的 CRUD 操作。通过 WebSocket 实现了前后端缓存的实时同步更新。
#### 字典缓存更新机制
**概述**
字典缓存更新机制通过 WebSocket 实现前后端字典缓存的实时同步,确保在字典分类和字典项的增删改等操作后,前端字典缓存能够自动更新。
**技术实现**
1. **后端实现**
`app/Services/System/DictionaryService.php` 中添加了 WebSocket 通知功能:
**通知方法:**
- `notifyDictionaryUpdate` - 字典分类更新通知
- 触发时机:创建、更新、删除、批量删除、批量更新状态
- 消息类型:`dictionary_update`
- `notifyDictionaryItemUpdate` - 字典项更新通知
- 触发时机:创建、更新、删除、批量删除、批量更新状态
- 消息类型:`dictionary_item_update`
**WebSocket 消息格式:**
字典分类更新消息:
```json
{
"type": "dictionary_update",
"data": {
"action": "create|update|delete|batch_delete|batch_update_status",
"resource_type": "dictionary",
"data": {
// 字典分类数据
},
"timestamp": 1234567890
}
}
```
字典项更新消息:
```json
{
"type": "dictionary_item_update",
"data": {
"action": "create|update|delete|batch_delete|batch_update_status",
"resource_type": "dictionary_item",
"data": {
// 字典项数据
},
"timestamp": 1234567890
}
}
```
2. **前端实现**
创建了 `resources/admin/src/composables/useWebSocket.js` 来处理 WebSocket 连接和消息监听:
**主要功能:**
- 初始化 WebSocket 连接(检查用户登录状态、验证用户信息完整性)
- 消息处理器:`handleDictionaryUpdate``handleDictionaryItemUpdate`
- 缓存刷新:接收到更新通知后,自动刷新字典缓存并显示成功提示
**App.vue 集成:**
```javascript
onMounted(async () => {
// 初始化 WebSocket 连接
if (userStore.isLoggedIn()) {
initWebSocket()
}
})
onUnmounted(() => {
// 关闭 WebSocket 连接
closeWebSocket()
})
```
**工作流程:**
```
用户操作(增删改字典)
后端 Controller 调用 Service
Service 执行数据库操作
Service 清理后端缓存Redis
Service 发送 WebSocket 广播通知
WebSocket 推送消息到所有在线客户端
前端接收 WebSocket 消息
触发相应的消息处理器
刷新前端字典缓存
显示成功提示
```
**注意事项:**
- WebSocket 仅在用户登录后建立连接
- 连接失败会自动重试(最多 5 次)
- 页面卸载时会自动关闭连接
- WebSocket 通知功能依赖于 Laravel-S (Swoole) 环境
- 在普通 PHP 环境下运行时WebSocket 通知会优雅降级(不发送通知,但不影响功能)
#### 获取字典列表
- **接口**: `GET /admin/dictionaries`
- **参数**:
@@ -658,8 +939,17 @@ app/Http/Controllers/System/
### 数据字典缓存
- **缓存键**: `dictionary:all``dictionary:code:{code}`
- **过期时间**: 60分钟
- **更新时机**: 字典数据增删改时自动清除
- **过期时间**: 3600秒1小时
- **更新时机**:
- 字典数据增删改时自动清除后端缓存Redis
- 通过 WebSocket 通知前端自动刷新缓存
**字典缓存同步流程:**
1. 后端执行字典操作(增删改)
2. 清理 Redis 缓存
3. 发送 WebSocket 广播通知
4. 前端接收通知并自动刷新缓存
5. 显示成功提示
## 服务层说明
@@ -681,13 +971,24 @@ app/Http/Controllers/System/
**主要方法**:
- `getList()`: 获取日志列表
- `getById()`: 根据 ID 获取日志详情
- `getStatistics()`: 获取统计数据
- `getListQuery()`: 获取日志查询构建器
- `clearLogs()`: 清理过期日志
- `delete()`: 删除单条日志
- `batchDelete()`: 批量删除日志
- `record()`: 记录日志(由中间件自动调用)
**特性**:
- 自动记录所有经过中间件的请求
- 计算请求执行时间
- 过滤敏感参数
- 获取客户端真实 IP支持代理
- 异常处理,记录失败不影响业务
### DictionaryService
提供数据字典和字典项的管理功能。
提供数据字典和字典项的管理功能,包括 WebSocket 通知机制
**主要方法**:
- `getList()`: 获取字典列表
@@ -696,6 +997,13 @@ app/Http/Controllers/System/
- `createItem()`: 创建字典项
- `update()`: 更新字典
- `updateItem()`: 更新字典项
- `notifyDictionaryUpdate()`: 发送字典更新通知
- `notifyDictionaryItemUpdate()`: 发送字典项更新通知
**缓存机制**:
- Redis 缓存字典数据TTL: 3600秒
- WebSocket 实时通知前端更新
- 前端 Pinia + 本地存储持久化
### TaskService
@@ -744,49 +1052,459 @@ php artisan db:seed --class=SystemSeeder
- 常用数据字典
- 全国省市区数据
## 前端集成示例
### 日志管理页面
```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. **Swoole环境注意事项**:
- 文件上传时注意临时文件清理
- 使用Redis缓存避免内存泄漏
- 图片压缩使用协程安全的方式
### 1. Swoole环境注意事项
- 文件上传时注意临时文件清理
- 使用Redis缓存避免内存泄漏
- 图片压缩使用协程安全的方式
- WebSocket 通知依赖于 Laravel-S 环境
2. **安全注意事项**:
- 文件上传必须验证文件类型和大小
- 敏感操作必须记录日志
- 配置数据不要存储密码等敏感信息
### 2. 安全注意事项
- 文件上传必须验证文件类型和大小
- 敏感操作必须记录日志
- 配置数据不要存储密码等敏感信息
- 日志敏感信息已自动过滤
3. **性能优化**:
- 城市数据使用Redis缓存
- 大量日志数据定期清理
- 图片上传时进行压缩处理
### 3. 性能优化
- 城市数据使用Redis缓存
- 大量日志数据定期清理
- 图片上传时进行压缩处理
- 日志记录在请求处理后执行,不影响响应速度
4. **文件上传**:
- 限制文件上传大小
- 验证文件MIME类型
- 定期清理临时文件
### 4. 文件上传
- 限制文件上传大小
- 验证文件MIME类型
- 定期清理临时文件
### 5. 日志管理
- 定期清理历史日志(建议使用任务调度器)
- 确保查询字段有索引
- 使用分页查询避免加载过多数据
## 性能优化建议
### 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. 数据库索引
确保以下字段有索引:
- `system_logs`: `user_id`, `username`, `module`, `status`, `created_at`
- `system_dictionaries`: `code`, `status`
- `system_dictionary_items`: `dictionary_id`, `status`
- `system_configs`: `group`, `key`, `status`
### 3. 分页查询
列表查询必须使用分页,避免一次加载过多数据。
### 4. 异步记录
日志记录操作应放在请求处理后,不影响响应速度。
### 5. 细粒度缓存更新
字典缓存当前实现为全量刷新,未来可以优化为增量更新:
```javascript
// 只更新受影响的字典
async function handleDictionaryUpdate(data) {
const { action, data: dictData } = data
if (action === 'update' && dictData.code) {
// 只更新特定的字典
await dictionaryStore.getDictionary(dictData.code, true)
} else {
// 全量刷新
await dictionaryStore.refresh(true)
}
}
```
## 扩展建议
1. **日志告警**: 添加日志异常告警功能
2. **配置加密**: 敏感配置数据加密存储
3. **多语言**: 支持配置数据的多语言
4. **任务监控**: 添加任务执行监控和通知
5. **CDN集成**: 文件上传支持CDN分发
### 1. 日志告警
添加日志异常告警功能,当出现大量错误日志时自动通知管理员。
### 2. 配置加密
敏感配置数据加密存储,提高安全性。
### 3. 多语言
支持配置数据的多语言,便于国际化部署。
### 4. 任务监控
添加任务执行监控和通知,实时掌握任务运行状态。
### 5. CDN集成
文件上传支持CDN分发提高访问速度。
### 6. WebSocket 权限控制
可以只向有权限的用户发送通知:
```php
// 后端只向有字典管理权限的用户发送
$adminUserIds = User::whereHas('roles', function($query) {
$query->where('name', 'admin');
})->pluck('id')->toArray();
$this->webSocketService->sendToUsers($adminUserIds, $message);
```
### 7. 消息队列
对于高并发场景,可以使用消息队列异步发送 WebSocket 通知:
```php
// 使用 Laravel 队列
UpdateDictionaryCacheJob::dispatch($action, $data);
```
## 常见问题
### Q: 如何清除城市数据缓存?
### Q1: 如何清除城市数据缓存?
A: 调用 `CityService::clearCache()` 方法或运行 `php artisan cache:forget city:tree`
### Q: 图片上传后如何压缩?
### Q2: 图片上传后如何压缩?
A: 上传时设置 `compress=true``quality` 参数,系统会自动压缩。
### Q: 如何配置定时任务?
### Q3: 如何配置定时任务?
A: 在Admin后台创建任务设置Cron表达式系统会自动调度执行。
### Q: 数据字典如何使用?
### Q4: 数据字典如何使用?
A: 通过Public API获取字典数据前端根据数据渲染下拉框等组件。
### Q: 日志数据过多如何处理?
### Q5: 日志数据过多如何处理?
A: 定期使用 `/admin/logs/clear` 接口清理过期日志,或在后台设置自动清理任务。
### Q6: 为什么某些请求没有被记录?
A: 检查路由是否应用了 `log.request` 中间件,或者在中间件中是否被排除了。
### Q7: 字典缓存未更新怎么办?
A: 检查以下几点:
- 确认 Laravel-S 服务是否启动:`php bin/laravels status`
- 检查浏览器控制台是否有 WebSocket 错误
- 确认用户已登录且有 token
- 手动刷新页面验证 API 是否正常
### Q8: WebSocket 连接失败会影响功能吗?
A: 不会。WebSocket 连接失败不影响页面正常使用,只是不会收到自动更新通知。字典数据仍会正常更新到数据库和 Redis 缓存,只是前端不会收到实时通知,需要手动刷新页面。
## 测试建议
### 1. 功能测试
1. 测试各种请求是否被正确记录
2. 测试敏感信息是否被正确过滤
3. 测试日志查询和筛选功能
4. 测试日志导出功能
5. 测试批量删除和清理功能
6. 测试字典 WebSocket 通知是否正确
### 2. 性能测试
1. 测试日志记录对响应时间的影响
2. 测试大量日志数据的查询性能
3. 测试并发写入的性能
4. 测试 WebSocket 广播性能
### 3. 集成测试(字典 WebSocket
1. 启动后端服务Laravel-S
2. 启动前端开发服务器
3. 在浏览器中登录系统
4. 打开开发者工具的 Network -> WS 标签查看 WebSocket 消息
5. 执行字典增删改操作
6. 验证:
- WebSocket 消息是否正确接收
- 缓存是否自动刷新
- 页面数据是否更新
- 提示消息是否显示
### 4. 并发测试
1. 打开多个浏览器窗口并登录
2. 在一个窗口中进行字典操作
3. 验证所有窗口的缓存是否同步更新
### 5. 边界测试
1. 测试异常情况下的日志记录
2. 测试超长参数的处理
3. 测试特殊字符的处理
4. 测试 WebSocket 断连重连机制
## 文件清单
### 核心文件
**控制器:**
```
app/Http/Controllers/System/Admin/Config.php
app/Http/Controllers/System/Admin/Log.php
app/Http/Controllers/System/Admin/Dictionary.php
app/Http/Controllers/System/Admin/Task.php
app/Http/Controllers/System/Admin/City.php
app/Http/Controllers/System/Admin/Upload.php
app/Http/Controllers/System/WebSocket.php
```
**中间件:**
```
app/Http/Middleware/LogRequestMiddleware.php
```
**请求验证:**
```
app/Http/Requests/LogRequest.php
```
**服务层:**
```
app/Services/System/ConfigService.php
app/Services/System/LogService.php
app/Services/System/DictionaryService.php
app/Services/System/TaskService.php
app/Services/System/CityService.php
app/Services/System/UploadService.php
app/Services/WebSocket/WebSocketService.php
```
**模型:**
```
app/Models/System/Config.php
app/Models/System/Log.php
app/Models/System/Dictionary.php
app/Models/System/DictionaryItem.php
app/Models/System/Task.php
app/Models/System/City.php
```
**路由:**
```
routes/admin.php (后台管理路由)
routes/api.php (公共 API 路由)
```
**前端:**
```
resources/admin/src/composables/useWebSocket.js
resources/admin/src/App.vue (集成 WebSocket)
```
**文档:**
```
docs/README_SYSTEM.md (本文档)
```
## 总结
System 基础模块提供了完整的系统管理功能,包括:
### 核心功能
- ✅ 系统配置管理(多分组、多类型支持)
- ✅ 数据字典管理(分类 + 字典项)
- ✅ 操作日志管理(自动记录、多维度查询、导出)
- ✅ 任务管理(定时任务、手动执行、统计)
- ✅ 城市数据管理(三级联动、缓存优化)
- ✅ 文件上传管理单文件、多文件、Base64、压缩
### 高级特性
- ✅ WebSocket 实时通知(字典缓存自动更新)
- ✅ Redis 缓存机制(性能优化)
- ✅ 自动化日志记录(中间件拦截)
- ✅ 敏感信息保护(自动过滤)
- ✅ 数据导出功能Excel 导出)
- ✅ 批量操作支持
- ✅ 完整的 API 文档
### 性能优化
- ✅ Redis 缓存(城市数据、系统配置、数据字典)
- ✅ 日志异步记录(不影响响应速度)
- ✅ 分页查询(避免加载过多数据)
- ✅ 图片压缩(减少存储空间)
- ✅ WebSocket 实时更新(减少不必要的请求)
### 安全特性
- ✅ 敏感信息过滤
- ✅ 文件类型验证
- ✅ 请求日志记录
- ✅ IP 地址记录
- ✅ 异常处理机制
System 模块现已完全集成到项目中,提供了完整的系统管理功能和优秀的用户体验。

File diff suppressed because it is too large Load Diff