From a3d6e0ce08baed91c3121930c18d64290ff00eeb Mon Sep 17 00:00:00 2001 From: zhangdaiscott Date: Mon, 18 Apr 2022 11:10:38 +0800 Subject: [PATCH] =?UTF-8?q?git=E5=A4=A7=E5=B0=8F=E5=86=99=E4=B8=8D?= =?UTF-8?q?=E6=95=8F=E6=84=9F=EF=BC=8C=E5=AF=BC=E8=87=B4=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E7=B1=BB=E5=90=8D=E6=9C=AA=E6=8F=90=E4=BA=A4=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jeecg/common/constant/VxeSocketConst.java | 31 +++ .../java/org/jeecg/common/util/IpUtils.java | 59 +++++ .../java/org/jeecg/common/util/Md5Util.java | 47 ++++ .../demo/mock/vxe/websocket/VxeSocket.java | 217 ++++++++++++++++++ 4 files changed, 354 insertions(+) create mode 100644 jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/VxeSocketConst.java create mode 100644 jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/IpUtils.java create mode 100644 jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Md5Util.java create mode 100644 jeecg-boot/jeecg-boot-module-demo/src/main/java/org/jeecg/modules/demo/mock/vxe/websocket/VxeSocket.java diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/VxeSocketConst.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/VxeSocketConst.java new file mode 100644 index 00000000..75594630 --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/VxeSocketConst.java @@ -0,0 +1,31 @@ +package org.jeecg.common.constant; + +/** + * VXESocket 常量 + * @author: jeecg-boot + */ +public class VxeSocketConst { + + /** + * 消息类型 + */ + public static final String TYPE = "type"; + /** + * 消息数据 + */ + public static final String DATA = "data"; + + /** + * 消息类型:心跳检测 + */ + public static final String TYPE_HB = "heart_beat"; + /** + * 消息类型:通用数据传递 + */ + public static final String TYPE_CSD = "common_send_date"; + /** + * 消息类型:更新vxe table数据 + */ + public static final String TYPE_UVT = "update_vxe_table"; + +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/IpUtils.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/IpUtils.java new file mode 100644 index 00000000..01b9d6be --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/IpUtils.java @@ -0,0 +1,59 @@ +package org.jeecg.common.util; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.jeecg.common.constant.CommonConstant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * IP地址 + * + * @Author scott + * @email jeecgos@163.com + * @Date 2019年01月14日 + */ +public class IpUtils { + private static Logger logger = LoggerFactory.getLogger(IpUtils.class); + + /** + * 获取IP地址 + * + * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址 + * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址 + */ + public static String getIpAddr(HttpServletRequest request) { + String ip = null; + try { + ip = request.getHeader("x-forwarded-for"); + if (StringUtils.isEmpty(ip) || CommonConstant.UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (StringUtils.isEmpty(ip) || ip.length() == 0 ||CommonConstant.UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (StringUtils.isEmpty(ip) || CommonConstant.UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (StringUtils.isEmpty(ip) || CommonConstant.UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (StringUtils.isEmpty(ip) || CommonConstant.UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + } catch (Exception e) { + logger.error("IPUtils ERROR ", e); + } + +// //使用代理,则获取第一个IP地址 +// if(StringUtils.isEmpty(ip) && ip.length() > 15) { +// if(ip.indexOf(",") > 0) { +// ip = ip.substring(0, ip.indexOf(",")); +// } +// } + + return ip; + } + +} diff --git a/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Md5Util.java b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Md5Util.java new file mode 100644 index 00000000..d79f3a5a --- /dev/null +++ b/jeecg-boot/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/Md5Util.java @@ -0,0 +1,47 @@ +package org.jeecg.common.util; + +import java.security.MessageDigest; + +/** + * @Description: 加密工具 + * @author: jeecg-boot + */ +public class Md5Util { + + private static final String[] HEXDIGITS = { "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; + + public static String byteArrayToHexString(byte[] b) { + StringBuffer resultSb = new StringBuffer(); + for (int i = 0; i < b.length; i++){ + resultSb.append(byteToHexString(b[i])); + } + return resultSb.toString(); + } + + private static String byteToHexString(byte b) { + int n = b; + if (n < 0) { + n += 256; + } + int d1 = n / 16; + int d2 = n % 16; + return HEXDIGITS[d1] + HEXDIGITS[d2]; + } + + public static String md5Encode(String origin, String charsetname) { + String resultString = null; + try { + resultString = new String(origin); + MessageDigest md = MessageDigest.getInstance("MD5"); + if (charsetname == null || "".equals(charsetname)) { + resultString = byteArrayToHexString(md.digest(resultString.getBytes())); + } else { + resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname))); + } + } catch (Exception exception) { + } + return resultString; + } + +} diff --git a/jeecg-boot/jeecg-boot-module-demo/src/main/java/org/jeecg/modules/demo/mock/vxe/websocket/VxeSocket.java b/jeecg-boot/jeecg-boot-module-demo/src/main/java/org/jeecg/modules/demo/mock/vxe/websocket/VxeSocket.java new file mode 100644 index 00000000..24d4eb31 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module-demo/src/main/java/org/jeecg/modules/demo/mock/vxe/websocket/VxeSocket.java @@ -0,0 +1,217 @@ +package org.jeecg.modules.demo.mock.vxe.websocket; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.jeecg.common.constant.VxeSocketConst; +import org.springframework.stereotype.Component; + +import javax.websocket.OnClose; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * vxe WebSocket,用于实现实时无痕刷新的功能 + * @author: jeecg-boot + */ +@Slf4j +@Component +@ServerEndpoint("/vxeSocket/{userId}/{pageId}") +public class VxeSocket { + + /** + * 当前 session + */ + private Session session; + /** + * 当前用户id + */ + private String userId; + /** + * 页面id,用于标识同一用户,不同页面的数据 + */ + private String pageId; + /** + * 当前socket唯一id + */ + private String socketId; + + /** + * 用户连接池,包含单个用户的所有socket连接; + * 因为一个用户可能打开多个页面,多个页面就会有多个连接; + * key是userId,value是Map对象;子Map的key是pageId,value是VXESocket对象 + */ + private static Map> userPool = new HashMap<>(); + /** + * 连接池,包含所有WebSocket连接; + * key是socketId,value是VXESocket对象 + */ + private static Map socketPool = new HashMap<>(); + + /** + * 获取某个用户所有的页面 + */ + public static Map getUserPool(String userId) { + return userPool.computeIfAbsent(userId, k -> new HashMap<>(5)); + } + + /** + * 向当前用户发送消息 + * + * @param message 消息内容 + */ + public void sendMessage(String message) { + try { + this.session.getAsyncRemote().sendText(message); + } catch (Exception e) { + log.error("【vxeSocket】消息发送失败:" + e.getMessage()); + } + } + + /** + * 封装消息json + * + * @param data 消息内容 + */ + public static String packageMessage(String type, Object data) { + JSONObject message = new JSONObject(); + message.put(VxeSocketConst.TYPE, type); + message.put(VxeSocketConst.DATA, data); + return message.toJSONString(); + } + + /** + * 向指定用户的所有页面发送消息 + * + * @param userId 接收消息的用户ID + * @param message 消息内容 + */ + public static void sendMessageTo(String userId, String message) { + Collection values = getUserPool(userId).values(); + if (values.size() > 0) { + for (VxeSocket socketItem : values) { + socketItem.sendMessage(message); + } + } else { + log.warn("【vxeSocket】消息发送失败:userId\"" + userId + "\"不存在或未在线!"); + } + } + + /** + * 向指定用户的指定页面发送消息 + * + * @param userId 接收消息的用户ID + * @param message 消息内容 + */ + public static void sendMessageTo(String userId, String pageId, String message) { + VxeSocket socketItem = getUserPool(userId).get(pageId); + if (socketItem != null) { + socketItem.sendMessage(message); + } else { + log.warn("【vxeSocket】消息发送失败:userId\"" + userId + "\"的pageId\"" + pageId + "\"不存在或未在线!"); + } + } + + /** + * 向多个用户的所有页面发送消息 + * + * @param userIds 接收消息的用户ID数组 + * @param message 消息内容 + */ + public static void sendMessageTo(String[] userIds, String message) { + for (String userId : userIds) { + VxeSocket.sendMessageTo(userId, message); + } + } + + /** + * 向所有用户的所有页面发送消息 + * + * @param message 消息内容 + */ + public static void sendMessageToAll(String message) { + for (VxeSocket socketItem : socketPool.values()) { + socketItem.sendMessage(message); + } + } + + /** + * websocket 开启连接 + */ + @OnOpen + public void onOpen(Session session, @PathParam("userId") String userId, @PathParam("pageId") String pageId) { + try { + this.userId = userId; + this.pageId = pageId; + this.socketId = userId + pageId; + this.session = session; + + socketPool.put(this.socketId, this); + getUserPool(userId).put(this.pageId, this); + + log.info("【vxeSocket】有新的连接,总数为:" + socketPool.size()); + } catch (Exception ignored) { + } + } + + /** + * websocket 断开连接 + */ + @OnClose + public void onClose() { + try { + socketPool.remove(this.socketId); + getUserPool(this.userId).remove(this.pageId); + + log.info("【vxeSocket】连接断开,总数为:" + socketPool.size()); + } catch (Exception ignored) { + } + } + + /** + * websocket 收到消息 + */ + @OnMessage + public void onMessage(String message) { + // log.info("【vxeSocket】onMessage:" + message); + JSONObject json; + try { + json = JSON.parseObject(message); + } catch (Exception e) { + log.warn("【vxeSocket】收到不合法的消息:" + message); + return; + } + String type = json.getString(VxeSocketConst.TYPE); + switch (type) { + // 心跳检测 + case VxeSocketConst.TYPE_HB: + this.sendMessage(VxeSocket.packageMessage(type, true)); + break; + // 更新form数据 + case VxeSocketConst.TYPE_UVT: + this.handleUpdateForm(json); + break; + default: + log.warn("【vxeSocket】收到不识别的消息类型:" + type); + break; + } + + + } + + /** + * 处理 UpdateForm 事件 + */ + private void handleUpdateForm(JSONObject json) { + // 将事件转发给所有人 + JSONObject data = json.getJSONObject(VxeSocketConst.DATA); + VxeSocket.sendMessageToAll(VxeSocket.packageMessage(VxeSocketConst.TYPE_UVT, data)); + } + +}