浏览代码

添加websocket

gaoxiong 1 年之前
父节点
当前提交
1a8f3d35cd
共有 24 个文件被更改,包括 908 次插入4 次删除
  1. 14 0
      pom.xml
  2. 27 0
      soc-api/soc-api-system/src/main/java/com/xunmei/system/api/vo/WebSocketInfoVo.java
  3. 36 0
      soc-api/soc-api-system/src/main/java/com/xunmei/system/api/vo/WebSocketSendVo.java
  4. 31 0
      soc-common/soc-common-json/pom.xml
  5. 41 0
      soc-common/soc-common-json/src/main/java/com/xunmei/common/json/config/JacksonConfig.java
  6. 42 0
      soc-common/soc-common-json/src/main/java/com/xunmei/common/json/handler/BigNumberSerializer.java
  7. 113 0
      soc-common/soc-common-json/src/main/java/com/xunmei/common/json/utils/JsonUtils.java
  8. 1 0
      soc-common/soc-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  9. 47 0
      soc-common/soc-common-websocket/pom.xml
  10. 56 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/config/WebSocketConfig.java
  11. 26 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/config/properties/WebSocketProperties.java
  12. 28 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/constant/WebSocketConstants.java
  13. 27 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/dto/WebSocketMessageDto.java
  14. 106 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/handler/PlusWebSocketHandler.java
  15. 46 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/holder/WebSocketSessionHolder.java
  16. 61 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/interceptor/PlusWebSocketInterceptor.java
  17. 43 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/listener/WebSocketTopicListener.java
  18. 110 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/utils/WebSocketUtils.java
  19. 1 0
      soc-common/soc-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  20. 10 0
      soc-gateway/src/main/java/com/xunmei/gateway/filter/AuthFilter.java
  21. 5 0
      soc-modules/soc-modules-system/pom.xml
  22. 2 0
      soc-modules/soc-modules-system/src/main/java/com/xunmei/system/controller/SysUserController.java
  23. 24 0
      soc-modules/soc-modules-system/src/main/java/com/xunmei/system/controller/WebSocketSendController.java
  24. 11 4
      soc-modules/soc-modules-system/src/main/java/com/xunmei/system/service/impl/SysWorkTimeServiceImpl.java

+ 14 - 0
pom.xml

@@ -240,6 +240,18 @@
                 <version>${soc.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.xunmei</groupId>
+                <artifactId>soc-common-json</artifactId>
+                <version>${soc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.xunmei</groupId>
+                <artifactId>soc-common-websocket</artifactId>
+                <version>${soc.version}</version>
+            </dependency>
+
 
             <dependency>
                 <groupId>org.projectlombok</groupId>
@@ -272,6 +284,8 @@
         <module>soc-auth</module>
         <module>soc-modules</module>
         <module>soc-visual</module>
+        <module>soc-common/soc-common-websocket</module>
+        <module>soc-common/soc-common-json</module>
     </modules>
     <dependencies>
         <!-- bootstrap 启动器 -->

+ 27 - 0
soc-api/soc-api-system/src/main/java/com/xunmei/system/api/vo/WebSocketInfoVo.java

@@ -0,0 +1,27 @@
+package com.xunmei.system.api.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 系统消息Websocket消息vo类
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class WebSocketInfoVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    /**
+     * 消息类型,1.告警消息,2.通知消息。3.系统消息
+     */
+    private int type;
+    /**
+     * 消息主机内容
+     */
+    private Object Content;
+
+}

+ 36 - 0
soc-api/soc-api-system/src/main/java/com/xunmei/system/api/vo/WebSocketSendVo.java

@@ -0,0 +1,36 @@
+package com.xunmei.system.api.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 消息发送类
+ * @Author: gaoxiong
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class WebSocketSendVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 发送消息的用户id
+     */
+    private Long userId;
+    /**
+     * 发送消息的机构id
+     */
+    private Long orgId;
+    /**
+     * 发送的消息体内容
+     */
+    private Object content;
+    /**
+     * 发送的消息体类型
+     */
+    private int type;
+}

+ 31 - 0
soc-common/soc-common-json/pom.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>soc-common</artifactId>
+        <groupId>com.xunmei</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>soc-common-json</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.xunmei</groupId>
+            <artifactId>soc-common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 41 - 0
soc-common/soc-common-json/src/main/java/com/xunmei/common/json/config/JacksonConfig.java

