ai-engineering-init 1.3.4 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/skill-forced-eval.js +2 -0
- package/.claude/settings.json +3 -3
- package/.claude/skills/add-skill/SKILL.md +79 -32
- package/.claude/skills/api-development/SKILL.md +83 -377
- package/.claude/skills/architecture-design/SKILL.md +138 -632
- package/.claude/skills/backend-annotations/SKILL.md +134 -506
- package/.claude/skills/banana-image/SKILL.md +10 -3
- package/.claude/skills/brainstorm/SKILL.md +103 -535
- package/.claude/skills/bug-detective/SKILL.md +147 -1097
- package/.claude/skills/bug-detective/references/error-patterns.md +242 -0
- package/.claude/skills/code-patterns/SKILL.md +116 -426
- package/.claude/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.claude/skills/crud-development/SKILL.md +64 -304
- package/.claude/skills/data-permission/SKILL.md +105 -412
- package/.claude/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.claude/skills/file-oss-management/SKILL.md +106 -714
- package/.claude/skills/file-oss-management/references/entities.md +105 -0
- package/.claude/skills/file-oss-management/references/service-impl.md +104 -0
- package/.claude/skills/leniu-api-development/SKILL.md +142 -626
- package/.claude/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.claude/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.claude/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.claude/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.claude/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.claude/skills/leniu-crud-development/SKILL.md +232 -938
- package/.claude/skills/leniu-crud-development/references/templates.md +597 -0
- package/.claude/skills/leniu-customization-location/SKILL.md +410 -0
- package/.claude/skills/leniu-data-permission/SKILL.md +70 -0
- package/.claude/skills/leniu-java-entity/SKILL.md +76 -590
- package/.claude/skills/leniu-java-entity/references/templates.md +237 -0
- package/.claude/skills/leniu-java-export/SKILL.md +94 -379
- package/.claude/skills/leniu-java-logging/SKILL.md +106 -709
- package/.claude/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.claude/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.claude/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.claude/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.claude/skills/leniu-report-customization/SKILL.md +111 -365
- package/.claude/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.claude/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.claude/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.claude/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.claude/skills/leniu-security-guard/SKILL.md +133 -347
- package/.claude/skills/mysql-debug/SKILL.md +364 -0
- package/.claude/skills/openspec-apply-change/SKILL.md +10 -1
- package/.claude/skills/openspec-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.claude/skills/openspec-continue-change/SKILL.md +9 -1
- package/.claude/skills/openspec-explore/SKILL.md +10 -1
- package/.claude/skills/openspec-ff-change/SKILL.md +9 -1
- package/.claude/skills/openspec-new-change/SKILL.md +9 -1
- package/.claude/skills/openspec-onboard/SKILL.md +15 -130
- package/.claude/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.claude/skills/openspec-verify-change/SKILL.md +9 -1
- package/.claude/skills/performance-doctor/SKILL.md +110 -434
- package/.claude/skills/redis-cache/SKILL.md +89 -595
- package/.claude/skills/redis-cache/references/listeners.md +23 -0
- package/.claude/skills/scheduled-jobs/SKILL.md +88 -407
- package/.claude/skills/security-guard/SKILL.md +137 -532
- package/.claude/skills/security-guard/references/encrypt-config.md +103 -0
- package/.claude/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.claude/skills/sms-mail/SKILL.md +116 -574
- package/.claude/skills/sms-mail/references/mail-config.md +88 -0
- package/.claude/skills/sms-mail/references/sms-config.md +74 -0
- package/.claude/skills/social-login/SKILL.md +112 -514
- package/.claude/skills/social-login/references/provider-configs.md +118 -0
- package/.claude/skills/tenant-management/SKILL.md +129 -444
- package/.claude/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.claude/skills/test-development/SKILL.md +86 -540
- package/.claude/skills/test-development/references/parameterized-examples.md +119 -0
- package/.claude/skills/utils-toolkit/SKILL.md +52 -305
- package/.claude/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.claude/skills/websocket-sse/SKILL.md +105 -550
- package/.claude/skills/workflow-engine/SKILL.md +147 -502
- package/.codex/skills/add-skill/SKILL.md +79 -32
- package/.codex/skills/api-development/SKILL.md +172 -599
- package/.codex/skills/architecture-design/SKILL.md +138 -504
- package/.codex/skills/backend-annotations/SKILL.md +134 -496
- package/.codex/skills/banana-image/SKILL.md +10 -3
- package/.codex/skills/brainstorm/SKILL.md +103 -535
- package/.codex/skills/bug-detective/SKILL.md +147 -1097
- package/.codex/skills/bug-detective/references/error-patterns.md +242 -0
- package/.codex/skills/code-patterns/SKILL.md +120 -282
- package/.codex/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.codex/skills/crud-development/SKILL.md +64 -292
- package/.codex/skills/data-permission/SKILL.md +108 -407
- package/.codex/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.codex/skills/database-ops/SKILL.md +8 -154
- package/.codex/skills/error-handler/SKILL.md +10 -0
- package/.codex/skills/file-oss-management/SKILL.md +106 -714
- package/.codex/skills/file-oss-management/references/entities.md +105 -0
- package/.codex/skills/file-oss-management/references/service-impl.md +104 -0
- package/.codex/skills/git-workflow/SKILL.md +27 -5
- package/.codex/skills/leniu-api-development/SKILL.md +142 -626
- package/.codex/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.codex/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.codex/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.codex/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.codex/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.codex/skills/leniu-crud-development/SKILL.md +232 -938
- package/.codex/skills/leniu-crud-development/references/templates.md +597 -0
- package/.codex/skills/leniu-customization-location/SKILL.md +410 -0
- package/.codex/skills/leniu-data-permission/SKILL.md +70 -0
- package/.codex/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.codex/skills/leniu-java-entity/SKILL.md +76 -590
- package/.codex/skills/leniu-java-entity/references/templates.md +237 -0
- package/.codex/skills/leniu-java-export/SKILL.md +94 -379
- package/.codex/skills/leniu-java-logging/SKILL.md +106 -709
- package/.codex/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.codex/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.codex/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.codex/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.codex/skills/leniu-report-customization/SKILL.md +111 -365
- package/.codex/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.codex/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.codex/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.codex/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.codex/skills/leniu-security-guard/SKILL.md +133 -347
- package/.codex/skills/mysql-debug/SKILL.md +364 -0
- package/.codex/skills/openspec-apply-change/SKILL.md +10 -1
- package/.codex/skills/openspec-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.codex/skills/openspec-continue-change/SKILL.md +9 -1
- package/.codex/skills/openspec-explore/SKILL.md +10 -1
- package/.codex/skills/openspec-ff-change/SKILL.md +9 -1
- package/.codex/skills/openspec-new-change/SKILL.md +9 -1
- package/.codex/skills/openspec-onboard/SKILL.md +15 -130
- package/.codex/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.codex/skills/openspec-verify-change/SKILL.md +9 -1
- package/.codex/skills/performance-doctor/SKILL.md +110 -434
- package/.codex/skills/project-navigator/SKILL.md +20 -1
- package/.codex/skills/redis-cache/SKILL.md +93 -589
- package/.codex/skills/redis-cache/references/listeners.md +23 -0
- package/.codex/skills/scheduled-jobs/SKILL.md +88 -407
- package/.codex/skills/security-guard/SKILL.md +141 -527
- package/.codex/skills/security-guard/references/encrypt-config.md +103 -0
- package/.codex/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.codex/skills/sms-mail/SKILL.md +116 -574
- package/.codex/skills/sms-mail/references/mail-config.md +88 -0
- package/.codex/skills/sms-mail/references/sms-config.md +74 -0
- package/.codex/skills/social-login/SKILL.md +112 -514
- package/.codex/skills/social-login/references/provider-configs.md +118 -0
- package/.codex/skills/store-pc/SKILL.md +258 -383
- package/.codex/skills/tenant-management/SKILL.md +129 -444
- package/.codex/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.codex/skills/test-development/SKILL.md +86 -540
- package/.codex/skills/test-development/references/parameterized-examples.md +119 -0
- package/.codex/skills/ui-pc/SKILL.md +350 -387
- package/.codex/skills/utils-toolkit/SKILL.md +52 -283
- package/.codex/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.codex/skills/websocket-sse/SKILL.md +105 -550
- package/.codex/skills/workflow-engine/SKILL.md +147 -502
- package/.cursor/hooks.json +3 -3
- package/.cursor/skills/add-skill/SKILL.md +79 -32
- package/.cursor/skills/api-development/SKILL.md +83 -377
- package/.cursor/skills/architecture-design/SKILL.md +138 -632
- package/.cursor/skills/backend-annotations/SKILL.md +134 -506
- package/.cursor/skills/banana-image/SKILL.md +10 -3
- package/.cursor/skills/brainstorm/SKILL.md +103 -535
- package/.cursor/skills/bug-detective/SKILL.md +147 -1097
- package/.cursor/skills/bug-detective/references/error-patterns.md +242 -0
- package/.cursor/skills/code-patterns/SKILL.md +116 -426
- package/.cursor/skills/code-patterns/references/leniu-code-patterns.md +87 -0
- package/.cursor/skills/crud-development/SKILL.md +64 -304
- package/.cursor/skills/data-permission/SKILL.md +105 -412
- package/.cursor/skills/data-permission/references/custom-data-scope.md +90 -0
- package/.cursor/skills/file-oss-management/SKILL.md +106 -714
- package/.cursor/skills/file-oss-management/references/entities.md +105 -0
- package/.cursor/skills/file-oss-management/references/service-impl.md +104 -0
- package/.cursor/skills/git-workflow/SKILL.md +27 -5
- package/.cursor/skills/leniu-api-development/SKILL.md +142 -626
- package/.cursor/skills/leniu-api-development/references/real-examples.md +273 -0
- package/.cursor/skills/leniu-architecture-design/SKILL.md +176 -391
- package/.cursor/skills/leniu-backend-annotations/SKILL.md +132 -519
- package/.cursor/skills/leniu-brainstorm/SKILL.md +132 -541
- package/.cursor/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
- package/.cursor/skills/leniu-crud-development/SKILL.md +232 -938
- package/.cursor/skills/leniu-crud-development/references/templates.md +597 -0
- package/.cursor/skills/leniu-customization-location/SKILL.md +410 -0
- package/.cursor/skills/leniu-data-permission/SKILL.md +70 -0
- package/.cursor/skills/leniu-java-code-style/SKILL.md +510 -0
- package/.cursor/skills/leniu-java-entity/SKILL.md +76 -590
- package/.cursor/skills/leniu-java-entity/references/templates.md +237 -0
- package/.cursor/skills/leniu-java-export/SKILL.md +94 -379
- package/.cursor/skills/leniu-java-logging/SKILL.md +106 -709
- package/.cursor/skills/leniu-java-logging/references/data-mask.md +46 -0
- package/.cursor/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
- package/.cursor/skills/leniu-java-mybatis/SKILL.md +73 -446
- package/.cursor/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
- package/.cursor/skills/leniu-report-customization/SKILL.md +111 -365
- package/.cursor/skills/leniu-report-customization/references/table-fields.md +93 -0
- package/.cursor/skills/leniu-report-standard-customization/SKILL.md +111 -334
- package/.cursor/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
- package/.cursor/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
- package/.cursor/skills/leniu-security-guard/SKILL.md +133 -347
- package/.cursor/skills/mysql-debug/SKILL.md +364 -0
- package/.cursor/skills/openspec-apply-change/SKILL.md +10 -1
- package/.cursor/skills/openspec-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-bulk-archive-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-continue-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-explore/SKILL.md +10 -1
- package/.cursor/skills/openspec-ff-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-new-change/SKILL.md +9 -1
- package/.cursor/skills/openspec-onboard/SKILL.md +15 -130
- package/.cursor/skills/openspec-sync-specs/SKILL.md +9 -1
- package/.cursor/skills/openspec-verify-change/SKILL.md +9 -1
- package/.cursor/skills/performance-doctor/SKILL.md +110 -434
- package/.cursor/skills/redis-cache/SKILL.md +89 -595
- package/.cursor/skills/redis-cache/references/listeners.md +23 -0
- package/.cursor/skills/scheduled-jobs/SKILL.md +88 -407
- package/.cursor/skills/security-guard/SKILL.md +137 -532
- package/.cursor/skills/security-guard/references/encrypt-config.md +103 -0
- package/.cursor/skills/security-guard/references/sensitive-strategies.md +42 -0
- package/.cursor/skills/sms-mail/SKILL.md +116 -574
- package/.cursor/skills/sms-mail/references/mail-config.md +88 -0
- package/.cursor/skills/sms-mail/references/sms-config.md +74 -0
- package/.cursor/skills/social-login/SKILL.md +112 -514
- package/.cursor/skills/social-login/references/provider-configs.md +118 -0
- package/.cursor/skills/tenant-management/SKILL.md +129 -444
- package/.cursor/skills/tenant-management/references/tenant-scenarios.md +91 -0
- package/.cursor/skills/test-development/SKILL.md +86 -540
- package/.cursor/skills/test-development/references/parameterized-examples.md +119 -0
- package/.cursor/skills/utils-toolkit/SKILL.md +52 -305
- package/.cursor/skills/utils-toolkit/references/redis-utils-api.md +56 -0
- package/.cursor/skills/websocket-sse/SKILL.md +105 -550
- package/.cursor/skills/workflow-engine/SKILL.md +147 -502
- package/package.json +1 -1
|
@@ -17,70 +17,29 @@ description: |
|
|
|
17
17
|
|
|
18
18
|
> **适用模块**:`ruoyi-common-websocket`、`ruoyi-common-sse`
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## 方案选型
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
| 方案 | 模块 | 方向 | 场景 |
|
|
23
|
+
|------|------|------|------|
|
|
24
|
+
| **WebSocket** | `ruoyi-common-websocket` | 双向 | 聊天、协作、低延迟交互 |
|
|
25
|
+
| **SSE** | `ruoyi-common-sse` | 服务端→客户端 | 通知推送、状态更新、AI流式响应 |
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|------|------|---------|---------|
|
|
26
|
-
| **WebSocket** | `ruoyi-common-websocket` | 双向通信 | 聊天、协作编辑、游戏 |
|
|
27
|
-
| **SSE** | `ruoyi-common-sse` | 服务端→客户端 | 通知推送、状态更新、数据流 |
|
|
28
|
-
|
|
29
|
-
**共同特性**:
|
|
30
|
-
- ✅ Sa-Token 认证集成
|
|
31
|
-
- ✅ Redis 发布订阅(多实例消息同步)
|
|
32
|
-
- ✅ 开箱即用,配置启用即可
|
|
27
|
+
**共同特性**:Sa-Token 认证集成、Redis 发布订阅(多实例消息同步)、配置启用即可。
|
|
33
28
|
|
|
34
29
|
---
|
|
35
30
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
### 何时使用 WebSocket
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
✅ 需要双向通信(客户端也要发送消息)
|
|
42
|
-
✅ 即时聊天、协作编辑
|
|
43
|
-
✅ 游戏、实时交互应用
|
|
44
|
-
✅ 需要低延迟的场景
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### 何时使用 SSE
|
|
31
|
+
## 一、WebSocket
|
|
48
32
|
|
|
49
|
-
|
|
50
|
-
✅ 只需服务端向客户端推送
|
|
51
|
-
✅ 系统通知、订单状态变更
|
|
52
|
-
✅ 数据仪表盘实时更新
|
|
53
|
-
✅ AI 流式响应(类似 ChatGPT)
|
|
54
|
-
✅ 需要简单实现、不需要双向通信
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### 对比表
|
|
58
|
-
|
|
59
|
-
| 特性 | WebSocket | SSE |
|
|
60
|
-
|------|-----------|-----|
|
|
61
|
-
| 通信方向 | 双向 | 单向(服务端→客户端) |
|
|
62
|
-
| 协议 | ws:// / wss:// | HTTP |
|
|
63
|
-
| 浏览器支持 | 全部现代浏览器 | 全部现代浏览器 |
|
|
64
|
-
| 自动重连 | 需自行实现 | 浏览器原生支持 |
|
|
65
|
-
| 连接数限制 | 无 | 浏览器限制(6个/域名) |
|
|
66
|
-
| 防火墙穿透 | 可能被阻止 | 走 HTTP,穿透性好 |
|
|
67
|
-
| 实现复杂度 | 中等 | 简单 |
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## 一、WebSocket 开发指南
|
|
72
|
-
|
|
73
|
-
### 1.1 启用配置
|
|
33
|
+
### 1.1 配置
|
|
74
34
|
|
|
75
35
|
```yaml
|
|
76
|
-
# application.yml
|
|
77
36
|
websocket:
|
|
78
37
|
enabled: true
|
|
79
|
-
path: /resource/websocket
|
|
80
|
-
allowedOrigins: "*" #
|
|
38
|
+
path: /resource/websocket
|
|
39
|
+
allowedOrigins: "*" # 生产环境应限制
|
|
81
40
|
```
|
|
82
41
|
|
|
83
|
-
### 1.2
|
|
42
|
+
### 1.2 WebSocketUtils API
|
|
84
43
|
|
|
85
44
|
**位置**:`org.dromara.common.websocket.utils.WebSocketUtils`
|
|
86
45
|
|
|
@@ -88,19 +47,17 @@ websocket:
|
|
|
88
47
|
import org.dromara.common.websocket.utils.WebSocketUtils;
|
|
89
48
|
import org.dromara.common.websocket.dto.WebSocketMessageDto;
|
|
90
49
|
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
// 1. 向指定用户发送消息(当前服务实例)
|
|
94
|
-
WebSocketUtils.sendMessage(userId, "您有新的订单");
|
|
50
|
+
// 单实例:向指定用户发送
|
|
51
|
+
WebSocketUtils.sendMessage(userId, "消息内容");
|
|
95
52
|
|
|
96
|
-
//
|
|
53
|
+
// 多实例:通过 Redis Pub/Sub 广播(推荐)
|
|
97
54
|
WebSocketMessageDto dto = new WebSocketMessageDto();
|
|
98
|
-
dto.setSessionKeys(List.of(userId1, userId2));
|
|
99
|
-
dto.setMessage("
|
|
55
|
+
dto.setSessionKeys(List.of(userId1, userId2));
|
|
56
|
+
dto.setMessage("消息内容");
|
|
100
57
|
WebSocketUtils.publishMessage(dto);
|
|
101
58
|
|
|
102
|
-
//
|
|
103
|
-
WebSocketUtils.publishAll("
|
|
59
|
+
// 群发所有在线用户
|
|
60
|
+
WebSocketUtils.publishAll("系统广播消息");
|
|
104
61
|
```
|
|
105
62
|
|
|
106
63
|
### 1.3 WebSocketMessageDto
|
|
@@ -108,138 +65,46 @@ WebSocketUtils.publishAll("系统将于10分钟后维护");
|
|
|
108
65
|
```java
|
|
109
66
|
@Data
|
|
110
67
|
public class WebSocketMessageDto implements Serializable {
|
|
111
|
-
|
|
112
|
-
* 需要推送到的用户ID列表(为空则群发)
|
|
113
|
-
*/
|
|
114
|
-
private List<Long> sessionKeys;
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* 需要发送的消息内容
|
|
118
|
-
*/
|
|
68
|
+
private List<Long> sessionKeys; // 目标用户ID(空则群发)
|
|
119
69
|
private String message;
|
|
120
70
|
}
|
|
121
71
|
```
|
|
122
72
|
|
|
123
|
-
### 1.4
|
|
73
|
+
### 1.4 WebSocketSessionHolder(会话管理)
|
|
124
74
|
|
|
125
75
|
**位置**:`org.dromara.common.websocket.holder.WebSocketSessionHolder`
|
|
126
76
|
|
|
127
77
|
```java
|
|
128
|
-
import org.dromara.common.websocket.holder.WebSocketSessionHolder;
|
|
129
|
-
|
|
130
|
-
// 检查用户是否在线(当前实例)
|
|
131
78
|
boolean online = WebSocketSessionHolder.existSession(userId);
|
|
132
|
-
|
|
133
|
-
// 获取所有在线用户ID(当前实例)
|
|
134
79
|
Set<Long> onlineUsers = WebSocketSessionHolder.getSessionsAll();
|
|
135
|
-
|
|
136
|
-
// 获取用户的 WebSocket 会话
|
|
137
80
|
WebSocketSession session = WebSocketSessionHolder.getSessions(userId);
|
|
138
81
|
```
|
|
139
82
|
|
|
140
|
-
### 1.5
|
|
141
|
-
|
|
142
|
-
#### 示例1:订单状态变更通知
|
|
143
|
-
|
|
144
|
-
```java
|
|
145
|
-
@Service
|
|
146
|
-
@RequiredArgsConstructor
|
|
147
|
-
public class OrderServiceImpl implements IOrderService {
|
|
148
|
-
|
|
149
|
-
@Override
|
|
150
|
-
@Transactional
|
|
151
|
-
public void updateOrderStatus(Long orderId, String status) {
|
|
152
|
-
// 1. 更新订单状态
|
|
153
|
-
Order order = orderMapper.selectById(orderId);
|
|
154
|
-
order.setStatus(status);
|
|
155
|
-
orderMapper.updateById(order);
|
|
156
|
-
|
|
157
|
-
// 2. 推送消息给用户
|
|
158
|
-
WebSocketMessageDto dto = new WebSocketMessageDto();
|
|
159
|
-
dto.setSessionKeys(List.of(order.getUserId()));
|
|
160
|
-
dto.setMessage(JsonUtils.toJsonString(Map.of(
|
|
161
|
-
"type", "ORDER_STATUS",
|
|
162
|
-
"orderId", orderId,
|
|
163
|
-
"status", status,
|
|
164
|
-
"message", "您的订单状态已更新为:" + status
|
|
165
|
-
)));
|
|
166
|
-
WebSocketUtils.publishMessage(dto);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
#### 示例2:系统广播通知
|
|
172
|
-
|
|
173
|
-
```java
|
|
174
|
-
@Service
|
|
175
|
-
public class NoticeServiceImpl implements INoticeService {
|
|
176
|
-
|
|
177
|
-
@Override
|
|
178
|
-
public void broadcastNotice(String title, String content) {
|
|
179
|
-
// 群发给所有在线用户
|
|
180
|
-
String message = JsonUtils.toJsonString(Map.of(
|
|
181
|
-
"type", "SYSTEM_NOTICE",
|
|
182
|
-
"title", title,
|
|
183
|
-
"content", content,
|
|
184
|
-
"time", DateUtils.getTime()
|
|
185
|
-
));
|
|
186
|
-
WebSocketUtils.publishAll(message);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### 1.6 前端连接示例
|
|
83
|
+
### 1.5 前端连接
|
|
192
84
|
|
|
193
85
|
```javascript
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const ws = new WebSocket(`ws://localhost:8080/resource/websocket?clientid=${clientId}&Authorization=${token}`);
|
|
199
|
-
|
|
200
|
-
ws.onopen = () => {
|
|
201
|
-
console.log('WebSocket 连接成功');
|
|
202
|
-
};
|
|
203
|
-
|
|
86
|
+
const ws = new WebSocket(
|
|
87
|
+
`ws://localhost:8080/resource/websocket?clientid=${clientId}&Authorization=${token}`
|
|
88
|
+
);
|
|
204
89
|
ws.onmessage = (event) => {
|
|
205
90
|
const data = JSON.parse(event.data);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// 根据消息类型处理
|
|
209
|
-
switch (data.type) {
|
|
210
|
-
case 'ORDER_STATUS':
|
|
211
|
-
handleOrderStatus(data);
|
|
212
|
-
break;
|
|
213
|
-
case 'SYSTEM_NOTICE':
|
|
214
|
-
showNotification(data.title, data.content);
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
ws.onclose = () => {
|
|
220
|
-
console.log('WebSocket 连接关闭');
|
|
221
|
-
// 可实现自动重连
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
ws.onerror = (error) => {
|
|
225
|
-
console.error('WebSocket 错误:', error);
|
|
91
|
+
// 根据 data.type 路由处理
|
|
226
92
|
};
|
|
227
93
|
```
|
|
228
94
|
|
|
229
95
|
---
|
|
230
96
|
|
|
231
|
-
## 二、SSE
|
|
97
|
+
## 二、SSE
|
|
232
98
|
|
|
233
|
-
### 2.1
|
|
99
|
+
### 2.1 配置
|
|
234
100
|
|
|
235
101
|
```yaml
|
|
236
|
-
# application.yml
|
|
237
102
|
sse:
|
|
238
103
|
enabled: true
|
|
239
|
-
path: /resource/sse
|
|
104
|
+
path: /resource/sse
|
|
240
105
|
```
|
|
241
106
|
|
|
242
|
-
### 2.2
|
|
107
|
+
### 2.2 SseMessageUtils API
|
|
243
108
|
|
|
244
109
|
**位置**:`org.dromara.common.sse.utils.SseMessageUtils`
|
|
245
110
|
|
|
@@ -247,27 +112,21 @@ sse:
|
|
|
247
112
|
import org.dromara.common.sse.utils.SseMessageUtils;
|
|
248
113
|
import org.dromara.common.sse.dto.SseMessageDto;
|
|
249
114
|
|
|
250
|
-
//
|
|
115
|
+
// 单实例:向指定用户 / 所有用户
|
|
116
|
+
SseMessageUtils.sendMessage(userId, "消息");
|
|
117
|
+
SseMessageUtils.sendMessage("广播消息");
|
|
251
118
|
|
|
252
|
-
//
|
|
253
|
-
SseMessageUtils.sendMessage(userId, "您有新的消息");
|
|
254
|
-
|
|
255
|
-
// 2. 向当前实例所有用户发送消息
|
|
256
|
-
SseMessageUtils.sendMessage("系统通知内容");
|
|
257
|
-
|
|
258
|
-
// 3. 向指定用户发送消息(支持多实例,通过 Redis 发布订阅)
|
|
119
|
+
// 多实例:通过 Redis Pub/Sub
|
|
259
120
|
SseMessageDto dto = new SseMessageDto();
|
|
260
|
-
dto.setUserIds(List.of(userId1, userId2));
|
|
261
|
-
dto.setMessage("
|
|
121
|
+
dto.setUserIds(List.of(userId1, userId2));
|
|
122
|
+
dto.setMessage("消息内容");
|
|
262
123
|
SseMessageUtils.publishMessage(dto);
|
|
263
124
|
|
|
264
|
-
//
|
|
265
|
-
SseMessageUtils.publishAll("
|
|
125
|
+
// 群发(多实例)
|
|
126
|
+
SseMessageUtils.publishAll("系统通知");
|
|
266
127
|
|
|
267
|
-
//
|
|
268
|
-
if (SseMessageUtils.isEnable()) {
|
|
269
|
-
// SSE 已启用
|
|
270
|
-
}
|
|
128
|
+
// 检查是否启用
|
|
129
|
+
if (SseMessageUtils.isEnable()) { ... }
|
|
271
130
|
```
|
|
272
131
|
|
|
273
132
|
### 2.3 SseMessageDto
|
|
@@ -275,300 +134,62 @@ if (SseMessageUtils.isEnable()) {
|
|
|
275
134
|
```java
|
|
276
135
|
@Data
|
|
277
136
|
public class SseMessageDto implements Serializable {
|
|
278
|
-
|
|
279
|
-
* 需要推送到的用户ID列表(为空则群发)
|
|
280
|
-
*/
|
|
281
|
-
private List<Long> userIds;
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* 需要发送的消息内容
|
|
285
|
-
*/
|
|
137
|
+
private List<Long> userIds; // 目标用户ID(空则群发)
|
|
286
138
|
private String message;
|
|
287
139
|
}
|
|
288
140
|
```
|
|
289
141
|
|
|
290
|
-
### 2.4
|
|
291
|
-
|
|
292
|
-
**位置**:`org.dromara.common.sse.core.SseEmitterManager`
|
|
293
|
-
|
|
294
|
-
```java
|
|
295
|
-
import org.dromara.common.sse.core.SseEmitterManager;
|
|
296
|
-
|
|
297
|
-
@Service
|
|
298
|
-
@RequiredArgsConstructor
|
|
299
|
-
public class CustomSseService {
|
|
300
|
-
|
|
301
|
-
private final SseEmitterManager sseEmitterManager;
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* 建立 SSE 连接(通常由 SseController 处理,业务代码无需调用)
|
|
305
|
-
*/
|
|
306
|
-
public SseEmitter connect(Long userId, String token) {
|
|
307
|
-
return sseEmitterManager.connect(userId, token);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* 断开 SSE 连接
|
|
312
|
-
*/
|
|
313
|
-
public void disconnect(Long userId, String token) {
|
|
314
|
-
sseEmitterManager.disconnect(userId, token);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### 2.5 业务集成示例
|
|
320
|
-
|
|
321
|
-
#### 示例1:审批流程通知
|
|
322
|
-
|
|
323
|
-
```java
|
|
324
|
-
@Service
|
|
325
|
-
@RequiredArgsConstructor
|
|
326
|
-
public class ApprovalServiceImpl implements IApprovalService {
|
|
327
|
-
|
|
328
|
-
@Override
|
|
329
|
-
@Transactional
|
|
330
|
-
public void approve(Long taskId, Boolean approved, String comment) {
|
|
331
|
-
// 1. 处理审批逻辑
|
|
332
|
-
ApprovalTask task = taskMapper.selectById(taskId);
|
|
333
|
-
task.setStatus(approved ? "APPROVED" : "REJECTED");
|
|
334
|
-
task.setComment(comment);
|
|
335
|
-
taskMapper.updateById(task);
|
|
336
|
-
|
|
337
|
-
// 2. 通知申请人
|
|
338
|
-
SseMessageDto dto = new SseMessageDto();
|
|
339
|
-
dto.setUserIds(List.of(task.getApplicantId()));
|
|
340
|
-
dto.setMessage(JsonUtils.toJsonString(Map.of(
|
|
341
|
-
"type", "APPROVAL_RESULT",
|
|
342
|
-
"taskId", taskId,
|
|
343
|
-
"approved", approved,
|
|
344
|
-
"comment", comment,
|
|
345
|
-
"time", DateUtils.getTime()
|
|
346
|
-
)));
|
|
347
|
-
SseMessageUtils.publishMessage(dto);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
#### 示例2:数据变更实时推送
|
|
353
|
-
|
|
354
|
-
```java
|
|
355
|
-
@Service
|
|
356
|
-
public class DashboardServiceImpl implements IDashboardService {
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* 推送仪表盘数据更新
|
|
360
|
-
*/
|
|
361
|
-
public void pushDashboardUpdate(Long userId, DashboardData data) {
|
|
362
|
-
String message = JsonUtils.toJsonString(Map.of(
|
|
363
|
-
"type", "DASHBOARD_UPDATE",
|
|
364
|
-
"data", data,
|
|
365
|
-
"updateTime", DateUtils.getTime()
|
|
366
|
-
));
|
|
367
|
-
SseMessageUtils.sendMessage(userId, message);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* 广播给所有管理员
|
|
372
|
-
*/
|
|
373
|
-
public void broadcastToAdmins(List<Long> adminIds, String content) {
|
|
374
|
-
SseMessageDto dto = new SseMessageDto();
|
|
375
|
-
dto.setUserIds(adminIds);
|
|
376
|
-
dto.setMessage(content);
|
|
377
|
-
SseMessageUtils.publishMessage(dto);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
### 2.6 前端连接示例
|
|
142
|
+
### 2.4 前端连接
|
|
383
143
|
|
|
384
144
|
```javascript
|
|
385
|
-
// 建立 SSE 连接
|
|
386
|
-
const token = getToken();
|
|
387
145
|
const eventSource = new EventSource(`/resource/sse?Authorization=${token}`);
|
|
388
|
-
|
|
389
|
-
eventSource.onopen = () => {
|
|
390
|
-
console.log('SSE 连接成功');
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
// 监听 message 事件
|
|
394
146
|
eventSource.addEventListener('message', (event) => {
|
|
395
147
|
const data = JSON.parse(event.data);
|
|
396
|
-
console.log('收到消息:', data);
|
|
397
|
-
|
|
398
|
-
switch (data.type) {
|
|
399
|
-
case 'APPROVAL_RESULT':
|
|
400
|
-
showApprovalResult(data);
|
|
401
|
-
break;
|
|
402
|
-
case 'DASHBOARD_UPDATE':
|
|
403
|
-
updateDashboard(data.data);
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
148
|
});
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
console.error('SSE 错误:', error);
|
|
410
|
-
// 浏览器会自动尝试重连
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
// 主动关闭连接
|
|
414
|
-
function closeConnection() {
|
|
149
|
+
// 页面关闭时主动关闭
|
|
150
|
+
window.onbeforeunload = () => {
|
|
415
151
|
eventSource.close();
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
console.log('SSE 连接已关闭');
|
|
419
|
-
});
|
|
420
|
-
}
|
|
152
|
+
navigator.sendBeacon('/resource/sse/close');
|
|
153
|
+
};
|
|
421
154
|
```
|
|
422
155
|
|
|
423
156
|
---
|
|
424
157
|
|
|
425
|
-
##
|
|
426
|
-
|
|
427
|
-
### 3.1 架构原理
|
|
428
|
-
|
|
429
|
-
```
|
|
430
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
431
|
-
│ Redis Pub/Sub │
|
|
432
|
-
│ │
|
|
433
|
-
│ Topic: global:websocket Topic: global:sse │
|
|
434
|
-
└─────────────────────────────────────────────────────────────┘
|
|
435
|
-
▲ │ ▲ │
|
|
436
|
-
│ ▼ │ ▼
|
|
437
|
-
┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐
|
|
438
|
-
│ 实例 1 │ │ 实例 2 │ │ 实例 1 │ │ 实例 2 │
|
|
439
|
-
│ WS连接 │ │ WS连接 │ │ SSE连接 │ │ SSE连接 │
|
|
440
|
-
└─────────┘ └─────────┘ └─────────┘ └─────────┘
|
|
441
|
-
│ │ │ │
|
|
442
|
-
┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐
|
|
443
|
-
│ 用户 A │ │ 用户 B │ │ 用户 C │ │ 用户 D │
|
|
444
|
-
└─────────┘ └─────────┘ └─────────┘ └─────────┘
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
### 3.2 消息同步机制
|
|
158
|
+
## 三、多实例消息同步
|
|
448
159
|
|
|
449
|
-
|
|
450
|
-
1. 调用 `WebSocketUtils.publishMessage(dto)`
|
|
451
|
-
2. 先检查目标用户是否在当前实例,在则直接发送
|
|
452
|
-
3. 不在当前实例的用户,通过 Redis 发布到 `global:websocket` 主题
|
|
453
|
-
4. 其他实例的 `WebSocketTopicListener` 接收并转发给本地用户
|
|
160
|
+
通过 Redis Pub/Sub 实现跨实例消息投递:
|
|
454
161
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
4. 匹配到的用户通过 `SseEmitter` 推送消息
|
|
162
|
+
| 方案 | Redis Topic | 方法 |
|
|
163
|
+
|------|------------|------|
|
|
164
|
+
| WebSocket | `global:websocket` | `publishMessage()` / `publishAll()` |
|
|
165
|
+
| SSE | `global:sse` | `publishMessage()` / `publishAll()` |
|
|
460
166
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
```java
|
|
464
|
-
// WebSocket 主题
|
|
465
|
-
public static final String WEB_SOCKET_TOPIC = "global:websocket";
|
|
466
|
-
|
|
467
|
-
// SSE 主题
|
|
468
|
-
private final static String SSE_TOPIC = "global:sse";
|
|
469
|
-
```
|
|
167
|
+
**流程**:调用 publish 方法 -> 发布到 Redis Topic -> 所有实例的 Listener 接收 -> 匹配本地用户并投递。
|
|
470
168
|
|
|
471
169
|
---
|
|
472
170
|
|
|
473
|
-
##
|
|
474
|
-
|
|
475
|
-
### 4.1 场景:系统通知推送
|
|
171
|
+
## 四、业务集成示例
|
|
476
172
|
|
|
477
173
|
```java
|
|
478
174
|
@Service
|
|
479
175
|
@RequiredArgsConstructor
|
|
480
|
-
public class
|
|
176
|
+
public class OrderNotifyService {
|
|
481
177
|
|
|
482
|
-
|
|
483
|
-
* 发送系统通知(SSE 方式)
|
|
484
|
-
*/
|
|
485
|
-
public void sendNotice(Long userId, String title, String content) {
|
|
178
|
+
public void notifyOrderStatusChange(Order order) {
|
|
486
179
|
String message = JsonUtils.toJsonString(Map.of(
|
|
487
|
-
"type", "
|
|
488
|
-
"
|
|
489
|
-
"
|
|
490
|
-
"
|
|
180
|
+
"type", "ORDER_STATUS_CHANGE",
|
|
181
|
+
"orderId", order.getId(),
|
|
182
|
+
"status", order.getStatus(),
|
|
183
|
+
"updateTime", DateUtils.getTime()
|
|
491
184
|
));
|
|
492
185
|
|
|
493
|
-
|
|
494
|
-
dto.setUserIds(List.of(userId));
|
|
495
|
-
dto.setMessage(message);
|
|
496
|
-
SseMessageUtils.publishMessage(dto);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* 广播系统公告
|
|
501
|
-
*/
|
|
502
|
-
public void broadcastAnnouncement(String title, String content) {
|
|
503
|
-
String message = JsonUtils.toJsonString(Map.of(
|
|
504
|
-
"type", "ANNOUNCEMENT",
|
|
505
|
-
"title", title,
|
|
506
|
-
"content", content,
|
|
507
|
-
"time", DateUtils.getTime()
|
|
508
|
-
));
|
|
509
|
-
SseMessageUtils.publishAll(message);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
### 4.2 场景:在线用户统计
|
|
515
|
-
|
|
516
|
-
```java
|
|
517
|
-
@Service
|
|
518
|
-
public class OnlineUserService {
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* 获取当前实例在线用户数(WebSocket)
|
|
522
|
-
*/
|
|
523
|
-
public int getOnlineCount() {
|
|
524
|
-
return WebSocketSessionHolder.getSessionsAll().size();
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* 检查用户是否在线(当前实例)
|
|
529
|
-
*/
|
|
530
|
-
public boolean isOnline(Long userId) {
|
|
531
|
-
return WebSocketSessionHolder.existSession(userId);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* 获取在线用户列表(当前实例)
|
|
536
|
-
*/
|
|
537
|
-
public Set<Long> getOnlineUsers() {
|
|
538
|
-
return WebSocketSessionHolder.getSessionsAll();
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
### 4.3 场景:订单状态实时更新
|
|
544
|
-
|
|
545
|
-
```java
|
|
546
|
-
@Service
|
|
547
|
-
@RequiredArgsConstructor
|
|
548
|
-
public class OrderNotifyService {
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* 订单状态变更通知
|
|
552
|
-
*/
|
|
553
|
-
public void notifyOrderStatusChange(Order order) {
|
|
554
|
-
// 1. 构建消息
|
|
555
|
-
Map<String, Object> data = new HashMap<>();
|
|
556
|
-
data.put("type", "ORDER_STATUS_CHANGE");
|
|
557
|
-
data.put("orderId", order.getId());
|
|
558
|
-
data.put("orderNo", order.getOrderNo());
|
|
559
|
-
data.put("oldStatus", order.getOldStatus());
|
|
560
|
-
data.put("newStatus", order.getStatus());
|
|
561
|
-
data.put("updateTime", DateUtils.getTime());
|
|
562
|
-
|
|
563
|
-
String message = JsonUtils.toJsonString(data);
|
|
564
|
-
|
|
565
|
-
// 2. 通知买家(SSE)
|
|
186
|
+
// SSE 通知买家
|
|
566
187
|
SseMessageDto buyerDto = new SseMessageDto();
|
|
567
188
|
buyerDto.setUserIds(List.of(order.getBuyerId()));
|
|
568
189
|
buyerDto.setMessage(message);
|
|
569
190
|
SseMessageUtils.publishMessage(buyerDto);
|
|
570
191
|
|
|
571
|
-
//
|
|
192
|
+
// WebSocket 通知卖家(需要双向通信时)
|
|
572
193
|
WebSocketMessageDto sellerDto = new WebSocketMessageDto();
|
|
573
194
|
sellerDto.setSessionKeys(List.of(order.getSellerId()));
|
|
574
195
|
sellerDto.setMessage(message);
|
|
@@ -579,138 +200,72 @@ public class OrderNotifyService {
|
|
|
579
200
|
|
|
580
201
|
---
|
|
581
202
|
|
|
582
|
-
##
|
|
583
|
-
|
|
584
|
-
### ❌ 错误1:未启用模块就使用
|
|
203
|
+
## 五、常见错误
|
|
585
204
|
|
|
586
205
|
```java
|
|
587
|
-
// ❌
|
|
588
|
-
WebSocketUtils.sendMessage(userId, "消息");
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
```yaml
|
|
592
|
-
# ✅ 正确:先启用
|
|
593
|
-
websocket:
|
|
594
|
-
enabled: true
|
|
595
|
-
```
|
|
206
|
+
// ❌ 多实例环境使用 sendMessage(只能发给当前实例用户)
|
|
207
|
+
WebSocketUtils.sendMessage(userId, "消息");
|
|
596
208
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
```java
|
|
600
|
-
// ❌ 错误:使用 sendMessage 在多实例环境
|
|
601
|
-
WebSocketUtils.sendMessage(userId, "消息"); // 只能发给当前实例的用户
|
|
602
|
-
|
|
603
|
-
// ✅ 正确:使用 publishMessage 支持多实例
|
|
209
|
+
// ✅ 使用 publishMessage 支持多实例
|
|
604
210
|
WebSocketMessageDto dto = new WebSocketMessageDto();
|
|
605
211
|
dto.setSessionKeys(List.of(userId));
|
|
606
212
|
dto.setMessage("消息");
|
|
607
|
-
WebSocketUtils.publishMessage(dto);
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
### ❌ 错误3:SSE 连接未关闭导致资源泄漏
|
|
611
|
-
|
|
612
|
-
```javascript
|
|
613
|
-
// ❌ 错误:页面关闭时未主动关闭 SSE
|
|
614
|
-
window.onbeforeunload = () => {
|
|
615
|
-
// 没有关闭 eventSource
|
|
616
|
-
};
|
|
213
|
+
WebSocketUtils.publishMessage(dto);
|
|
617
214
|
|
|
618
|
-
//
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
```
|
|
215
|
+
// ❌ 循环逐个发送
|
|
216
|
+
for (Long uid : userIds) {
|
|
217
|
+
dto.setSessionKeys(List.of(uid));
|
|
218
|
+
WebSocketUtils.publishMessage(dto);
|
|
219
|
+
}
|
|
624
220
|
|
|
625
|
-
|
|
221
|
+
// ✅ 批量发送
|
|
222
|
+
dto.setSessionKeys(userIds);
|
|
223
|
+
WebSocketUtils.publishMessage(dto);
|
|
626
224
|
|
|
627
|
-
|
|
628
|
-
// ❌ 错误:直接发送字符串,前端难以解析
|
|
225
|
+
// ❌ 发送纯字符串(前端难解析)
|
|
629
226
|
WebSocketUtils.publishAll("订单已更新");
|
|
630
227
|
|
|
631
|
-
// ✅
|
|
632
|
-
|
|
633
|
-
"type", "ORDER_UPDATE",
|
|
634
|
-
|
|
635
|
-
"time", DateUtils.getTime()
|
|
636
|
-
));
|
|
637
|
-
WebSocketUtils.publishAll(message);
|
|
228
|
+
// ✅ JSON 格式 + type 字段
|
|
229
|
+
WebSocketUtils.publishAll(JsonUtils.toJsonString(Map.of(
|
|
230
|
+
"type", "ORDER_UPDATE", "data", orderData
|
|
231
|
+
)));
|
|
638
232
|
```
|
|
639
233
|
|
|
640
|
-
### ❌ 错误5:在循环中逐个发送消息
|
|
641
|
-
|
|
642
|
-
```java
|
|
643
|
-
// ❌ 错误:效率低
|
|
644
|
-
for (Long userId : userIds) {
|
|
645
|
-
WebSocketMessageDto dto = new WebSocketMessageDto();
|
|
646
|
-
dto.setSessionKeys(List.of(userId));
|
|
647
|
-
dto.setMessage(message);
|
|
648
|
-
WebSocketUtils.publishMessage(dto); // 每次都发布到 Redis
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// ✅ 正确:批量发送
|
|
652
|
-
WebSocketMessageDto dto = new WebSocketMessageDto();
|
|
653
|
-
dto.setSessionKeys(userIds); // 一次性设置所有目标用户
|
|
654
|
-
dto.setMessage(message);
|
|
655
|
-
WebSocketUtils.publishMessage(dto); // 只发布一次
|
|
656
|
-
```
|
|
657
|
-
|
|
658
|
-
---
|
|
659
|
-
|
|
660
|
-
## 六、API 速查表
|
|
661
|
-
|
|
662
|
-
### WebSocket API
|
|
663
|
-
|
|
664
|
-
| 方法 | 说明 | 适用场景 |
|
|
665
|
-
|------|------|---------|
|
|
666
|
-
| `WebSocketUtils.sendMessage(userId, message)` | 发送给指定用户(当前实例) | 单实例部署 |
|
|
667
|
-
| `WebSocketUtils.publishMessage(dto)` | 发送给指定用户(多实例) | 多实例部署 |
|
|
668
|
-
| `WebSocketUtils.publishAll(message)` | 群发所有用户 | 系统广播 |
|
|
669
|
-
| `WebSocketSessionHolder.existSession(userId)` | 检查用户是否在线 | 在线状态 |
|
|
670
|
-
| `WebSocketSessionHolder.getSessionsAll()` | 获取所有在线用户 | 统计 |
|
|
671
|
-
|
|
672
|
-
### SSE API
|
|
673
|
-
|
|
674
|
-
| 方法 | 说明 | 适用场景 |
|
|
675
|
-
|------|------|---------|
|
|
676
|
-
| `SseMessageUtils.sendMessage(userId, message)` | 发送给指定用户(当前实例) | 单实例部署 |
|
|
677
|
-
| `SseMessageUtils.sendMessage(message)` | 发送给所有用户(当前实例) | 单实例广播 |
|
|
678
|
-
| `SseMessageUtils.publishMessage(dto)` | 发送给指定用户(多实例) | 多实例部署 |
|
|
679
|
-
| `SseMessageUtils.publishAll(message)` | 群发所有用户(多实例) | 系统广播 |
|
|
680
|
-
| `SseMessageUtils.isEnable()` | 检查 SSE 是否启用 | 条件判断 |
|
|
681
|
-
|
|
682
234
|
---
|
|
683
235
|
|
|
684
|
-
##
|
|
236
|
+
## 六、API 速查
|
|
685
237
|
|
|
686
|
-
### WebSocket
|
|
238
|
+
### WebSocket
|
|
687
239
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
240
|
+
| 方法 | 说明 |
|
|
241
|
+
|------|------|
|
|
242
|
+
| `WebSocketUtils.sendMessage(userId, msg)` | 发给指定用户(当前实例) |
|
|
243
|
+
| `WebSocketUtils.publishMessage(dto)` | 发给指定用户(多实例) |
|
|
244
|
+
| `WebSocketUtils.publishAll(msg)` | 群发(多实例) |
|
|
245
|
+
| `WebSocketSessionHolder.existSession(userId)` | 检查在线 |
|
|
246
|
+
| `WebSocketSessionHolder.getSessionsAll()` | 所有在线用户 |
|
|
694
247
|
|
|
695
|
-
### SSE
|
|
248
|
+
### SSE
|
|
696
249
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
250
|
+
| 方法 | 说明 |
|
|
251
|
+
|------|------|
|
|
252
|
+
| `SseMessageUtils.sendMessage(userId, msg)` | 发给指定用户(当前实例) |
|
|
253
|
+
| `SseMessageUtils.sendMessage(msg)` | 发给所有用户(当前实例) |
|
|
254
|
+
| `SseMessageUtils.publishMessage(dto)` | 发给指定用户(多实例) |
|
|
255
|
+
| `SseMessageUtils.publishAll(msg)` | 群发(多实例) |
|
|
256
|
+
| `SseMessageUtils.isEnable()` | 检查是否启用 |
|
|
702
257
|
|
|
703
258
|
---
|
|
704
259
|
|
|
705
|
-
##
|
|
260
|
+
## 七、参考代码位置
|
|
706
261
|
|
|
707
262
|
| 类型 | 位置 |
|
|
708
263
|
|------|------|
|
|
709
|
-
| WebSocket 工具类 | `ruoyi-common/ruoyi-common-websocket
|
|
710
|
-
| WebSocket 会话管理 | `ruoyi-common/ruoyi-common-websocket
|
|
711
|
-
| WebSocket 消息DTO | `ruoyi-common/ruoyi-common-websocket
|
|
712
|
-
| WebSocket 配置 | `ruoyi-common/ruoyi-common-websocket
|
|
713
|
-
| SSE 工具类 | `ruoyi-common/ruoyi-common-sse
|
|
714
|
-
| SSE 连接管理 | `ruoyi-common/ruoyi-common-sse
|
|
715
|
-
| SSE 控制器 | `ruoyi-common/ruoyi-common-sse
|
|
716
|
-
| SSE 消息DTO | `ruoyi-common/ruoyi-common-sse
|
|
264
|
+
| WebSocket 工具类 | `ruoyi-common/ruoyi-common-websocket/.../utils/WebSocketUtils.java` |
|
|
265
|
+
| WebSocket 会话管理 | `ruoyi-common/ruoyi-common-websocket/.../holder/WebSocketSessionHolder.java` |
|
|
266
|
+
| WebSocket 消息DTO | `ruoyi-common/ruoyi-common-websocket/.../dto/WebSocketMessageDto.java` |
|
|
267
|
+
| WebSocket 配置 | `ruoyi-common/ruoyi-common-websocket/.../config/WebSocketConfig.java` |
|
|
268
|
+
| SSE 工具类 | `ruoyi-common/ruoyi-common-sse/.../utils/SseMessageUtils.java` |
|
|
269
|
+
| SSE 连接管理 | `ruoyi-common/ruoyi-common-sse/.../core/SseEmitterManager.java` |
|
|
270
|
+
| SSE 控制器 | `ruoyi-common/ruoyi-common-sse/.../controller/SseController.java` |
|
|
271
|
+
| SSE 消息DTO | `ruoyi-common/ruoyi-common-sse/.../dto/SseMessageDto.java` |
|