质控
This commit is contained in:
parent
bd9d44c5fb
commit
9cbe312933
@ -0,0 +1,50 @@
|
||||
package com.czlis.interfaceCommon.config;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
|
||||
/**
|
||||
* 独立的WebSocket Spring上下文配置器
|
||||
* 作用:解决WebSocket端点中获取Spring Bean的问题,替代过时的SpringConfigurator
|
||||
* 适配:SpringBoot 2.x + 若依框架 + Tomcat 8/9/10
|
||||
*/
|
||||
@Component // 让Spring扫描并初始化该类,获取上下文
|
||||
public class SpringContextWebSocketConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {
|
||||
|
||||
// 静态持有Spring上下文(全局唯一)
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* 实现ApplicationContextAware接口,Spring启动时自动注入上下文
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
// 仅初始化一次,避免多次赋值
|
||||
if (applicationContext == null) {
|
||||
applicationContext = context;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写创建端点实例的核心方法
|
||||
* 从Spring容器中获取WebSocket端点实例,保证Bean注入生效
|
||||
*/
|
||||
@Override
|
||||
public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
|
||||
// 双重校验:上下文存在 + 容器中有该Bean
|
||||
if (applicationContext != null) {
|
||||
try {
|
||||
return applicationContext.getBean(clazz);
|
||||
} catch (BeansException e) {
|
||||
// 容器中无该Bean时,降级使用默认实例化逻辑(兼容测试场景)
|
||||
return super.getEndpointInstance(clazz);
|
||||
}
|
||||
}
|
||||
// 上下文未初始化时,走父类默认逻辑
|
||||
return super.getEndpointInstance(clazz);
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,9 @@ import com.alibaba.fastjson2.JSON;
|
||||
import com.czlis.common.core.domain.model.LoginUser;
|
||||
import com.czlis.common.utils.SecurityUtils;
|
||||
import com.czlis.common.utils.spring.SpringUtils;
|
||||
import com.czlis.interfaceCommon.config.SpringContextWebSocketConfigurator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.server.standard.SpringConfigurator;
|
||||
|
||||
import javax.websocket.*;
|
||||
@ -24,7 +26,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* 工具方法暴露:在 WebSocket 端点类中提供静态工具方法(如 sendToUser、broadcast),其他业务直接调用该方法,无需关心实例创建;
|
||||
* 避免重复连接:连接的创建 / 销毁由 WebSocket 协议自动管理(@OnOpen 新增、@OnClose 移除),业务层不干预实例创建。
|
||||
*/
|
||||
@ServerEndpoint(value = "/ws/msgserver/{userId}", configurator = SpringConfigurator.class)
|
||||
@Component
|
||||
@ServerEndpoint(value = "/ws/msgserver/{userId}", configurator = SpringContextWebSocketConfigurator.class)
|
||||
public class ChatWebSocketServer {
|
||||
// 🌟 核心固定参数白名单:这些参数不存入动态属性(单独解析为固定身份属性)
|
||||
private static final Set<String> CORE_PARAMS = new HashSet<>(Arrays.asList("token", "username", "role"));
|
||||
@ -94,22 +97,47 @@ public class ChatWebSocketServer {
|
||||
try {
|
||||
// 1. 基础校验:消息为空/连接关闭 → 直接返回
|
||||
if (message == null || message.trim().isEmpty() || !session.isOpen()) {
|
||||
System.out.println("消息为空或连接已关闭,忽略处理:userId=" + userId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 解析消息为统一协议对象(JSON解析)
|
||||
WebSocketMessage wsMessage = JSON.parseObject(message, WebSocketMessage.class);
|
||||
if (wsMessage == null) {
|
||||
sendToUser(userId, "错误:消息格式不合法,请按JSON格式发送");
|
||||
// 2. 前置校验:判断是否为合法JSON(避免非JSON字符串直接解析)
|
||||
String trimMsg = message.trim();
|
||||
if (!isValidJson(trimMsg)) {
|
||||
String errorMsg = "错误:消息格式必须是JSON字符串,当前消息:" + trimMsg;
|
||||
sendToUser(userId, errorMsg);
|
||||
System.out.println("非JSON消息被拒绝:userId=" + userId + ",消息=" + trimMsg);
|
||||
return;
|
||||
}
|
||||
//暂时使用默认时间戳,生产环境应该使用客户端必传字段
|
||||
|
||||
// 3. 解析JSON为消息对象(捕获解析异常)
|
||||
WebSocketMessage wsMessage;
|
||||
try {
|
||||
wsMessage = JSON.parseObject(trimMsg, WebSocketMessage.class);
|
||||
} catch (Exception e) {
|
||||
String errorMsg = "错误:JSON格式不合法,无法解析为WebSocketMessage,原因:" + e.getMessage();
|
||||
sendToUser(userId, errorMsg);
|
||||
System.out.println("JSON解析失败:userId=" + userId + ",消息=" + trimMsg + ",原因=" + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 校验解析后的对象是否为空
|
||||
if (wsMessage == null) {
|
||||
String errorMsg = "错误:消息解析后为空,请检查JSON格式";
|
||||
sendToUser(userId, errorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. 补充默认值(时间戳/发送者ID)
|
||||
if (wsMessage.getTimestamp() == null || wsMessage.getTimestamp() == 0) {
|
||||
wsMessage.setTimestamp(System.currentTimeMillis());
|
||||
}
|
||||
// 3. 补充发送者ID(从路径参数获取,避免客户端伪造)
|
||||
wsMessage.setSenderId(userId);
|
||||
|
||||
// 6. 生成唯一消息ID(避免重试队列重复)
|
||||
if (wsMessage.getMsgId() == null || wsMessage.getMsgId().isEmpty()) {
|
||||
wsMessage.setMsgId(userId + "_" + System.currentTimeMillis());
|
||||
}
|
||||
// 4. 获取Spring管理的业务层Bean,转发消息处理
|
||||
WebSocketMsgService msgService = SpringUtils.getBean(WebSocketMsgService.class);
|
||||
if (msgService == null) {
|
||||
@ -190,7 +218,26 @@ public class ChatWebSocketServer {
|
||||
}
|
||||
return roleUsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据登录IP筛选在线用户(精确匹配)
|
||||
* @param loginIp 要查询的登录IP(如 "192.168.1.100")
|
||||
* @return 匹配该IP的在线且连接有效的用户列表
|
||||
*/
|
||||
public static List<WebSocketUser> getOnlineUsersByLoginIp(String loginIp) {
|
||||
List<WebSocketUser> ipUsers = new ArrayList<>();
|
||||
// 前置空值校验:避免传入null/空字符串导致无效查询
|
||||
if (loginIp == null || loginIp.trim().isEmpty()) {
|
||||
return ipUsers;
|
||||
}
|
||||
// 遍历在线用户,筛选匹配IP且连接有效的用户
|
||||
for (WebSocketUser user : ONLINE_USER_MAP.values()) {
|
||||
// 双重校验:IP精确匹配 + 连接有效
|
||||
if (loginIp.equals(user.getLoginIp()) && user.isConnected()) {
|
||||
ipUsers.add(user);
|
||||
}
|
||||
}
|
||||
return ipUsers;
|
||||
}
|
||||
/**
|
||||
* 获取在线用户的userId列表(兼容原有逻辑)
|
||||
*/
|
||||
@ -291,5 +338,19 @@ public class ChatWebSocketServer {
|
||||
user.removeDynamicAttr(attrKey);
|
||||
return true;
|
||||
}
|
||||
private boolean isValidJson(String jsonStr) {
|
||||
// 1. 先过滤空值,避免无意义解析
|
||||
if (jsonStr == null || jsonStr.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 核心:用 FastJSON2 解析为 JSON 对象/数组,解析成功则合法
|
||||
JSON.parse(jsonStr);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
// 3. 解析失败(抛出 JSONException),说明非合法 JSON
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import lombok.NoArgsConstructor;
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class WebSocketMessage {
|
||||
private String msgId;
|
||||
// 消息类型(如CHAT/NOTICE/ORDER/HEARTBEAT)
|
||||
private String msgType;
|
||||
// 业务数据(JSON字符串,可解析为对应业务DTO)
|
||||
|
||||
@ -1,17 +1,11 @@
|
||||
package com.czlis.interfaceCommon.websocket;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.czlis.common.annotation.Async;
|
||||
import com.czlis.common.core.redis.RedisCache;
|
||||
import com.czlis.common.utils.spring.SpringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import javax.websocket.Session;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**业务层(Spring Bean)专注于「消息解析、权限校验、业务逻辑处理、结果推送」,支持不同类型消息的差异化处理。
|
||||
@ -32,6 +26,7 @@ public class WebSocketMsgServiceImpl implements WebSocketMsgService {
|
||||
ChatWebSocketServer.sendToUser(wsMessage.getSenderId(), "提示:消息已处理,无需重复发送");
|
||||
return;
|
||||
}
|
||||
System.err.println("wsMessage=" + wsMessage);
|
||||
try {
|
||||
// 1. 基础校验:消息类型、发送者ID
|
||||
String msgType = wsMessage.getMsgType();
|
||||
@ -170,22 +165,82 @@ public class WebSocketMsgServiceImpl implements WebSocketMsgService {
|
||||
private void handleHeartbeatMsg(WebSocketMessage wsMessage) {
|
||||
String senderId = wsMessage.getSenderId();
|
||||
// 回复心跳确认
|
||||
ChatWebSocketServer.sendToUser(senderId, JSON.toJSONString(new WebSocketMessage("HEARTBEAT_ACK", "pong", senderId, null, System.currentTimeMillis())));
|
||||
ChatWebSocketServer.sendToUser(senderId, JSON.toJSONString(new WebSocketMessage(senderId + "_" + System.currentTimeMillis(),"HEARTBEAT_ACK", "pong", senderId, null, System.currentTimeMillis())));
|
||||
}
|
||||
/**
|
||||
* 示例3:给在线用户设置动态属性(如设备类型)
|
||||
* 示例方法:给在线用户设置动态属性(如设备类型),优化后版本
|
||||
*/
|
||||
public void setClientInfo(WebSocketMessage wsMessage) {
|
||||
// 1. 前置空值校验:避免空指针异常
|
||||
if (wsMessage == null) {
|
||||
System.err.println("setClientInfo失败:wsMessage为null");
|
||||
return;
|
||||
}
|
||||
|
||||
String senderId = wsMessage.getSenderId();
|
||||
String data = wsMessage.getData();
|
||||
Map<String, Object> dataStrMap = JSON.parseObject(data, Map.class);
|
||||
|
||||
// 校验核心参数
|
||||
if (senderId == null || senderId.trim().isEmpty()) {
|
||||
System.err.println("setClientInfo失败:senderId为空");
|
||||
return;
|
||||
}
|
||||
if (data == null || data.trim().isEmpty()) {
|
||||
System.err.println("setClientInfo失败:data为空,senderId=" + senderId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 安全解析JSON:捕获解析异常
|
||||
Map<String, Object> dataStrMap;
|
||||
try {
|
||||
dataStrMap = JSON.parseObject(data, Map.class);
|
||||
} catch (Exception e) {
|
||||
System.err.println("setClientInfo失败:JSON解析异常,senderId=" + senderId + ",data=" + data + ",异常:" + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 获取在线用户并校验有效性
|
||||
WebSocketUser user = ChatWebSocketServer.getOnlineUser(senderId);
|
||||
if (user != null) {
|
||||
for (Map.Entry<String, Object> entry : dataStrMap.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value =(String)entry.getValue();
|
||||
user.setDynamicAttr(key, value);
|
||||
if (user == null) {
|
||||
System.err.println("setClientInfo失败:未找到在线用户,senderId=" + senderId);
|
||||
return;
|
||||
}
|
||||
// // 额外校验连接是否有效
|
||||
// if (!user.isConnected()) {
|
||||
// System.err.println("setClientInfo失败:用户连接已关闭,senderId=" + senderId);
|
||||
// // 可选:移除离线用户
|
||||
// ChatWebSocketServer.removeOnlineUser(senderId);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 4. 遍历更新属性:优化判断逻辑,避免重复赋值
|
||||
for (Map.Entry<String, Object> entry : dataStrMap.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
// 空值校验:避免key/value为null导致的异常
|
||||
if (key == null || entry.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
// 统一转为字符串(兼容非字符串类型的value)
|
||||
String value = String.valueOf(entry.getValue()).trim();
|
||||
|
||||
// 优先更新固定属性,再更新动态属性
|
||||
switch (key) {
|
||||
case "username":
|
||||
user.setUsername(value);
|
||||
break;
|
||||
case "role":
|
||||
user.setRole(value);
|
||||
break;
|
||||
case "loginIp":
|
||||
user.setLoginIp(value);
|
||||
break;
|
||||
default:
|
||||
// 非固定属性,存入动态属性
|
||||
user.setDynamicAttr(key, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("setClientInfo成功:用户属性更新完成,senderId=" + senderId);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
package com.czlis.interfaceCommon.websocket;
|
||||
|
||||
import javax.websocket.Session;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -10,12 +9,12 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* 封装WebSocket用户连接信息:聚合Session + 身份属性 + 连接元数据
|
||||
*/
|
||||
public class WebSocketUser {
|
||||
// 核心字段:用户唯一标识
|
||||
// 核心字段:用户唯一标识(仍保持final,不建议修改)
|
||||
private final String userId;
|
||||
// 身份属性(可根据业务扩展)
|
||||
private final String username; // 用户名
|
||||
private final String role; // 角色(如ADMIN/USER)
|
||||
private final String loginIp; // 登录IP
|
||||
// 身份属性(移除final修饰符,支持修改)
|
||||
private String username; // 用户名
|
||||
private String role; // 角色(如ADMIN/USER)
|
||||
private String loginIp; // 登录IP
|
||||
// 连接元数据
|
||||
private final Session session; // WebSocket连接会话
|
||||
private final LocalDateTime connectTime; // 连接建立时间
|
||||
@ -38,18 +37,34 @@ public class WebSocketUser {
|
||||
return userId;
|
||||
}
|
||||
|
||||
// ========== 可修改属性(getter + setter) ==========
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
// 添加username的setter方法
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
// 添加role的setter方法
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getLoginIp() {
|
||||
return loginIp;
|
||||
}
|
||||
|
||||
// 添加loginIp的setter方法
|
||||
public void setLoginIp(String loginIp) {
|
||||
this.loginIp = loginIp;
|
||||
}
|
||||
|
||||
public Session getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
@ -33,8 +33,8 @@ public class DictController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ComDictService comDictService;
|
||||
@Autowired
|
||||
WebSocketInstrComm webSocketInstrComm;
|
||||
// @Autowired
|
||||
// WebSocketInstrComm webSocketInstrComm;
|
||||
|
||||
/**
|
||||
* 获取字典列表
|
||||
@ -107,11 +107,11 @@ public class DictController extends BaseController {
|
||||
return comDictService.insertComDictList(comDictList, updateSupport);
|
||||
}
|
||||
|
||||
@ApiOperation("测试服务")
|
||||
@GetMapping("/test1")
|
||||
public Result test1(){
|
||||
String jsonStr = "{\"data\":\"1234\"}";
|
||||
webSocketInstrComm.sendToAllClient(jsonStr);
|
||||
return new Result("0","成功!");
|
||||
}
|
||||
// @ApiOperation("测试服务")
|
||||
// @GetMapping("/test1")
|
||||
// public Result test1(){
|
||||
// String jsonStr = "{\"data\":\"1234\"}";
|
||||
// webSocketInstrComm.sendToAllClient(jsonStr);
|
||||
// return new Result("0","成功!");
|
||||
// }
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ import com.czlis.common.core.domain.Result;
|
||||
import com.czlis.common.core.domain.entity.lis.LabIntersendList;
|
||||
import com.czlis.common.core.domain.entity.lis.LabReqdetail;
|
||||
import com.czlis.common.core.domain.entity.lis.LabReqmain;
|
||||
import com.czlis.common.utils.SecurityUtils;
|
||||
import com.czlis.common.utils.ip.IpUtils;
|
||||
import com.czlis.interfaceCommon.constants.LisinterfaceNameConstants;
|
||||
import com.czlis.interfaceCommon.pojo.entity.ReportParam;
|
||||
import com.czlis.interfaceCommon.pojo.inter.GetReqInterface;
|
||||
@ -16,7 +18,10 @@ import com.czlis.interfaceCommon.utils.CommonUtil;
|
||||
import com.czlis.interfaceCommon.utils.LisInterfaceUtil;
|
||||
import com.czlis.interfaceCommon.utils.LisUtil;
|
||||
import com.czlis.interfaceCommon.utils.ReportUtil;
|
||||
import com.czlis.interfaceCommon.websocket.ChatWebSocketServer;
|
||||
import com.czlis.interfaceCommon.websocket.WebSocketInstrComm;
|
||||
import com.czlis.interfaceCommon.websocket.WebSocketMessage;
|
||||
import com.czlis.interfaceCommon.websocket.WebSocketUser;
|
||||
import com.czlis.zycx.mapper.LabIntersendListMapper;
|
||||
import com.czlis.zycx.mapper.ZycxMapper;
|
||||
import com.czlis.zycx.pojo.QuerySqdVO;
|
||||
@ -312,9 +317,26 @@ public class ZycxServiceImpl implements ZycxService {
|
||||
labIntersendList.setUserid(userId);
|
||||
labIntersendList.setCreatedate(commonUtil.getCurrentTime());
|
||||
labIntersendListMapper.insert(labIntersendList);
|
||||
return new Result("0","打印成功!",guid);
|
||||
WebSocketMessage wsMessage=new WebSocketMessage();
|
||||
wsMessage.setMsgType("lisbarprint");
|
||||
wsMessage.setData("lisbarprint://"+guid);
|
||||
wsMessage.setSenderId(SecurityUtils.getUsername());
|
||||
return sendprintmsg(JSONUtil.toJsonStr(wsMessage));
|
||||
// return new Result("0","打印成功!",guid);
|
||||
}
|
||||
|
||||
public Result sendprintmsg(String msg){
|
||||
String ip= IpUtils.getIpAddr();
|
||||
List<WebSocketUser> printUsers = ChatWebSocketServer.getOnlineUsersByLoginIp(ip);
|
||||
if (printUsers.isEmpty()) {
|
||||
System.out.println("当前客户端打印插件不在线");
|
||||
return new Result("-1","当前客户端打印插件不在线");
|
||||
}
|
||||
WebSocketUser webSocketUser=printUsers.get(0);
|
||||
ChatWebSocketServer.sendToUser(webSocketUser.getUserId(), msg);
|
||||
System.out.println("已给客户端[" + webSocketUser.getUsername() + "]推送打印通知,IP:" + webSocketUser.getLoginIp());
|
||||
return new Result("0","打印成功");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user