@@ -0,0 +1,41 @@
+package com.xunmei.common.json.config;
+
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.xunmei.common.json.handler.BigNumberSerializer;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.TimeZone;
+
+@Slf4j
+@AutoConfiguration(before = JacksonAutoConfiguration.class)
+public class JacksonConfig {
+
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer customizer() {
+        return builder -> {
+            // 全局配置序列化返回 JSON 处理
+            JavaTimeModule javaTimeModule = new JavaTimeModule();
+            javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
+            javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
+            javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
+            javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
+            javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
+            builder.modules(javaTimeModule);
+            builder.timeZone(TimeZone.getDefault());
+            log.info("初始化 jackson 配置");
+        };
+    }
+}

+ 42 - 0
soc-common/soc-common-json/src/main/java/com/xunmei/common/json/handler/BigNumberSerializer.java

@@ -0,0 +1,42 @@
+package com.xunmei.common.json.handler;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
+
+import java.io.IOException;
+
+/**
+ * 超出 JS 最大最小值 处理
+ *
+ * @author Lion Li
+ */
+@JacksonStdImpl
+public class BigNumberSerializer extends NumberSerializer {
+
+    /**
+     * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来
+     */
+    private static final long MAX_SAFE_INTEGER = 9007199254740991L;
+    private static final long MIN_SAFE_INTEGER = -9007199254740991L;
+
+    /**
+     * 提供实例
+     */
+    public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);
+
+    public BigNumberSerializer(Class<? extends Number> rawType) {
+        super(rawType);
+    }
+
+    @Override
+    public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+        // 超出范围 序列化位字符串
+        if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
+            super.serialize(value, gen, provider);
+        } else {
+            gen.writeString(value.toString());
+        }
+    }
+}

+ 113 - 0
soc-common/soc-common-json/src/main/java/com/xunmei/common/json/utils/JsonUtils.java

