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