Initial commit

Made-with: Cursor
This commit is contained in:
lyf
2026-03-12 17:51:30 +08:00
commit 79102a18fb
1507 changed files with 480854 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
astral-service/astral-core/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@@ -0,0 +1,27 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.astral</groupId>
<artifactId>astral-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>astral-core</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.astral</groupId>
<artifactId>astral-system</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-security</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,55 @@
package com.astral.core.config.mybatis;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor());
// 乐观锁插件
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
// 阻断插件
interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
return interceptor;
}
/**
* 分页插件,自动识别数据库类型
*/
public PaginationInnerInterceptor paginationInnerInterceptor() {
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 设置数据库类型
paginationInnerInterceptor.setDbType(DbType.POSTGRE_SQL);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(-1L);
return paginationInnerInterceptor;
}
/**
* 乐观锁插件
*/
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}
/**
* 如果是对全表的删除和更新操作,就会终止该操作
*/
public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
return new BlockAttackInnerInterceptor();
}
}

View File

@@ -0,0 +1,30 @@
package com.astral.core.config.mybatis;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MybatisPlusMetaobjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
this.setFieldValByName("isDeleted", 0, metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
}
/**
* 获取登录用户名
*/
private String getLoginUsername() {
return "";
}
}

View File

@@ -0,0 +1,253 @@
//package com.astral.core.config.webSocketConfig;
//
//import com.fasterxml.jackson.databind.ObjectMapper;
//
//import javax.websocket.Session;
//import java.util.Collections;
//import java.util.Iterator;
//import java.util.LinkedList;
//import java.util.List;
//import java.util.concurrent.BlockingQueue;
//import java.util.concurrent.LinkedBlockingQueue;
//import java.util.concurrent.TimeUnit;
//
//public class ChatRoomWebSocket {
//
// private static final ObjectMapper objectMapper = new ObjectMapper();
//
// // Message types
// public static final String TYPE_ERROR = "error";
// public static final String TYPE_BIM2GLTF = "bim2gltf";
// public static final String TYPE_CAD = "cad";
// public static final String TYPE_CHATROOM_JOIN = "chatroom-join";
// public static final String TYPE_CHATROOM_LEAVE = "chatroom-leave";
//
// // Message class
// public static class Message {
// private String name;
// private ResponseMessage message;
//
// public Message() {}
//
// public Message(String name, ResponseMessage message) {
// this.name = name;
// this.message = message;
// }
//
// // Getters and setters
// public String getName() {
// return name;
// }
//
// public void setName(String name) {
// this.name = name;
// }
//
// public ResponseMessage getMessage() {
// return message;
// }
//
// public void setMessage(ResponseMessage message) {
// this.message = message;
// }
// }
//
// // ResponseMessage class
// public static class ResponseMessage {
// private String type;
// private String subscriber;
// private Object data;
//
// public ResponseMessage() {}
//
// public ResponseMessage(String type, String subscriber, Object data) {
// this.type = type;
// this.subscriber = subscriber;
// this.data = data;
// }
//
// // Getters and setters
// public String getType() {
// return type;
// }
//
// public void setType(String type) {
// this.type = type;
// }
//
// public String getSubscriber() {
// return subscriber;
// }
//
// public void setSubscriber(String subscriber) {
// this.subscriber = subscriber;
// }
//
// public Object getData() {
// return data;
// }
//
// public void setData(Object data) {
// this.data = data;
// }
// }
//
// // Connection class
// public static class Connection {
// private String uname;
// private Session wsConn;
//
// public Connection(String uname, Session wsConn) {
// this.uname = uname;
// this.wsConn = wsConn;
// }
//
// public String getUname() {
// return uname;
// }
//
// public Session getWsConn() {
// return wsConn;
// }
//
// public void close() {
// try {
// if (wsConn != null && wsConn.isOpen()) {
// wsConn.close();
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// }
//
// // Channels
// private static final BlockingQueue<Connection> subscribeQueue = new LinkedBlockingQueue<>(10);
// private static final BlockingQueue<String> unsubscribeQueue = new LinkedBlockingQueue<>(10);
// private static final BlockingQueue<Message> publishQueue = new LinkedBlockingQueue<>(10);
//
// // Subscribers list
// private static final List<Connection> subscribers = Collections.synchronizedList(new LinkedList<>());
//
// static {
// // Start the chatroom thread
// new Thread(ChatRoomWebSocket::chatroom).start();
// }
//
// public static void join(Connection conn) {
// subscribeQueue.offer(conn);
// }
//
// public static void leave(String user) {
// unsubscribeQueue.offer(user);
// }
//
// public static void publish(Message message) {
// publishQueue.offer(message);
// }
//
// public static Connection getConnByUName(String uname) {
// synchronized (subscribers) {
// for (Connection conn : subscribers) {
// if (conn.getUname().equals(uname)) {
// return conn;
// }
// }
// }
// return null;
// }
//
// private static boolean isUserExist(String user) {
// synchronized (subscribers) {
// for (Connection conn : subscribers) {
// if (conn.getUname().equals(user)) {
// return true;
// }
// }
// }
// return false;
// }
//
// private static void broadcastWebSocket(Message message) {
// synchronized (subscribers) {
// Iterator<Connection> iterator = subscribers.iterator();
// while (iterator.hasNext()) {
// Connection conn = iterator.next();
// Session ws = conn.getWsConn();
// if (ws != null && ws.isOpen()) {
// try {
// ResponseMessage msg = message.getMessage();
// if (msg.getSubscriber() == null || msg.getSubscriber().isEmpty()) {
// msg.setSubscriber(message.getName());
// }
//
// String msgJson = objectMapper.writeValueAsString(msg);
// ws.getBasicRemote().sendText(msgJson);
// } catch (Exception e) {
// e.printStackTrace();
// iterator.remove();
// unsubscribeQueue.offer(conn.getUname());
// }
// }
// }
// }
// }
//
// private static void chatroom() {
// while (true) {
// try {
// Connection subConn = subscribeQueue.poll(100, TimeUnit.MILLISECONDS);
// if (subConn != null) {
// if (!isUserExist(subConn.getUname())) {
// synchronized (subscribers) {
// subscribers.add(subConn);
// }
//
// ResponseMessage msgJson = new ResponseMessage(
// TYPE_CHATROOM_JOIN,
// subConn.getUname(),
// subConn.getUname() + " has joined the room."
// );
// publishQueue.offer(new Message(subConn.getUname(), msgJson));
// System.out.println("User {} join the room." + subConn.getUname());
// }
// }
//
// // Check publish queue
// Message message = publishQueue.poll(100, TimeUnit.MILLISECONDS);
// if (message != null) {
// broadcastWebSocket(message);
// }
//
// // Check unsubscribe queue
// String unsub = unsubscribeQueue.poll(100, TimeUnit.MILLISECONDS);
// if (unsub != null) {
// synchronized (subscribers) {
// Iterator<Connection> iterator = subscribers.iterator();
// while (iterator.hasNext()) {
// Connection conn = iterator.next();
// if (conn.getUname().equals(unsub)) {
// iterator.remove();
// conn.close();
//
// // Broadcast leave message
// ResponseMessage msgJson = new ResponseMessage(
// TYPE_CHATROOM_LEAVE,
// unsub,
// unsub + " exit the room."
// );
// publishQueue.offer(new Message(unsub, msgJson));
// break;
// }
// }
// }
// }
// } catch (InterruptedException e) {
// e.printStackTrace();
// Thread.currentThread().interrupt();
// break;
// }
// }
// }
//
//}