@@ -0,0 +1,113 @@
+package com.xunmei.common.json.utils;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import com.xunmei.common.core.utils.SpringUtils;
+import com.xunmei.common.core.utils.StringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * JSON 工具类
+ *
+ * @author 芋道源码
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class JsonUtils {
+
+    private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
+
+    public static ObjectMapper getObjectMapper() {
+        return OBJECT_MAPPER;
+    }
+
+    public static String toJsonString(Object object) {
+        if (ObjectUtil.isNull(object)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.writeValueAsString(object);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(String text, Class<T> clazz) {
+        if (StringUtils.isEmpty(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, clazz);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
+        if (ArrayUtil.isEmpty(bytes)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(bytes, clazz);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(String text, TypeReference<T> typeReference) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, typeReference);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Dict parseMap(String text) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
+        } catch (MismatchedInputException e) {
+            // 类型不匹配说明不是json
+            return null;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static List<Dict> parseArrayMap(String text) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> List<T> parseArray(String text, Class<T> clazz) {
+        if (StringUtils.isEmpty(text)) {
+            return new ArrayList<>();
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}

+ 1 - 0
soc-common/soc-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+com.xunmei.common.json.config.JacksonConfig

+ 47 - 0
soc-common/soc-common-websocket/pom.xml

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>soc-common</artifactId>
+        <groupId>com.xunmei</groupId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>soc-common-websocket</artifactId>
+
+    <description>websocket 模块</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.xunmei</groupId>
+            <artifactId>soc-common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.xunmei</groupId>
+            <artifactId>soc-common-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.xunmei</groupId>
+            <artifactId>soc-common-json</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.xunmei</groupId>
+            <artifactId>soc-api-system</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>com.xunmei</groupId>
+            <artifactId>soc-common-security</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 56 - 0
soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/config/WebSocketConfig.java

@@ -0,0 +1,56 @@
+package com.xunmei.common.websocket.config;
+
+import cn.hutool.core.util.StrUtil;
+import com.xunmei.common.websocket.config.properties.WebSocketProperties;
+import com.xunmei.common.websocket.handler.PlusWebSocketHandler;
+import com.xunmei.common.websocket.interceptor.PlusWebSocketInterceptor;
+import com.xunmei.common.websocket.listener.WebSocketTopicListener;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+
+@AutoConfiguration
+@ConditionalOnProperty(value = "websocket.enabled", havingValue = "true")
+@EnableConfigurationProperties(WebSocketProperties.class)
+@EnableWebSocket
+public class WebSocketConfig  {
+
+    @Bean
+    public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor handshakeInterceptor,
+                                                   WebSocketHandler webSocketHandler,
+                                                   WebSocketProperties webSocketProperties) {
+        if (StrUtil.isBlank(webSocketProperties.getPath())) {
+            webSocketProperties.setPath("/websocket");
+        }
+
+        if (StrUtil.isBlank(webSocketProperties.getAllowedOrigins())) {
+            webSocketProperties.setAllowedOrigins("*");
+        }
+
+        return registry -> registry
+                .addHandler(webSocketHandler, webSocketProperties.getPath())
+                .addInterceptors(handshakeInterceptor)
+                .setAllowedOriginPatterns()
+                .setAllowedOrigins(webSocketProperties.getAllowedOrigins());
+    }
+
+    @Bean
+    public HandshakeInterceptor handshakeInterceptor() {
+        return new PlusWebSocketInterceptor();
+    }
+
+    @Bean
+    public WebSocketHandler webSocketHandler() {
+        return new PlusWebSocketHandler();
+    }
+
+    @Bean
+    public WebSocketTopicListener topicListener() {
+        return new WebSocketTopicListener();
+    }
+}

+ 26 - 0
soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/config/properties/WebSocketProperties.java

@@ -0,0 +1,26 @@
+package com.xunmei.common.websocket.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * WebSocket 配置项
+ *
+ * 
+ */
+@ConfigurationProperties("websocket")
+@Data
+public class WebSocketProperties {
+
+    private Boolean enabled;
+
+    /**
+     * 路径
+     */
+    private String path;
+
+    /**
+     *  设置访问源地址
+     */
+    private String allowedOrigins;
+}

+ 28 - 0
soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/constant/WebSocketConstants.java

@@ -0,0 +1,28 @@
+package com.xunmei.common.websocket.constant;
+
+/**
+ * websocket的常量配置
+ *
+ * 
+ */
+public interface WebSocketConstants {
+    /**
+     * websocketSession中的参数的key
+     */
+    String LOGIN_USER_KEY = "loginUser";
+
+    /**
+     * 订阅的频道
+     */
+    String WEB_SOCKET_TOPIC = "global:websocket";
+
+    /**
+     * 前端心跳检查的命令
+     */
+    String PING = "ping";
+
+    /**
+     * 服务端心跳恢复的字符串
+     */
+    String PONG = "pong";
+}

+ 27 - 0
soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/dto/WebSocketMessageDto.java

@@ -0,0 +1,27 @@
+package com.xunmei.common.websocket.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 消息的dto
+ *
+ * 
+ */
+@Data
+public class WebSocketMessageDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 需要推送到的session key 列表
+     */
+    private List<Long> sessionKeys;
+
+    /**
+     * 需要发送的消息
+     */
+    private String message;
+}

+ 106 - 0
soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/handler/PlusWebSocketHandler.java

@@ -0,0 +1,106 @@
+package com.xunmei.common.websocket.handler;
+
+import com.xunmei.common.security.utils.SecurityUtils;
+import com.xunmei.common.websocket.dto.WebSocketMessageDto;
+import com.xunmei.common.websocket.holder.WebSocketSessionHolder;
+import com.xunmei.common.websocket.utils.WebSocketUtils;
+import com.xunmei.system.api.model.LoginUser;
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.web.socket.*;
+import org.springframework.web.socket.handler.AbstractWebSocketHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.xunmei.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY;
+
+/**
+ * WebSocketHandler 实现类
+ *
+ * 
+ */
+@Slf4j
+public class PlusWebSocketHandler extends AbstractWebSocketHandler {
+
+    /**
+     * 连接成功后
+     */
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) {
+        LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
+        WebSocketSessionHolder.addSession(loginUser.getUserid(), session);
+        log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserid(), loginUser.getUsername());
+    }
+
+    /**
+     * 处理发送来的文本消息
+     *
+     * @param session
+     * @param message
+     * @throws Exception
+     */
+    @Override
+    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+        LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
+        List<Long> userIds = new ArrayList<>();
+        userIds.add(loginUser.getUserid());
+        WebSocketMessageDto webSocketMessageDto = new WebSocketMessageDto();
+        webSocketMessageDto.setSessionKeys(userIds);
+        webSocketMessageDto.setMessage(message.getPayload());
+        WebSocketUtils.publishMessage(webSocketMessageDto);
+    }
+
+    @Override
+    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
+        super.handleBinaryMessage(session, message);
+    }
+
+    /**
+     * 心跳监测的回复
+     *
+     * @param session
+     * @param message
+     * @throws Exception
+     */
+    @Override
+    protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
+        WebSocketUtils.sendPongMessage(session);
+    }
+
+    /**
+     * 连接出错时
+     *
+     * @param session
+     * @param exception
+     * @throws Exception
+     */
+    @Override
+    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
+        log.error("[transport error] sessionId: {} , exception:{}", session.getId(), exception.getMessage());
+    }
+
+    /**
+     * 连接关闭后
+     *
+     * @param session
+     * @param status
+     */
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
+        LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
+        WebSocketSessionHolder.removeSession(loginUser.getUserid());
+        log.info("[disconnect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserid(), loginUser.getUsername());
+    }
+
+    /**
+     * 是否支持分片消息
+     *
+     * @return
+     */
+    @Override
+    public boolean supportsPartialMessages() {
+        return false;
+    }
+
+}

