fix:用户相关调整、添加websocket模块
parent
edd7aed56b
commit
4d55ff956f
|
@ -26,6 +26,11 @@
|
|||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON工具类 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
|
|
|
@ -57,6 +57,11 @@ public interface UserConstants {
|
|||
*/
|
||||
String DICT_NORMAL = "0";
|
||||
|
||||
/**
|
||||
* 字典停用状态
|
||||
*/
|
||||
String DICT_ABNORMAL = "1";
|
||||
|
||||
/**
|
||||
* 是否为系统默认(是)
|
||||
*/
|
||||
|
|
|
@ -110,8 +110,16 @@ public enum ErrCode implements IEnum {
|
|||
RECORD_NOT_FOUND(50000032, "记录不存在"),
|
||||
ADD_PLATFORM_USER_ERROR(50000033, "添加平台用户失败"),
|
||||
UPLOAD_FILE_ERROR(50000034, "上传文件失败"),
|
||||
FILE_NAME_IS_NULL(50000035, "文件名为空,获取文件名失败");
|
||||
FILE_NAME_IS_NULL(50000035, "文件名为空,获取文件名失败"),
|
||||
PRODUCT_KEY_EXIST(50000036, "ProductKey已存在"),
|
||||
DATA_ALREADY(50000037, "记录已存在"),
|
||||
|
||||
TENANT_NOT_FOUND(50001001, "租户不存在"),
|
||||
TENANT_DISABLE(50001002, "租户已禁用"),
|
||||
TENANT_EXPIRE(50001003, "租户已过期"),
|
||||
UNAUTHORIZED_TENANT(50001004, "不允许操作管理租户"),
|
||||
PACKAGE_NOT_FOUND(50001005, "套餐不存在"),
|
||||
;
|
||||
|
||||
private int code;
|
||||
private String message;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package cc.iotkit.common.undefined;
|
||||
package cc.iotkit.common.model;
|
||||
|
||||
import cc.iotkit.common.utils.StringUtils;
|
||||
import lombok.Data;
|
|
@ -1,4 +1,4 @@
|
|||
package cc.iotkit.common.undefined;
|
||||
package cc.iotkit.common.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
|
@ -1,4 +1,4 @@
|
|||
package cc.iotkit.common.undefined;
|
||||
package cc.iotkit.common.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
0
iot-common-core/src/main/java/cc/iotkit/common/service/ConfigService.java
Executable file → Normal file
0
iot-common-core/src/main/java/cc/iotkit/common/service/ConfigService.java
Executable file → Normal file
0
iot-common-core/src/main/java/cc/iotkit/common/service/DeptService.java
Executable file → Normal file
0
iot-common-core/src/main/java/cc/iotkit/common/service/DeptService.java
Executable file → Normal file
0
iot-common-core/src/main/java/cc/iotkit/common/service/DictService.java
Executable file → Normal file
0
iot-common-core/src/main/java/cc/iotkit/common/service/DictService.java
Executable file → Normal file
0
iot-common-core/src/main/java/cc/iotkit/common/service/OssService.java
Executable file → Normal file
0
iot-common-core/src/main/java/cc/iotkit/common/service/OssService.java
Executable file → Normal file
0
iot-common-core/src/main/java/cc/iotkit/common/service/UserService.java
Executable file → Normal file
0
iot-common-core/src/main/java/cc/iotkit/common/service/UserService.java
Executable file → Normal file
|
@ -1,35 +0,0 @@
|
|||
package cc.iotkit.common.undefined;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 短信登录对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
|
||||
@Data
|
||||
public class EmailLoginBody {
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
@NotBlank(message = "{tenant.number.not.blank}")
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@NotBlank(message = "{user.email.not.blank}")
|
||||
@Email(message = "{user.email.not.valid}")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 邮箱code
|
||||
*/
|
||||
@NotBlank(message = "{email.code.not.blank}")
|
||||
private String emailCode;
|
||||
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package cc.iotkit.common.undefined;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户登录对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
|
||||
@Data
|
||||
public class LoginBody {
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
@NotBlank(message = "{tenant.number.not.blank}")
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "{user.username.not.blank}")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 用户密码
|
||||
*/
|
||||
@NotBlank(message = "{user.password.not.blank}")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 唯一标识
|
||||
*/
|
||||
private String uuid;
|
||||
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package cc.iotkit.common.undefined;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 用户注册对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class RegisterBody extends LoginBody {
|
||||
|
||||
private String userType;
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package cc.iotkit.common.undefined;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 短信登录对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
|
||||
@Data
|
||||
public class SmsLoginBody {
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
@NotBlank(message = "{tenant.number.not.blank}")
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@NotBlank(message = "{user.phonenumber.not.blank}")
|
||||
private String phonenumber;
|
||||
|
||||
/**
|
||||
* 短信code
|
||||
*/
|
||||
@NotBlank(message = "{sms.code.not.blank}")
|
||||
private String smsCode;
|
||||
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package cc.iotkit.common.undefined;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
/**
|
||||
* 小程序登录用户身份权限
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@NoArgsConstructor
|
||||
public class XcxLoginUser extends LoginUser {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* openid
|
||||
*/
|
||||
private String openid;
|
||||
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* +----------------------------------------------------------------------
|
||||
* | Copyright (c) 奇特物联 2021-2022 All rights reserved.
|
||||
* +----------------------------------------------------------------------
|
||||
* | Licensed 未经许可不能去掉「奇特物联」相关版权
|
||||
* +----------------------------------------------------------------------
|
||||
* | Author: xw2sy@163.com
|
||||
* +----------------------------------------------------------------------
|
||||
*/
|
||||
package cc.iotkit.common.utils;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 微信小程序工具类
|
||||
*/
|
||||
@Slf4j
|
||||
public class WeChatUtil {
|
||||
|
||||
public static String httpRequest(String requestUrl, String requestMethod, String output) {
|
||||
try {
|
||||
URL url = new URL(requestUrl);
|
||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||
connection.setDoOutput(true);
|
||||
connection.setDoInput(true);
|
||||
connection.setUseCaches(false);
|
||||
connection.setRequestMethod(requestMethod);
|
||||
if (null != output) {
|
||||
OutputStream outputStream = connection.getOutputStream();
|
||||
outputStream.write(output.getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.close();
|
||||
}
|
||||
// 从输入流读取返回内容
|
||||
InputStream inputStream = connection.getInputStream();
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
|
||||
String str;
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
while ((str = bufferedReader.readLine()) != null) {
|
||||
buffer.append(str);
|
||||
}
|
||||
bufferedReader.close();
|
||||
inputStreamReader.close();
|
||||
inputStream.close();
|
||||
connection.disconnect();
|
||||
return buffer.toString();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String decryptData(String encryptDataB64, String sessionKeyB64, String ivB64) {
|
||||
return new String(
|
||||
decryptOfDiyIv(
|
||||
Base64.decode(encryptDataB64),
|
||||
Base64.decode(sessionKeyB64),
|
||||
Base64.decode(ivB64)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static final String KEY_ALGORITHM = "AES";
|
||||
private static final String ALGORITHM_STR = "AES/CBC/PKCS7Padding";
|
||||
private static Key key;
|
||||
private static Cipher cipher;
|
||||
|
||||
private static void init(byte[] keyBytes) {
|
||||
// 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
|
||||
int base = 16;
|
||||
if (keyBytes.length % base != 0) {
|
||||
int groups = keyBytes.length / base + 1;
|
||||
byte[] temp = new byte[groups * base];
|
||||
Arrays.fill(temp, (byte) 0);
|
||||
System.arraycopy(keyBytes, 0, temp, 0, keyBytes.length);
|
||||
keyBytes = temp;
|
||||
}
|
||||
// 初始化
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
// 转化成JAVA的密钥格式
|
||||
key = new SecretKeySpec(keyBytes, KEY_ALGORITHM);
|
||||
try {
|
||||
// 初始化cipher
|
||||
cipher = Cipher.getInstance(ALGORITHM_STR, "BC");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密方法
|
||||
*
|
||||
* @param encryptedData 要解密的字符串
|
||||
* @param keyBytes 解密密钥
|
||||
* @param ivs 自定义对称解密算法初始向量 iv
|
||||
* @return 解密后的字节数组
|
||||
*/
|
||||
private static byte[] decryptOfDiyIv(byte[] encryptedData, byte[] keyBytes, byte[] ivs) {
|
||||
byte[] encryptedText = null;
|
||||
init(keyBytes);
|
||||
try {
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivs));
|
||||
encryptedText = cipher.doFinal(encryptedData);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return encryptedText;
|
||||
}
|
||||
|
||||
}
|
|
@ -46,7 +46,7 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
|||
List<String> swaggerUrls = List.of("/doc.html","/favicon.ico", "/webjars/**", "/resources/**"
|
||||
, "/swagger-resources/**", "/swagger-ui.html/**");
|
||||
|
||||
List loginUrls = List.of("/code", "/auth/tenant/list", "/auth/login");
|
||||
List loginUrls = List.of("/code", "/auth/tenant/list", "/auth/login", "/auth/logout");
|
||||
List<String> openApiUrls = List.of( "/openapi/v1/getToken");
|
||||
|
||||
List<String> excludeUrls = new ArrayList<>();
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package cc.iotkit.common.satoken.core.service;
|
||||
|
||||
import cc.iotkit.common.enums.UserType;
|
||||
import cc.iotkit.common.model.LoginUser;
|
||||
import cc.iotkit.common.satoken.utils.LoginHelper;
|
||||
import cc.iotkit.common.undefined.LoginUser;
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
|
@ -2,10 +2,10 @@ package cc.iotkit.common.satoken.listener;
|
|||
|
||||
import cc.iotkit.common.constant.CacheConstants;
|
||||
import cc.iotkit.common.enums.UserType;
|
||||
import cc.iotkit.common.model.LoginUser;
|
||||
import cc.iotkit.common.model.UserOnlineDTO;
|
||||
import cc.iotkit.common.redis.utils.RedisUtils;
|
||||
import cc.iotkit.common.satoken.utils.LoginHelper;
|
||||
import cc.iotkit.common.undefined.LoginUser;
|
||||
import cc.iotkit.common.undefined.UserOnlineDTO;
|
||||
import cc.iotkit.common.utils.ip.AddressUtils;
|
||||
import cc.iotkit.common.web.utils.ServletUtils;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
|
|
|
@ -4,10 +4,10 @@ import cc.iotkit.common.constant.TenantConstants;
|
|||
import cc.iotkit.common.constant.UserConstants;
|
||||
import cc.iotkit.common.enums.DeviceType;
|
||||
import cc.iotkit.common.enums.UserType;
|
||||
import cc.iotkit.common.undefined.LoginUser;
|
||||
import cc.iotkit.common.model.LoginUser;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.exception.InvalidContextException;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
|
@ -15,7 +15,6 @@ import lombok.AccessLevel;
|
|||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -72,11 +71,17 @@ public class LoginHelper {
|
|||
* 获取用户(多级缓存)
|
||||
*/
|
||||
public static LoginUser getLoginUser() {
|
||||
LoginUser loginUser = (LoginUser) SaHolder.getStorage().get(LOGIN_USER_KEY);
|
||||
if (loginUser != null) {
|
||||
LoginUser loginUser = null;
|
||||
Object saCache=SaHolder.getStorage().get(LOGIN_USER_KEY);
|
||||
if (saCache != null) {
|
||||
loginUser=(LoginUser) saCache;
|
||||
return loginUser;
|
||||
}
|
||||
SaSession cache=StpUtil.getTokenSession();
|
||||
if (cache != null) {
|
||||
loginUser= (LoginUser) cache.get(LOGIN_USER_KEY);
|
||||
return loginUser;
|
||||
}
|
||||
loginUser = (LoginUser) StpUtil.getTokenSession().get(LOGIN_USER_KEY);
|
||||
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
|
||||
return loginUser;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package cc.iotkit.common.tenant.entiry;
|
|||
|
||||
import cc.iotkit.common.tenant.dao.TenantAware;
|
||||
import cc.iotkit.common.tenant.listener.TenantListener;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.annotations.Filter;
|
||||
import org.hibernate.annotations.FilterDef;
|
||||
import org.hibernate.annotations.ParamDef;
|
||||
|
@ -29,6 +29,7 @@ import java.util.Date;
|
|||
|
||||
@MappedSuperclass
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@FilterDef(name = "tenantFilter", parameters = {@ParamDef(name = "tenantId", type = "string")})
|
||||
@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
|
||||
@EntityListeners(TenantListener.class)
|
||||
|
@ -38,7 +39,6 @@ public abstract class BaseTenantEntity implements TenantAware, Serializable {
|
|||
@Id
|
||||
private Long id;
|
||||
|
||||
@Size(max = 30)
|
||||
@Column(name = "tenant_id")
|
||||
private String tenantId;
|
||||
|
||||
|
|
|
@ -3,9 +3,11 @@ package cc.iotkit.common.tenant.helper;
|
|||
import cc.iotkit.common.constant.GlobalConstants;
|
||||
import cc.iotkit.common.redis.utils.RedisUtils;
|
||||
import cc.iotkit.common.satoken.utils.LoginHelper;
|
||||
import cc.iotkit.common.utils.SpringUtils;
|
||||
import cc.iotkit.common.utils.StringUtils;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.spring.SpringMVCUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
@ -24,6 +26,14 @@ public class TenantHelper {
|
|||
|
||||
private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 租户功能是否启用
|
||||
*/
|
||||
public static boolean isEnable() {
|
||||
return Convert.toBool(SpringUtils.getProperty("tenant.enable"), false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置动态租户(一直有效 需要手动清理)
|
||||
* <p>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package cc.iotkit.common.service;
|
||||
package cc.iotkit.common.thing;
|
||||
|
||||
import cc.iotkit.common.thing.ThingService;
|
||||
|
||||
/**
|
||||
* 通用设备服务
|
|
@ -11,6 +11,36 @@
|
|||
|
||||
<artifactId>iot-common-websocket</artifactId>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<artifactId>iot-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<artifactId>iot-common-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<artifactId>iot-common-satoken</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package cc.iotkit.common.websocket.config;
|
||||
|
||||
import cc.iotkit.common.websocket.config.properties.WebSocketProperties;
|
||||
import cc.iotkit.common.websocket.handler.PlusWebSocketHandler;
|
||||
import cc.iotkit.common.websocket.interceptor.PlusWebSocketInterceptor;
|
||||
import cc.iotkit.common.websocket.listener.WebSocketTopicListener;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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;
|
||||
|
||||
/**
|
||||
* WebSocket 配置
|
||||
*
|
||||
* @author zendwang
|
||||
*/
|
||||
@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)
|
||||
.setAllowedOrigins(webSocketProperties.getAllowedOrigins());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HandshakeInterceptor handshakeInterceptor() {
|
||||
return new PlusWebSocketInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketHandler webSocketHandler() {
|
||||
return new PlusWebSocketHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebSocketTopicListener topicListener() {
|
||||
return new WebSocketTopicListener();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package cc.iotkit.common.websocket.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* WebSocket 配置项
|
||||
*
|
||||
* @author zendwang
|
||||
*/
|
||||
@ConfigurationProperties("websocket")
|
||||
@Data
|
||||
public class WebSocketProperties {
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 路径
|
||||
*/
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 设置访问源地址
|
||||
*/
|
||||
private String allowedOrigins;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package cc.iotkit.common.websocket.constant;
|
||||
|
||||
/**
|
||||
* websocket的常量配置
|
||||
*
|
||||
* @author zendwang
|
||||
*/
|
||||
public interface WebSocketConstants {
|
||||
/**
|
||||
* websocketSession中的参数的key
|
||||
*/
|
||||
String LOGIN_USER_KEY = "loginUser";
|
||||
|
||||
/**
|
||||
* 订阅的频道
|
||||
*/
|
||||
String WEB_SOCKET_TOPIC = "global:websocket";
|
||||
|
||||
/**
|
||||
* 前端心跳检查的命令
|
||||
*/
|
||||
String PING = "ping";
|
||||
|
||||
/**
|
||||
* 服务端心跳恢复的字符串
|
||||
*/
|
||||
String PONG = "pong";
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package cc.iotkit.common.websocket.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 消息的dto
|
||||
*
|
||||
* @author zendwang
|
||||
*/
|
||||
@Data
|
||||
public class WebSocketMessageDto implements Serializable {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 需要推送到的session key 列表
|
||||
*/
|
||||
private List<Long> sessionKeys;
|
||||
|
||||
/**
|
||||
* 需要发送的消息
|
||||
*/
|
||||
private String message;
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package cc.iotkit.common.websocket.handler;
|
||||
|
||||
import cc.iotkit.common.model.LoginUser;
|
||||
import cc.iotkit.common.websocket.constant.WebSocketConstants;
|
||||
import cc.iotkit.common.websocket.dto.WebSocketMessageDto;
|
||||
import cc.iotkit.common.websocket.holder.WebSocketSessionHolder;
|
||||
import cc.iotkit.common.websocket.utils.WebSocketUtils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.socket.*;
|
||||
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* WebSocketHandler 实现类
|
||||
*
|
||||
* @author zendwang
|
||||
*/
|
||||
@Slf4j
|
||||
public class PlusWebSocketHandler extends AbstractWebSocketHandler {
|
||||
|
||||
/**
|
||||
* 连接成功后
|
||||
*/
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
LoginUser loginUser = (LoginUser) session.getAttributes().get(WebSocketConstants.LOGIN_USER_KEY);
|
||||
WebSocketSessionHolder.addSession(loginUser.getUserId(), session);
|
||||
log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理发送来的文本消息
|
||||
*
|
||||
* @param session
|
||||
* @param message
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
LoginUser loginUser = (LoginUser) session.getAttributes().get(WebSocketConstants.LOGIN_USER_KEY);
|
||||
log.info("PlusWebSocketHandler, 连接:" + session.getId() + ",已收到消息:" + message.getPayload());
|
||||
List<Long> userIds = List.of(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(WebSocketConstants.LOGIN_USER_KEY);
|
||||
WebSocketSessionHolder.removeSession(loginUser.getUserId());
|
||||
log.info("[disconnect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持分片消息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsPartialMessages() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package cc.iotkit.common.websocket.holder;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* WebSocketSession 用于保存当前所有在线的会话信息
|
||||
*
|
||||
* @author zendwang
|
||||
*/
|
||||
@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 Boolean existSession(Long sessionKey) {
|
||||
return USER_SESSION_MAP.containsKey(sessionKey);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package cc.iotkit.common.websocket.interceptor;
|
||||
|
||||
import cc.iotkit.common.model.LoginUser;
|
||||
import cc.iotkit.common.satoken.utils.LoginHelper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.util.Map;
|
||||
|
||||
import static cc.iotkit.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY;
|
||||
|
||||
/**
|
||||
* WebSocket握手请求的拦截器
|
||||
*
|
||||
* @author zendwang
|
||||
*/
|
||||
@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) {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package cc.iotkit.common.websocket.listener;
|
||||
|
||||
import cc.iotkit.common.websocket.utils.WebSocketUtils;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cc.iotkit.common.websocket.holder.WebSocketSessionHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* WebSocket 主题订阅监听器
|
||||
*
|
||||
* @author zendwang
|
||||
*/
|
||||
@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());
|
||||
if (CollUtil.isNotEmpty(message.getSessionKeys())) {
|
||||
message.getSessionKeys().forEach(key -> {
|
||||
if (WebSocketSessionHolder.existSession(key)) {
|
||||
WebSocketUtils.sendMessage(key, message.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
log.info("初始化WebSocket主题订阅监听器成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package cc.iotkit.common.websocket.utils;
|
||||
|
||||
import cc.iotkit.common.model.LoginUser;
|
||||
import cc.iotkit.common.redis.utils.RedisUtils;
|
||||
import cc.iotkit.common.websocket.dto.WebSocketMessageDto;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cc.iotkit.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 cc.iotkit.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY;
|
||||
import static cc.iotkit.common.websocket.constant.WebSocketConstants.WEB_SOCKET_TOPIC;
|
||||
|
||||
/**
|
||||
* 工具类
|
||||
*
|
||||
* @author zendwang
|
||||
*/
|
||||
@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());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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.error("[send] session会话已经关闭");
|
||||
} else {
|
||||
try {
|
||||
// 获取当前会话中的用户
|
||||
LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
|
||||
session.sendMessage(message);
|
||||
log.info("[send] sessionId: {},userId:{},userType:{},message:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType(), message);
|
||||
} catch (IOException e) {
|
||||
log.error("[send] session({}) 发送消息({}) 异常", session, message, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
cc.iotkit.common.websocket.config.WebSocketConfig
|
|
@ -55,19 +55,13 @@ public class JavaScriptEngine implements IScriptEngine {
|
|||
public <T> T invokeMethod(TypeReference<T> type, String methodName, Object... args) {
|
||||
Value member = jsScript.getMember("invoke");
|
||||
|
||||
StringBuilder sbArgs = new StringBuilder("[");
|
||||
//将入参转成json
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
args[i] = JsonUtils.toJsonString(args[i]);
|
||||
sbArgs.append(i == args.length - 1 ? "," : "").append(args[i]);
|
||||
}
|
||||
sbArgs.append("]");
|
||||
StringBuilder sbArgs = formatArgs(args);
|
||||
|
||||
//通过调用invoke方法将目标方法返回结果转成json
|
||||
Value rst = member.execute(methodName, args);
|
||||
|
||||
String json = rst.asString();
|
||||
log.info("invoke script {},args:{}, result:{}", methodName, sbArgs, json);
|
||||
log.info("invoke script={}, args={}, result={}", methodName, sbArgs, json);
|
||||
|
||||
//没有返回值
|
||||
if (json == null || "null".equals(json)) {
|
||||
|
@ -77,4 +71,15 @@ public class JavaScriptEngine implements IScriptEngine {
|
|||
return JsonUtils.parseObject(json, type);
|
||||
}
|
||||
|
||||
private static StringBuilder formatArgs(Object[] args) {
|
||||
StringBuilder sbArgs = new StringBuilder("[");
|
||||
//将入参转成json
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
args[i] = JsonUtils.toJsonString(args[i]);
|
||||
sbArgs.append(args[i]).append(i != args.length - 1 ? "," : "");
|
||||
}
|
||||
sbArgs.append("]");
|
||||
return sbArgs;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue