416 lines
10 KiB
Markdown
416 lines
10 KiB
Markdown
# 字典缓存更新机制
|
||
|
||
## 概述
|
||
|
||
本文档说明前后端字典缓存的更新逻辑,确保在字典分类和字典项的增删改等操作后,前端字典缓存能够自动更新。
|
||
|
||
## 技术实现
|
||
|
||
### 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. 缓存一致性
|
||
|
||
- 后端缓存使用 Redis,TTL 为 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)正常运行
|