+ 46 - 0
soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/holder/WebSocketSessionHolder.java

@@ -0,0 +1,46 @@
+package com.xunmei.common.websocket.holder;
+
+import com.xunmei.common.redis.utils.RedisUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.redisson.api.RBucket;
+import org.redisson.api.RedissonClient;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * WebSocketSession 用于保存当前所有在线的会话信息
+ *
+ * 
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class WebSocketSessionHolder {
+
+    private static final Map<Long, WebSocketSession> USER_SESSION_MAP = new ConcurrentHashMap<>();
+
+    public static void addSession(Long sessionKey, WebSocketSession session) {
+        USER_SESSION_MAP.put(sessionKey, session);
+    }
+
+    public static void removeSession(Long sessionKey) {
+        if (USER_SESSION_MAP.containsKey(sessionKey)) {
+            USER_SESSION_MAP.remove(sessionKey);
+        }
+    }
+
+    public static WebSocketSession getSessions(Long sessionKey) {
+        return USER_SESSION_MAP.get(sessionKey);
+    }
+
+    public static Set<Long> getSessionsAll() {
+        return USER_SESSION_MAP.keySet();
+    }
+
+    public static Boolean existSession(Long sessionKey) {
+        return USER_SESSION_MAP.containsKey(sessionKey);
+    }
+}

+ 61 - 0
soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/interceptor/PlusWebSocketInterceptor.java

@@ -0,0 +1,61 @@
+package com.xunmei.common.websocket.interceptor;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.xunmei.common.core.utils.JwtUtils;
+import com.xunmei.system.api.model.LoginUser;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import static com.xunmei.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY;
+
+
+/**
+ * WebSocket握手请求的拦截器
+ *
+ * 
+ */
+@Slf4j
+public class PlusWebSocketInterceptor implements HandshakeInterceptor {
+
+    /**
+     * 握手前
+     *
+     * @param request    request
+     * @param response   response
+     * @param wsHandler  wsHandler
+     * @param attributes attributes
+     * @return 是否握手成功
+     */
+    @Override
+    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
+        HttpHeaders headers = request.getHeaders();
+        String userId = headers.get("id").get(0);
+        String userName = headers.get("userName").get(0);
+        LoginUser loginUser = new LoginUser();
+        loginUser.setUserid(Long.valueOf(userId));
+        loginUser.setUsername(userName);
+        attributes.put(LOGIN_USER_KEY, loginUser);
+        return true;
+    }
+
+    /**
+     * 握手后
+     *
+     * @param request   request
+     * @param response  response
+     * @param wsHandler wsHandler
+     * @param exception 异常
+     */
+    @Override
+    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
+
+    }
+}

+ 43 - 0
soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/listener/WebSocketTopicListener.java

@@ -0,0 +1,43 @@
+package com.xunmei.common.websocket.listener;
+
+import cn.hutool.core.collection.CollUtil;
+import com.xunmei.common.websocket.holder.WebSocketSessionHolder;
+import com.xunmei.common.websocket.utils.WebSocketUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.Ordered;
+
+/**
+ * WebSocket 主题订阅监听器
+ *
+ * 
+ */
+@Slf4j
+public class WebSocketTopicListener implements ApplicationRunner, Ordered {
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        WebSocketUtils.subscribeMessage((message) -> {
+            log.info("WebSocket主题订阅收到消息session keys={} message={}", message.getSessionKeys(), message.getMessage());
+            // 如果key不为空就按照key发消息 如果为空就群发
+            if (CollUtil.isNotEmpty(message.getSessionKeys())) {
+                message.getSessionKeys().forEach(key -> {
+                    if (WebSocketSessionHolder.existSession(key)) {
+                        WebSocketUtils.sendMessage(key, message.getMessage());
+                    }
+                });
+            } else {
+                WebSocketSessionHolder.getSessionsAll().forEach(key -> {
+                    WebSocketUtils.sendMessage(key, message.getMessage());
+                });
+            }
+        });
+        log.info("初始化WebSocket主题订阅监听器成功");
+    }
+
+    @Override
+    public int getOrder() {
+        return -1;
+    }
+}