View File

@@ -0,0 +1,54 @@
//package com.astral.core.config.webSocketConfig;
//
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.beans.factory.annotation.Value;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.stereotype.Component;
//
//import javax.annotation.PostConstruct;
//
//@Component
//public class RevitWebSocketConfig {
//
// // 从配置文件中读取Revit的地址、端口、路径和超时时间
// @Value("${revit.address}")
// private String address;
//
// @Value("${revit.port}")
// private String port;
//
// @Value("${revit.path}")
// private String path;
//
// @Value("${revit.timeout}")
// private int timeout;
//
// private RevitWsClient revitWs;
//
// public RevitWsClient getRevitWs() {
// return revitWs;
// }
//
// public RevitWsClient createRevitWs() {
// // 启动WebSocket连接Revit
// RevitWsClient revitWs = new RevitWsClient(address, port, path, timeout);
// revitWs.start();
//
// // 设置8小时自动重连以毫秒为单位
// long reconnectInterval = 8 * 60 * 60 * 1000; // 8小时
// new Thread(() -> {
// while (true) {
// try {
// Thread.sleep(reconnectInterval);
// if (!revitWs.isConnected()) {
// revitWs.reconnect();
// }
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }).start();
// return revitWs;
// }
//
//}

View File

@@ -0,0 +1,180 @@
package com.astral.core.config.webSocketConfig;
import com.astral.common.utils.CommonUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.websocket.*;
import java.io.IOException;
import java.net.URI;
import java.util.Objects;
import java.util.concurrent.*;
@Component
@ClientEndpoint
public class RevitWsClient {
private Session session;
private String address;
private String port;
private String path;
private int timeout;
// 使用BlockingQueue作为消息队列
private final BlockingQueue<String> sendMsgQueue = new LinkedBlockingQueue<>();
private final BlockingQueue<String> recvMsgQueue = new LinkedBlockingQueue<>();
private WebSocketContainer container;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public RevitWsClient() {
}
public RevitWsClient(String address, String port, String path, int timeout) {
this.address = address;
this.port = port;
this.path = path;
this.timeout = timeout;
}
public void start() {
try {
URI uri = new URI("ws://" + address + ":" + port + path);
container = ContainerProvider.getWebSocketContainer();
container.setDefaultMaxSessionIdleTimeout(timeout);
session = container.connectToServer(this, uri);
} catch (Exception e) {
e.printStackTrace();
}
}
private void startHeartbeat() {
scheduler.scheduleAtFixedRate(() -> {
try {
if (session != null && session.isOpen()) {
session.getBasicRemote().sendText("ping");
}
} catch (IOException e) {
e.printStackTrace();
}
}, 0, 30, TimeUnit.SECONDS); // 每 30 秒发送一次心跳
}
public void reconnect() {
if (session != null && session.isOpen()) {
try {
session.close();
} catch (Exception e) {
e.printStackTrace();
}
}
start();
}
public boolean isConnected() {
return session != null && session.isOpen();
}
@OnOpen
public void onOpen(Session session) {
this.session = session;
System.out.println("Connected to Revit WebSocket server");
startHeartbeat();
}
@OnMessage
public void onMessage(String message) {
System.out.println("Received message from Revit: " + message);
try {
if(Objects.equals(message, "pong")) return;
if(CommonUtils.isJsonValid(message)){
recvMsgQueue.put(message);
}
} catch (InterruptedException e) {
System.err.println("Failed to save message: " + e.getMessage());
}
}
@OnClose
public void onClose(Session session, CloseReason closeReason) {
System.out.println("Disconnected from Revit WebSocket server: " + closeReason);
// this.session = null;
reconnect();
}
@OnError
public void onError(Session session, Throwable throwable) {
System.out.println("WebSocket error: " + throwable.getMessage());
}
/**
* 服务器端推送消息
*/
public void sendMsg(String message) {
try {
// 将消息放入队列
sendMsgQueue.put(message);
startMessageSender();
} catch (InterruptedException e) {
// 处理中断异常
Thread.currentThread().interrupt();
System.err.println("Failed to send message: " + e.getMessage());
}
}
/**
* 服务器端推送消息
*/
public void pushMessage(String message) {
if (session != null && session.isOpen()) {
try {
synchronized (session){
session.getBasicRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 启动一个线程从队列中取出消息并发送
*/
public void startMessageSender() {
Thread senderThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
// 从队列中取出消息
String msg = sendMsgQueue.take();
pushMessage(msg);
} catch (InterruptedException e) {
// 处理中断异常
Thread.currentThread().interrupt();
System.err.println("Message sender thread interrupted: " + e.getMessage());
}
}
});
senderThread.setDaemon(true); // 设置为守护线程
senderThread.start();
}
public String readMsg() {
try {
// 从队列中取出消息,如果队列为空则阻塞
return recvMsgQueue.take();
} catch (InterruptedException e) {
// 处理中断异常
Thread.currentThread().interrupt();
System.err.println("Failed to read message: " + e.getMessage());
return "";
}
}
}

