Browse Source

Merge remote-tracking branch 'origin/V0.1.1_alarm' into V0.1.1_alarm

jingyuanchao 1 năm trước cách đây
mục cha
commit
f223af2fbe
31 tập tin đã thay đổi với 1392 bổ sung4 xóa
  1. 14 0
      pom.xml
  2. 61 0
      project_data/config/nacos/application-dev.yml
  3. 34 0
      project_data/config/nacos/soc-auth-dev.yml
  4. 78 0
      project_data/config/nacos/soc-core-dev.yml
  5. 6 0
      project_data/config/nacos/soc-file-dev.yml
  6. 158 0
      project_data/config/nacos/soc-gateway-dev.yml
  7. 56 0
      project_data/config/nacos/soc-job-dev.yml
  8. 91 0
      project_data/config/nacos/soc-system-dev.yml
  9. 27 0
      soc-api/soc-api-system/src/main/java/com/xunmei/system/api/vo/WebSocketInfoVo.java
  10. 36 0
      soc-api/soc-api-system/src/main/java/com/xunmei/system/api/vo/WebSocketSendVo.java
  11. 31 0
      soc-common/soc-common-json/pom.xml
  12. 41 0
      soc-common/soc-common-json/src/main/java/com/xunmei/common/json/config/JacksonConfig.java
  13. 42 0
      soc-common/soc-common-json/src/main/java/com/xunmei/common/json/handler/BigNumberSerializer.java
  14. 113 0
      soc-common/soc-common-json/src/main/java/com/xunmei/common/json/utils/JsonUtils.java
  15. 1 0
      soc-common/soc-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  16. 47 0
      soc-common/soc-common-websocket/pom.xml
  17. 56 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/config/WebSocketConfig.java
  18. 26 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/config/properties/WebSocketProperties.java
  19. 28 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/constant/WebSocketConstants.java
  20. 27 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/dto/WebSocketMessageDto.java
  21. 106 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/handler/PlusWebSocketHandler.java
  22. 46 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/holder/WebSocketSessionHolder.java
  23. 61 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/interceptor/PlusWebSocketInterceptor.java
  24. 43 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/listener/WebSocketTopicListener.java
  25. 110 0
      soc-common/soc-common-websocket/src/main/java/com/xunmei/common/websocket/utils/WebSocketUtils.java
  26. 1 0
      soc-common/soc-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  27. 10 0
      soc-gateway/src/main/java/com/xunmei/gateway/filter/AuthFilter.java
  28. 5 0
      soc-modules/soc-modules-system/pom.xml
  29. 2 0
      soc-modules/soc-modules-system/src/main/java/com/xunmei/system/controller/SysUserController.java
  30. 24 0
      soc-modules/soc-modules-system/src/main/java/com/xunmei/system/controller/WebSocketSendController.java
  31. 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 启动器 -->

+ 61 - 0
project_data/config/nacos/application-dev.yml

@@ -0,0 +1,61 @@
+spring:
+  redis:
+    database: 2
+    sentinel:
+      nodes:
+        - "redis://10.87.21.157:26379"
+        - "redis://10.87.21.158:26379"
+        - "redis://10.87.21.159:26379"
+      master: mymaster
+    password: Xunmeizongmu_2019
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  mvc:
+    pathmatch:
+      matching-strategy: ant_path_matcher
+  servlet:
+    multipart:
+      max-file-size: 20MB
+# feign 配置
+feign:
+  sentinel:
+    enabled: true
+  okhttp:
+    enabled: true
+  httpclient:
+    enabled: false
+  client:
+    config:
+      default:
+        connectTimeout: 10000
+        readTimeout: 10000
+  compression:
+    request:
+      enabled: true
+    response:
+      enabled: true
+
+# 暴露监控端点
+management:
+  endpoints:
+    web:
+      exposure:
+        include: '*'
+fjnx:
+  host:
+  systemCode:
+  passIP:
+  passPort:
+  appCode:
+  secretName:
+jwt:
+  secretKey: zbcdefghijklmnopqrstuvwxyz
+  issuer: xunmei.com
+
+websocket:
+  # 如果关闭 需要和前端开关一起关闭
+  enabled: true
+  # 路径
+  path: /websocket
+  # 设置访问源地址
+  allowedOrigins: '*'

+ 34 - 0
project_data/config/nacos/soc-auth-dev.yml