+ 110 - 0
soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/utils/WebSocketUtils.java

@@ -0,0 +1,110 @@
+package com.xunmei.common.websocket.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import com.xunmei.common.redis.utils.RedisUtils;
+import com.xunmei.common.websocket.dto.WebSocketMessageDto;
+import com.xunmei.common.websocket.holder.WebSocketSessionHolder;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.socket.PongMessage;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketMessage;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static com.xunmei.common.websocket.constant.WebSocketConstants.WEB_SOCKET_TOPIC;
+
+/**
+ * 工具类
+ *
+ * 
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class WebSocketUtils {
+
+    /**
+     * 发送消息
+     *
+     * @param sessionKey session主键 一般为用户id
+     * @param message    消息文本
+     */
+    public static void sendMessage(Long sessionKey, String message) {
+        WebSocketSession session = WebSocketSessionHolder.getSessions(sessionKey);
+        sendMessage(session, message);
+    }
+
+    /**
+     * 订阅消息
+     *
+     * @param consumer 自定义处理
+     */
+    public static void subscribeMessage(Consumer<WebSocketMessageDto> consumer) {
+        RedisUtils.subscribe(WEB_SOCKET_TOPIC, WebSocketMessageDto.class, consumer);
+    }
+
+    /**
+     * 发布订阅的消息
+     *
+     * @param webSocketMessage 消息对象
+     */
+    public static void publishMessage(WebSocketMessageDto webSocketMessage) {
+        List<Long> unsentSessionKeys = new ArrayList<>();
+        // 当前服务内session,直接发送消息
+        for (Long sessionKey : webSocketMessage.getSessionKeys()) {
+            if (WebSocketSessionHolder.existSession(sessionKey)) {
+                WebSocketUtils.sendMessage(sessionKey, webSocketMessage.getMessage());
+                continue;
+            }
+            unsentSessionKeys.add(sessionKey);
+        }
+        // 不在当前服务内session,发布订阅消息
+        if (CollUtil.isNotEmpty(unsentSessionKeys)) {
+            WebSocketMessageDto broadcastMessage = new WebSocketMessageDto();
+            broadcastMessage.setMessage(webSocketMessage.getMessage());
+            broadcastMessage.setSessionKeys(unsentSessionKeys);
+            RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> {
+                log.info("WebSocket发送主题订阅消息topic:{} session keys:{} message:{}",
+                    WEB_SOCKET_TOPIC, unsentSessionKeys, webSocketMessage.getMessage());
+            });
+        }
+    }
+
+    /**
+     * 发布订阅的消息(群发)
+     *
+     * @param message 消息内容
+     */
+    public static void publishAll(String message) {
+        WebSocketMessageDto broadcastMessage = new WebSocketMessageDto();
+        broadcastMessage.setMessage(message);
+        RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> {
+            log.info("WebSocket发送主题订阅消息topic:{} message:{}", WEB_SOCKET_TOPIC, message);
+        });
+    }
+
+    public static void sendPongMessage(WebSocketSession session) {
+        sendMessage(session, new PongMessage());
+    }
+
+    public static void sendMessage(WebSocketSession session, String message) {
+        sendMessage(session, new TextMessage(message));
+    }
+
+    private static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
+        if (session == null || !session.isOpen()) {
+            log.warn("[send] session会话已经关闭");
+        } else {
+            try {
+                session.sendMessage(message);
+            } catch (IOException e) {
+                log.error("[send] session({}) 发送消息({}) 异常", session, message, e);
+            }
+        }
+    }
+}

+ 1 - 0
soc-common/soc-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+com.xunmei.common.websocket.config.WebSocketConfig

+ 10 - 0
soc-gateway/src/main/java/com/xunmei/gateway/filter/AuthFilter.java