View File

@@ -0,0 +1,151 @@
package com.astral.core.config.webSocketConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.*;
@Component
@Slf4j
//@ServerEndpoint("/websocket")
@ServerEndpoint("/sys/ws")
public class WebSocket {
private Session session;
/**
* 用户ID
*/
private String userId;
/**
* 缓存 webSocket连接到单机服务class中整体方案支持集群
*/
private static CopyOnWriteArraySet<WebSocket> webSockets = new CopyOnWriteArraySet<>();
/**
* 线程安全Map
*/
private static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session) {
try {
String userId = session.getRequestParameterMap().get("uname").get(0);
this.session = session;
this.userId = userId;
webSockets.add(this);
sessionPool.put(userId, session);
log.info("【websocket消息】有新的连接总数为:" + webSockets.size());
} catch (Exception e) {
e.printStackTrace();
}
}
@OnMessage
public void onMessage(Session session, ByteBuffer byteBuffer) {
System.out.println("收到Ping消息");
if (byteBuffer.remaining() == 1 && byteBuffer.get(0) == (byte) 0x9) {
try {
byteBuffer.rewind();
session.getBasicRemote().sendBinary(ByteBuffer.wrap(new byte[]{0xA}));
// session.getBasicRemote().sendText("pong");
} catch (IOException e) {
e.printStackTrace();
}
}
}
@OnClose
public void onClose() {
try {
webSockets.remove(this);
sessionPool.remove(this.userId);
log.info("【websocket消息】连接断开总数为:" + webSockets.size());
} catch (Exception e) {
e.printStackTrace();
}
}
@OnError
public void onError(Session session, Throwable t) {
log.info("【websocket消息】出现未知错误 ");
t.printStackTrace();
}
/**
* 服务端推送消息
*
* @param userId
* @param message
*/
public void pushMessage(String userId, String message) {
if (StringUtils.isEmpty(userId)) {
return;
}
Session session = sessionPool.get(userId);
if (session != null && session.isOpen()) {
try {
synchronized (session){
log.info("【websocket消息】 单点消息:" + message);
session.getBasicRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 服务器端推送消息
*/
public void pushMessage(String message) {
try {
webSockets.forEach(ws -> ws.session.getAsyncRemote().sendText(message));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 后台发送消息到redis
*
* @param message
*/
public void sendMessage(String message) {
this.pushMessage(message);
}
/**
* 此为单点消息
*
* @param userId
* @param message
*/
public void sendMessage(String userId, String message) {
log.info("【websocket消息】单点消息用户:" + userId + "内容:" + message);
this.pushMessage(userId, message);
}
/**
* 此为单点消息(多人)
*
* @param userIds
* @param message
*/
public void sendMessage(String[] userIds, String message) {
for (String userId : userIds) {
sendMessage(userId, message);
}
}
}

View File

@@ -0,0 +1,40 @@
package com.astral.core.config.webSocketConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
//
//@Configuration
//@EnableWebSocketMessageBroker
//public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
//
// @Override
// public void configureMessageBroker(MessageBrokerRegistry config) {
// // 配置消息代理
// // 客户端订阅的前缀
// config.enableSimpleBroker("/topic");
// // 客户端发送消息的前缀
// config.setApplicationDestinationPrefixes("/app");
// }
//
// @Override
// public void registerStompEndpoints(StompEndpointRegistry registry) {
// // 注册一个WebSocket端点客户端将使用它连接到WebSocket服务器
// registry.addEndpoint("/sys/ws").withSockJS(); // 支持SockJS
// }
//
//}
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}