@@ -0,0 +1,34 @@
+swagger:
+  title: 认证授权中心接口文档
+  license: Powered By soc
+  licenseUrl:
+#统一运营门户登录信息
+fjnx:
+  #  统一运营门户ip
+  host: 192.111.60.143
+  #  统一运营门户端口
+  port: 9191
+  #  统一运营门户定义的系统编码
+  systemCode: 61303
+  #  密管系统ip
+  passIp: 192.111.7.75
+  #  密管系统端口
+  passPort: 40105
+  #  密管系统对应标识
+  appCode: MSP
+  #  密管系统 秘钥
+  secretName: msp.uop-SM4.zek
+  # 使用token获取用户信息,get方式
+  webInfoUrl: http://{0}:{1}/yusp-group/api/session?systemCode={2}
+  #  用户登录接口 方式post
+  userLoginUrl: http://{0}:{1}/yusp-group/api/login?systemCode={2}
+# 移动运营门户参数
+fjnxApp:
+  #  移动运营平台地址
+  host: 192.111.37.181
+  #根据token获取移动运营平台账号信息 post方式
+  accountUrl: https://{0}/uaa/api/v1.0/user/account?access_token={1}
+  #  根据账号获取人员信息 post方式
+  userInfoUrl: https://{0}/sap/api/v1.0/user/user/find/{1}?access_token={2}
+
+

+ 78 - 0
project_data/config/nacos/soc-core-dev.yml

@@ -0,0 +1,78 @@
+# spring配置
+spring:
+  datasource:
+    druid:
+      stat-view-servlet:
+        enabled: true
+        loginUsername: admin
+        loginPassword: 123456
+    dynamic:
+      druid:
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        maxWait: 60000
+        timeBetweenEvictionRunsMillis: 60000
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        filters: stat,slf4j
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        # 主库数据源
+        master:
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://10.87.21.158:3306/soc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
+          username: root
+          password: XM_zm2019
+mybatis-plus:
+  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级
+  # 例如 com.**.**.mapper
+  mapperPackage: com.xunmei.**.mapper
+  # 对应的 XML 文件位置
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 实体扫描,多个package用逗号或者分号分隔
+  typeAliasesPackage: com.xunmei.**.domain
+  # 启动时是否检查 MyBatis XML 文件的存在,默认不检查
+  checkConfigLocation: false
+  configuration:
+    # 自动驼峰命名规则(camel case)映射
+    mapUnderscoreToCamelCase: true
+    # MyBatis 自动映射策略
+    # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
+    autoMappingBehavior: PARTIAL
+    # MyBatis 自动映射时未知列或未知属性处理策
+    # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
+    autoMappingUnknownColumnBehavior: NONE
+    # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
+    # 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
+    # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
+    #logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+    logImpl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    # 是否打印 Logo banner
+    banner: true
+    dbConfig:
+      # 主键类型
+      # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
+      idType: ASSIGN_ID
+      # 逻辑已删除值
+      logicDeleteValue: 2
+      # 逻辑未删除值
+      logicNotDeleteValue: 0
+      # 字段验证策略之 insert,在 insert 的时候的字段验证策略
+      # IGNORED 忽略 NOT_NULL 非NULL NOT_EMPTY 非空 DEFAULT 默认 NEVER 不加入 SQL
+      insertStrategy: NOT_NULL
+      # 字段验证策略之 update,在 update 的时候的字段验证策略
+      updateStrategy: NOT_NULL
+      # 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
+      where-strategy: NOT_NULL
+# swagger配置
+swagger:
+  title: CORE模块接口文档
+  license: Powered By soc
+  licenseUrl:

+ 6 - 0
project_data/config/nacos/soc-file-dev.yml

@@ -0,0 +1,6 @@
+# 本地文件上传
+file:
+  secretKey: NOYQ4xpRiUM=
+  domain: http://10.87.11.94:9888
+  path: /home/xunmei/uploadPath
+  prefix: /statics

+ 158 - 0
project_data/config/nacos/soc-gateway-dev.yml