@@ -18,9 +18,12 @@ import org.springframework.cloud.gateway.filter.GlobalFilter;
 import org.springframework.core.Ordered;
 import org.springframework.http.server.reactive.ServerHttpRequest;
 import org.springframework.stereotype.Component;
+import org.springframework.util.MultiValueMap;
 import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Mono;
 
+import java.util.List;
+
 /**
  * 网关鉴权
  *
@@ -124,6 +127,13 @@ public class AuthFilter implements GlobalFilter, Ordered
     private String getToken(ServerHttpRequest request)
     {
         String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
+
+        if(StringUtils.isEmpty(token)){
+            MultiValueMap<String, String> queryParams = request.getQueryParams();
+            List<String> values = queryParams.get(TokenConstants.AUTHENTICATION);
+            token = values != null && !values.isEmpty() ? values.get(0) : null;
+        }
+
         // 如果前端设置了令牌前缀,则裁剪掉前缀
         if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX))
         {

+ 5 - 0
soc-modules/soc-modules-system/pom.xml

@@ -109,6 +109,11 @@
             <optional>true</optional>
         </dependency>
 
+        <dependency>
+            <groupId>com.xunmei</groupId>
+            <artifactId>soc-common-websocket</artifactId>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 2 - 0
soc-modules/soc-modules-system/src/main/java/com/xunmei/system/controller/SysUserController.java

@@ -21,6 +21,8 @@ import com.xunmei.common.log.enums.BusinessType;
 import com.xunmei.common.security.annotation.InnerAuth;
 import com.xunmei.common.security.annotation.RequiresPermissions;
 import com.xunmei.common.security.utils.SecurityUtils;
+import com.xunmei.common.websocket.dto.WebSocketMessageDto;
+import com.xunmei.common.websocket.utils.WebSocketUtils;
 import com.xunmei.system.api.domain.SysOrg;
 import com.xunmei.system.api.domain.SysRole;
 import com.xunmei.system.api.domain.SysUser;

+ 24 - 0
soc-modules/soc-modules-system/src/main/java/com/xunmei/system/controller/WebSocketSendController.java

@@ -0,0 +1,24 @@
+package com.xunmei.system.controller;
+
+import com.xunmei.system.api.vo.WebSocketSendVo;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * websocket 全局消息通过websocket发送。
+ */
+@RestController
+@RequestMapping("/socket/send")
+public class WebSocketSendController {
+
+    /**
+     * 消息发送接口
+     * @param webSocketSendVo
+     */
+    @RequestMapping(value = "/message")
+    public void sendMessage(@RequestBody WebSocketSendVo webSocketSendVo){
+        //根据传递的参数,来判断websocket发送的人员数据,如消息类型,人员id,或者机构id
+
+    }
+}

+ 11 - 4
soc-modules/soc-modules-system/src/main/java/com/xunmei/system/service/impl/SysWorkTimeServiceImpl.java

@@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.xunmei.common.core.domain.worktime.domain.SysWorkTime;
@@ -485,12 +486,16 @@ public class SysWorkTimeServiceImpl extends ServiceImpl<SysWorkTimeMapper, SysWo
 
     @Override
     public List<SysWorkTime> findFutureWorkTime(WorkTimeDto workTimeDto) {
-        return lambdaQuery()
+        LambdaQueryChainWrapper<SysWorkTime> warp = lambdaQuery()
                 .ge(SysWorkTime::getYmdDate, workTimeDto.getStartTime())
                 .le(SysWorkTime::getYmdDate, workTimeDto.getEndTime())
-                .in(SysWorkTime::getOrgId, workTimeDto.getOrgIdList())
-                .eq(SysWorkTime::getIsEnable, 1L)
-                .select(SysWorkTime::getId,
+                .eq(SysWorkTime::getIsEnable, 1L);
+
+        if(ObjectUtil.isNotEmpty(workTimeDto.getOrgIdList())){
+            warp.in(SysWorkTime::getOrgId, workTimeDto.getOrgIdList());
+        }
+
+        List<SysWorkTime> list = warp.select(SysWorkTime::getId,
                         SysWorkTime::getIsEnable,
                         SysWorkTime::getYmdDate,
                         SysWorkTime::getOrgId, SysWorkTime::getOpenTime,
@@ -500,6 +505,8 @@ public class SysWorkTimeServiceImpl extends ServiceImpl<SysWorkTimeMapper, SysWo
                         SysWorkTime::getIsDuty)
                 .list();
 
+        return list;
+
     }
 
     @Override