依赖
xml
复制代码
<!-- WebSocket 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
配置文件
java
复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
代码
java
复制代码
import com.alibaba.fastjson.JSONObject;
import com.shengun.dao.entity.DevicePosition;
import com.shengun.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
@ServerEndpoint("/twin/ws")
public class DigitalTwinWebSocket {
@Autowired
private RedisUtil redisUtil;
// 在线会话
private static final Map<String, Session> SESSION_MAP = new ConcurrentHashMap<>();
// 最新位置缓存(实时展示用)
public static final Map<String, DevicePosition> POSITION_CACHE = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session) {
SESSION_MAP.put(session.getId(), session);
System.out.println("孪生前端连接成功:" + session.getId());
}
@OnMessage
public void onMessage(String message, Session session) {
try {
// 前端传过来的位置JSON
DevicePosition position = JSONObject.parseObject(message, DevicePosition.class);
position.setUpdateTime(LocalDateTime.now());
// 1. 缓存最新位置(实时展示)
POSITION_CACHE.put(position.getObjId(), position);
// 2. 异步存库(不阻塞实时推送)
saveToDB(position);
// 3. 广播给所有孪生大屏(多端同步)
broadcast(message);
} catch (Exception e) {
System.err.println("位置解析失败");
}
}
@OnClose
public void onClose(Session session) {
SESSION_MAP.remove(session.getId());
}
@OnError
public void onError(Session session, Throwable error) {
SESSION_MAP.remove(session.getId());
}
// 广播消息
private void broadcast(String message) {
for (Session session : SESSION_MAP.values()) {
try {
session.getBasicRemote().sendText(message);
} catch (Exception ignored) {}
}
}
// 存库(可替换 MySQL/Redis/时序库)
private void saveToDB(DevicePosition position) {
System.out.println("物体:" + position.getObjId()
+ " X:" + position.getX()
+ " Y:" + position.getY());
// 你自己写 insert 逻辑
redisUtil.set("position", JSONObject.toJSONString(position));
}
}
实体类
java
复制代码
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class DevicePosition {
// 物体ID(车辆/设备/人物)
private String objId;
// 孪生场景坐标
private double x;
private double y;
private double z;
// 旋转角度(可选)
private double rotateY;
// 更新时间
private LocalDateTime updateTime;
}
前端调用,循环调用
html
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<body>
</body>
<script>
let ws;
const objId = "car_001"; // 你的物体ID
// 连接 WebSocket
function connect() {
ws = new WebSocket("ws://localhost:8080/twin/ws");
ws.onopen = function () {
console.log("孪生连接成功");
};
// 接收后端广播的位置(多端实时同步)
ws.onmessage = function (evt) {
const pos = JSON.parse(evt.data);
console.log("实时位置:", pos);
// ========== 数字孪生引擎更新物体位置 ==========
// threejs / 虚幻 / 大屏组件 在这里更新坐标
// model.position.set(pos.x, pos.y, pos.z);
// model.rotation.y = pos.rotateY;
};
}
// 发送位置给后端(物体移动时调用)
function sendPosition(x, y, z, rotateY) {
const data = {
objId: objId,
x: x,
y: y,
z: z,
rotateY: rotateY
};
ws.send(JSON.stringify(data));
}
// 模拟物体移动:每100ms推送一次(丝滑流畅)
let x = 0;
setInterval(() => {
x += 0.1;
sendPosition(x, 10, 0, 0);
}, 100);
connect();
</script>
</html>