WebSocket
系统内置 WebSocket 实时通信能力,用于服务端向客户端推送消息(通知公告、权限变更红点等场景均经此通道)。
3.0 微服务版下,WebSocket 连接层独立为第六服务 lihua-websocket(默认端口 8086,无库不落表,可多实例水平扩容)。业务服务不依赖连接层,仅依赖 lihua-base-ws 消息边界模块,经 Redis pub/sub 投递推送:
业务服务(system 等) lihua-websocket 服务(可多实例)
┌──────────────────────┐ Redis pub/sub ┌──────────────────────┐
│ WebSocketPushUtils │ ──── ws_push ───→ │ WsPushSubscriber │
│ (lihua-base-ws) │ │ WebSocketManager │──→ 客户端
└──────────────────────┘ │ (持有 /ws-connect) │
└──────────────────────┘客户端连接仍统一走网关:/ws-connect/** 路由 lb://lihua-websocket(Nacos 服务名负载均衡),多实例部署时由负载均衡分摊连接。
消息类型
消息类型由 WebSocketMsgTypeEnum 枚举维护(位于 lihua-base-common):
WS_NOTICE:通知消息(公告发布推送,消息体为公告内容)WS_HEARTBEAT:心跳,客户端每 30s 发送(data="ping"),服务端经内置处理器回pongWS_REFRESH_PERMISSION:权限数据更新提示,角色/菜单变更后定向推送给受影响在线用户,前端置「数据更新」红点(见 安全模块)
下行帧结构为 {type, data, timestamp},timestamp 由连接层发送时自动填充。
发送消息
业务侧统一使用 WebSocketPushUtils(lihua-base-ws 提供)静态方法投递推送,不感知连接层:
// 注入消息类型枚举与工具类
import com.lihua.common.enums.WebSocketMsgTypeEnum;
import com.lihua.ws.push.WebSocketPushUtils;
// 向全部在线用户广播
WebSocketPushUtils.pushAll(WebSocketMsgTypeEnum.WS_NOTICE, data);
// 向指定用户投递
List<String> userIdList;
WebSocketPushUtils.push(userIdList, WebSocketMsgTypeEnum.WS_NOTICE, data);铁纪律
禁止业务服务依赖 lihua-websocket、直调 WebSocketManager 或使用进程内事件触达推送——那是单实例语义,多实例部署时其他实例上的连接将收不到推送。
投递语义
push的userIdList为null时等价全员广播(一般直接用pushAll)- 推送为 fire-and-forget:无送达承诺与重试,可靠性靠持久层 + 客户端拉取兜底,消息体保持最小化
- 会话表中无此用户的实例静默跳过,属扇出常态
内部实现:投递即向 Redis topic ws_push 发布一条 WsPushMessage(userIdList + type + data 的 JSON),所有订阅该 topic 的 WS 实例各收一次、各自推送本地持有的连接——多实例天然扇出。
事务提交后再推送
「写库 + 推送」组合一律使用 TransactionSendUtils.runAfterCommit 包裹(在发布点包裹,工具内部不包):存在活动事务则挂 afterCommit 执行,否则立即执行。避免事务未提交消息先达,客户端回拉读不到数据
真实示例(公告发布,SysNoticeServiceImpl):
// 全员公告:事务提交后向全部用户投递
TransactionSendUtils.runAfterCommit(() ->
WebSocketPushUtils.pushAll(WebSocketMsgTypeEnum.WS_NOTICE, sysNotice));
// 指定范围公告:事务提交后向指定用户投递
List<String> userIds = sysUserNoticeService.queryUserIds(id);
TransactionSendUtils.runAfterCommit(() ->
WebSocketPushUtils.push(userIds, WebSocketMsgTypeEnum.WS_NOTICE, sysNotice));权限变更红点同理:PermissionUpdateUtils.markChanged 内部先写 Redis 红点标记,再经 runAfterCommit → WebSocketPushUtils.push 投递 WS_REFRESH_PERMISSION。
接收消息(上行处理)
客户端上行帧(结构与下行对称的 {type, data, timestamp})由 lihua-websocket 服务解析后,按 type 分发给 WsMessageReceiver 处理器。二开新上行消息三步:
- 确定帧
type(内置枚举值或自定义字符串) - 实现
WsMessageReceiver接口并标注@Component(自动被宿主扫描注册) - 在
receive中消费data,需要应答时经reply回写
public class CustomWsMessageReceiver implements WsMessageReceiver {
@Override
public String type() {
// 内置值取 WebSocketMsgTypeEnum.name(),二开可自定义新值
return "CUSTOM_TYPE";
}
@Override
public void receive(String userId, Object data, WsReply reply) {
// userId:握手鉴权通过的用户 id
// data:上行载荷
// reply.send(...):向当前连接回写同 type 帧(可选)
}
}生效范围
处理器运行在连接所在进程——cloud 下即 lihua-websocket 服务进程。在业务服务(如 lihua-system)进程内注册的处理器收不到调用;跨服务的上行业务处理须经 WS 上行 Redis topic 桥接(com.lihua.ws.receive 包预留方向),勿用进程内事件(event 不跨进程)。
内置参考实现 HeartbeatWsMessageReceiver:处理 WS_HEARTBEAT 帧并回 pong,确认链路双向可用。该帧为连接层内置语义,在连接进程内闭环、不经 Redis 扇出。服务端不基于心跳做超时踢线,断连由容器连接回调驱动客户端自动重连(最多 3 次)。
连接管理(lihua-websocket 服务内部)
以下机制由 lihua-websocket 服务实现,业务侧无需关心,仅供了解:
- 握手鉴权:客户端连接前请求一次性令牌(
/system/auth/onceToken,Redis 存储有效期 1 分钟),握手时携带token + clientId + clientType参数(/ws-connect?token=xx&clientId=xx&clientType=xx),鉴权通过即删除令牌。服务进程内/ws-connect/**在 SecurityConfig 白名单放行,鉴权完全由握手拦截器承担 - 会话表:两级结构
userId → (userId_clientId_clientType → session);同一用户同一端重复建连时新连接建立后旧连接自动关闭 - 慢消费者保护:每个连接经
ConcurrentWebSocketSessionDecorator装饰——单条消息发送超 5 秒或发送缓冲超 512KB 的连接自动断开,避免阻塞推送线程 - 上行分发:按帧 type 查处理器表分发,type 重复保留先注册者并告警;解析失败或无处理方仅记 debug 日志,不影响连接存活;处理内异常由连接层兜底捕获,回写经装饰会话与下行共享发送排队锁