@@ -0,0 +1,158 @@
+spring:
+  cloud:
+    gateway:
+      globalcors:
+        corsConfigurations:
+          '[/**]':
+            allowedOriginPatterns: "*"
+            allowed-methods: "*"
+            allowed-headers: "*"
+            allow-credentials: true
+            exposedHeaders: "Content-Disposition,Content-Type,Cache-Control"
+      discovery:
+        locator:
+          lowerCaseServiceId: true
+          enabled: true
+      routes:
+        # 认证中心
+        - id: soc-auth
+          uri: lb://soc-auth
+          predicates:
+            - Path=/auth/**
+          filters:
+            # 验证码处理
+            - CacheRequestFilter
+            - ValidateCodeFilter
+            - StripPrefix=1
+        # 代码生成
+        - id: soc-gen
+          uri: lb://soc-gen
+          predicates:
+            - Path=/code/*/*
+          filters:
+            - StripPrefix=1
+        # 定时任务
+        - id: soc-job
+          uri: lb://soc-job
+          predicates:
+            - Path=/schedule/**
+          filters:
+            - StripPrefix=1
+        # 系统模块
+        - id: soc-system
+          uri: lb://soc-system
+          predicates:
+            - Path=/system/**
+          filters:
+            - StripPrefix=1
+        # 文件服务
+        - id: soc-file
+          uri: lb://soc-file
+          predicates:
+            - Path=/file/**
+          filters:
+            - StripPrefix=1
+        # 核心服务
+        - id: soc-core
+          uri: lb://soc-core
+          predicates:
+            - Path=/core/**
+          filters:
+            - StripPrefix=1
+        # 同步服务
+        - id: soc-sync
+          uri: lb://soc-sync
+          predicates:
+            - Path=/sync/**
+          filters:
+            - StripPrefix=1
+            # 北向服务
+        - id: soc-mediator
+          uri: lb://soc-mediator
+          predicates:
+            - Path=/mediator/**
+          filters:
+            - StripPrefix=1
+        - id: soc-iot
+          uri: lb://soc-iot
+          predicates:
+            - Path=/iot/**
+          filters:
+            - StripPrefix=1
+            # 短信服务
+        - id: soc-sms
+          uri: lb://soc-sms
+          predicates:
+            - Path=/sms/**
+          filters:
+            - StripPrefix=1
+        # 部署中心服务
+        - id: soc-deploy
+          uri: lb://soc-deploy
+          predicates:
+            - Path=/api/deploy/**
+          filters:
+            - StripPrefix=2
+          # websocket模块
+        - id: soc-websocket
+          uri: lb:ws://soc-system
+          predicates:
+            - Path=/websocket/**
+        - id: soc-websocket
+          uri: lb:ws://soc-mediator
+          predicates:
+            - Path=/ws/**
+        - id: soc-websocket
+          uri: lb:ws://soc-mediator
+          predicates:
+            - Path=/ws1/**
+        - id: soc-host
+          uri: lb:ws://soc-host
+          predicates:
+            - Path=/host/**
+        - id: soc-host
+          uri: lb:ws://soc-host
+          predicates:
+            - Path=/host1/**
+
+# 安全配置
+security:
+  # 验证码
+  captcha:
+    enabled: false
+    type: math
+  # 防止XSS攻击
+  xss:
+    enabled: true
+    excludeUrls:
+      - /system/notice
+      - /system/config
+  # 不校验白名单
+  ignore:
+    whites:
+      - /auth/logout
+      - /auth/logoutApp
+      - /auth/tokenlogin
+      - /auth/login
+      - /auth/loginApp
+      - /auth/register
+      - /*/v2/api-docs
+      - /csrf
+      - /mediator/**
+      - /system/version/checkUpdate
+      - /system/version/download/*
+      - /file/file/getFile/*
+      - /api/deploy/register
+      - /api/deploy/accesstoken
+      - /api/deploy/package/download/**
+      - /api/deploy/agent/download/**
+      - /api/deploy/frontend/synchronDate
+      - /api/deploy/heartbeat
+      - /api/deploy/list
+      - /api/deploy/report
+      - /api/deploy/task
+      - /api/deploy/taskReport
+      - /ws/**
+      - /ws1/**
+      - /host/**
+      - /host1/**

+ 56 - 0
project_data/config/nacos/soc-job-dev.yml

@@ -0,0 +1,56 @@
+# spring配置
+spring:
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://10.87.21.158:3306/quartz?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+    username: root
+    password: XM_zm2019
+
+# mybatis配置
+mybatis-plus:
+  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级
+  # 例如 com.**.**.mapper
+  mapperPackage: com.xunmei.**.mapper
+  # 对应的 XML 文件位置
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 实体扫描,多个package用逗号或者分号分隔
+  typeAliasesPackage: com.xunmei.**.domain
+  # 启动时是否检查 MyBatis XML 文件的存在,默认不检查
+  checkConfigLocation: false
+  configuration:
+    # 自动驼峰命名规则(camel case)映射
+    mapUnderscoreToCamelCase: true
+    # MyBatis 自动映射策略
+    # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
+    autoMappingBehavior: PARTIAL
+    # MyBatis 自动映射时未知列或未知属性处理策
+    # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
+    autoMappingUnknownColumnBehavior: NONE
+    # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
+    # 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
+    # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
+    #logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+    logImpl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    # 是否打印 Logo banner
+    banner: true
+    dbConfig:
+      # 主键类型
+      # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
+      idType: ASSIGN_ID
+      # 逻辑已删除值
+      logicDeleteValue: 2
+      # 逻辑未删除值
+      logicNotDeleteValue: 0
+      # 字段验证策略之 insert,在 insert 的时候的字段验证策略
+      # IGNORED 忽略 NOT_NULL 非NULL NOT_EMPTY 非空 DEFAULT 默认 NEVER 不加入 SQL
+      insertStrategy: NOT_NULL
+      # 字段验证策略之 update,在 update 的时候的字段验证策略
+      updateStrategy: NOT_NULL
+      # 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
+      where-strategy: NOT_NULL
+# swagger配置
+swagger:
+  title: 定时任务接口文档
+  license:
+  licenseUrl:

+ 91 - 0
project_data/config/nacos/soc-system-dev.yml

@@ -0,0 +1,91 @@
+# spring配置
+spring:
+  datasource:
+    druid:
+      stat-view-servlet:
+        enabled: true
+        loginUsername: admin
+        loginPassword: 123456
+    dynamic:
+      druid:
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        maxWait: 60000
+        timeBetweenEvictionRunsMillis: 60000
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: true
+        testOnReturn: true
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        filters: stat,slf4j
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        # 主库数据源
+        master:
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://10.87.23.48:3306/soc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true&rewriteBatchedStatements=true&connectTimeout=30000&autoReconnect=true&failOverReadOnly=false
+          username: root
+          password: XM_zm2019
+          # 从库数据源
+          # slave:
+          # username:
+          # password:
+          # url:
+          # driver-class-name:
+
+    # mybatis配置
+    #mybatis:
+    # 搜索指定包别名
+    #typeAliasesPackage: com.xunmei.system
+    # 配置mapper的扫描,找到所有的mapper.xml映射文件
+    #mapperLocations: classpath:mapper/**/*.xml
+mybatis-plus:
+  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级
+  # 例如 com.**.**.mapper
+  mapperPackage: com.xunmei.**.mapper
+  # 对应的 XML 文件位置
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 实体扫描,多个package用逗号或者分号分隔
+  typeAliasesPackage: com.xunmei.**.domain
+  # 启动时是否检查 MyBatis XML 文件的存在,默认不检查
+  checkConfigLocation: false
+  configuration:
+    # 自动驼峰命名规则(camel case)映射
+    mapUnderscoreToCamelCase: true
+    # MyBatis 自动映射策略
+    # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
+    autoMappingBehavior: PARTIAL
+    # MyBatis 自动映射时未知列或未知属性处理策
+    # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
+    autoMappingUnknownColumnBehavior: NONE
+    # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
+    # 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
+    # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
+    #logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+    logImpl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    # 是否打印 Logo banner
+    banner: true
+    dbConfig:
+      # 主键类型
+      # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
+      idType: ASSIGN_ID
+      # 逻辑已删除值
+      logicDeleteValue: 2
+      # 逻辑未删除值
+      logicNotDeleteValue: 0
+      # 字段验证策略之 insert,在 insert 的时候的字段验证策略
+      # IGNORED 忽略 NOT_NULL 非NULL NOT_EMPTY 非空 DEFAULT 默认 NEVER 不加入 SQL
+      insertStrategy: NOT_NULL
+      # 字段验证策略之 update,在 update 的时候的字段验证策略
+      updateStrategy: NOT_NULL
+      # 字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
+      where-strategy: NOT_NULL
+# swagger配置
+swagger:
+  title: 系统模块接口文档
+  license: Powered By soc
+  licenseUrl:

+ 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