fix:用户相关调整、添加websocket模块

master
xiwa 2023-10-15 09:19:54 +08:00
parent edd7aed56b
commit 4d55ff956f
38 changed files with 700 additions and 180 deletions

View File

@ -26,6 +26,11 @@
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<!-- JSON工具类 --> <!-- JSON工具类 -->
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>

View File

@ -57,6 +57,11 @@ public interface UserConstants {
*/ */
String DICT_NORMAL = "0"; String DICT_NORMAL = "0";
/**
*
*/
String DICT_ABNORMAL = "1";
/** /**
* *
*/ */

View File

@ -110,8 +110,16 @@ public enum ErrCode implements IEnum {
RECORD_NOT_FOUND(50000032, "记录不存在"), RECORD_NOT_FOUND(50000032, "记录不存在"),
ADD_PLATFORM_USER_ERROR(50000033, "添加平台用户失败"), ADD_PLATFORM_USER_ERROR(50000033, "添加平台用户失败"),
UPLOAD_FILE_ERROR(50000034, "上传文件失败"), 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 int code;
private String message; private String message;

View File

@ -1,4 +1,4 @@
package cc.iotkit.common.undefined; package cc.iotkit.common.model;
import cc.iotkit.common.utils.StringUtils; import cc.iotkit.common.utils.StringUtils;
import lombok.Data; import lombok.Data;

View File

@ -1,4 +1,4 @@
package cc.iotkit.common.undefined; package cc.iotkit.common.model;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;

View File

@ -1,4 +1,4 @@
package cc.iotkit.common.undefined; package cc.iotkit.common.model;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;

View File

View 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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -46,7 +46,7 @@ public class SaTokenConfig implements WebMvcConfigurer {
List<String> swaggerUrls = List.of("/doc.html","/favicon.ico", "/webjars/**", "/resources/**" List<String> swaggerUrls = List.of("/doc.html","/favicon.ico", "/webjars/**", "/resources/**"
, "/swagger-resources/**", "/swagger-ui.html/**"); , "/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> openApiUrls = List.of( "/openapi/v1/getToken");
List<String> excludeUrls = new ArrayList<>(); List<String> excludeUrls = new ArrayList<>();

View File

@ -1,8 +1,8 @@
package cc.iotkit.common.satoken.core.service; package cc.iotkit.common.satoken.core.service;
import cc.iotkit.common.enums.UserType; import cc.iotkit.common.enums.UserType;
import cc.iotkit.common.model.LoginUser;
import cc.iotkit.common.satoken.utils.LoginHelper; import cc.iotkit.common.satoken.utils.LoginHelper;
import cc.iotkit.common.undefined.LoginUser;
import cn.dev33.satoken.stp.StpInterface; import cn.dev33.satoken.stp.StpInterface;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -2,10 +2,10 @@ package cc.iotkit.common.satoken.listener;
import cc.iotkit.common.constant.CacheConstants; import cc.iotkit.common.constant.CacheConstants;
import cc.iotkit.common.enums.UserType; 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.redis.utils.RedisUtils;
import cc.iotkit.common.satoken.utils.LoginHelper; 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.utils.ip.AddressUtils;
import cc.iotkit.common.web.utils.ServletUtils; import cc.iotkit.common.web.utils.ServletUtils;
import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.config.SaTokenConfig;

View File

@ -4,10 +4,10 @@ import cc.iotkit.common.constant.TenantConstants;
import cc.iotkit.common.constant.UserConstants; import cc.iotkit.common.constant.UserConstants;
import cc.iotkit.common.enums.DeviceType; import cc.iotkit.common.enums.DeviceType;
import cc.iotkit.common.enums.UserType; 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.SaHolder;
import cn.dev33.satoken.context.model.SaStorage; 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.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
@ -15,7 +15,6 @@ import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
import java.util.Set; import java.util.Set;
/** /**
@ -72,11 +71,17 @@ public class LoginHelper {
* () * ()
*/ */
public static LoginUser getLoginUser() { public static LoginUser getLoginUser() {
LoginUser loginUser = (LoginUser) SaHolder.getStorage().get(LOGIN_USER_KEY); LoginUser loginUser = null;
if (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; return loginUser;
} }
loginUser = (LoginUser) StpUtil.getTokenSession().get(LOGIN_USER_KEY);
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser); SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
return loginUser; return loginUser;
} }

View File

@ -2,8 +2,8 @@ package cc.iotkit.common.tenant.entiry;
import cc.iotkit.common.tenant.dao.TenantAware; import cc.iotkit.common.tenant.dao.TenantAware;
import cc.iotkit.common.tenant.listener.TenantListener; import cc.iotkit.common.tenant.listener.TenantListener;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Filter; import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef; import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef; import org.hibernate.annotations.ParamDef;
@ -29,6 +29,7 @@ import java.util.Date;
@MappedSuperclass @MappedSuperclass
@Data @Data
@NoArgsConstructor
@FilterDef(name = "tenantFilter", parameters = {@ParamDef(name = "tenantId", type = "string")}) @FilterDef(name = "tenantFilter", parameters = {@ParamDef(name = "tenantId", type = "string")})
@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId") @Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
@EntityListeners(TenantListener.class) @EntityListeners(TenantListener.class)
@ -38,7 +39,6 @@ public abstract class BaseTenantEntity implements TenantAware, Serializable {
@Id @Id
private Long id; private Long id;
@Size(max = 30)
@Column(name = "tenant_id") @Column(name = "tenant_id")
private String tenantId; private String tenantId;

View File

@ -3,9 +3,11 @@ package cc.iotkit.common.tenant.helper;
import cc.iotkit.common.constant.GlobalConstants; import cc.iotkit.common.constant.GlobalConstants;
import cc.iotkit.common.redis.utils.RedisUtils; import cc.iotkit.common.redis.utils.RedisUtils;
import cc.iotkit.common.satoken.utils.LoginHelper; import cc.iotkit.common.satoken.utils.LoginHelper;
import cc.iotkit.common.utils.SpringUtils;
import cc.iotkit.common.utils.StringUtils; import cc.iotkit.common.utils.StringUtils;
import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.spring.SpringMVCUtil; import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.hutool.core.convert.Convert;
import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -24,6 +26,14 @@ public class TenantHelper {
private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>(); private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>();
/**
*
*/
public static boolean isEnable() {
return Convert.toBool(SpringUtils.getProperty("tenant.enable"), false);
}
/** /**
* ( ) * ( )
* <p> * <p>

View File

@ -1,6 +1,5 @@
package cc.iotkit.common.service; package cc.iotkit.common.thing;
import cc.iotkit.common.thing.ThingService;
/** /**
* *

View File

@ -11,6 +11,36 @@
<artifactId>iot-common-websocket</artifactId> <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> <build>
<plugins> <plugins>
<plugin> <plugin>

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -0,0 +1,28 @@
package cc.iotkit.common.websocket.constant;
/**
* websocket
*
* @author zendwang
*/
public interface WebSocketConstants {
/**
* websocketSessionkey
*/
String LOGIN_USER_KEY = "loginUser";
/**
*
*/
String WEB_SOCKET_TOPIC = "global:websocket";
/**
*
*/
String PING = "ping";
/**
*
*/
String PONG = "pong";
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1 @@
cc.iotkit.common.websocket.config.WebSocketConfig

View File

@ -55,19 +55,13 @@ public class JavaScriptEngine implements IScriptEngine {
public <T> T invokeMethod(TypeReference<T> type, String methodName, Object... args) { public <T> T invokeMethod(TypeReference<T> type, String methodName, Object... args) {
Value member = jsScript.getMember("invoke"); Value member = jsScript.getMember("invoke");
StringBuilder sbArgs = new StringBuilder("["); StringBuilder sbArgs = formatArgs(args);
//将入参转成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("]");
//通过调用invoke方法将目标方法返回结果转成json //通过调用invoke方法将目标方法返回结果转成json
Value rst = member.execute(methodName, args); Value rst = member.execute(methodName, args);
String json = rst.asString(); 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)) { if (json == null || "null".equals(json)) {
@ -77,4 +71,15 @@ public class JavaScriptEngine implements IScriptEngine {
return JsonUtils.parseObject(json, type); 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;
}
} }

View File

@ -265,6 +265,12 @@
<version>1.12.400</version> <version>1.12.400</version>
</dependency> </dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.57</version>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>