数字孪生后台
This commit is contained in:
@@ -91,4 +91,7 @@ public class MydoJob extends BaseEntity
|
|||||||
private String inOut;
|
private String inOut;
|
||||||
|
|
||||||
private String theFloor;
|
private String theFloor;
|
||||||
|
|
||||||
|
private String productId;
|
||||||
|
private String productName;
|
||||||
}
|
}
|
||||||
|
|||||||
147
ruoyi-admin/src/main/java/com/ruoyi/tcp/GetDeviceAlarm.java
Normal file
147
ruoyi-admin/src/main/java/com/ruoyi/tcp/GetDeviceAlarm.java
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package com.ruoyi.tcp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.ruoyi.common.utils.DictUtils;
|
||||||
|
import com.ruoyi.device.srgv.domain.DeviceSrgv;
|
||||||
|
import com.ruoyi.device.ssj.domain.DeviceSsj;
|
||||||
|
import com.ruoyi.device.tsj.domain.DeviceTsj;
|
||||||
|
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
|
||||||
|
import com.ruoyi.schedule.plcRead.ssj.PlcSsjRead;
|
||||||
|
import com.ruoyi.schedule.plcRead.tsj.PlcTsjRead;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class GetDeviceAlarm {
|
||||||
|
private final TcpServerManager manager;
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数:注入 TCP 管理器,用于管理 5555 端口客户端与广播
|
||||||
|
*/
|
||||||
|
public GetDeviceAlarm(TcpServerManager manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化:注册 5555 端口的客户端连接监听,客户端第一次连接时立即发送报警数据
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
manager.setClientListener("port5555", this::handleConnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端连接处理:构建报警数据并写入到 Socket 输出流
|
||||||
|
*/
|
||||||
|
private void handleConnect(Socket socket) {
|
||||||
|
try {
|
||||||
|
String payload = buildAlarmPayload();
|
||||||
|
OutputStream os = socket.getOutputStream();
|
||||||
|
os.write(((payload == null ? "" : payload) + "\n").getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.flush();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 周期发送:每 2 秒检查 5555 端口是否有连接,有则广播报警数据,无连接不发送
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 2000)
|
||||||
|
public void send() {
|
||||||
|
Integer count = manager.status().getOrDefault("port5555", 0);
|
||||||
|
if (count != null && count > 0) {
|
||||||
|
String payload = buildAlarmPayload();
|
||||||
|
manager.broadcast("port5555", payload == null ? "" : payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建报警数据:汇总四向车、输送机、提升机报警
|
||||||
|
* - 四向车:报警代码 > 1,字典类型 srgv_alarm
|
||||||
|
* - 输送机:报警代码 > 2,字典类型 ssj_ex
|
||||||
|
* - 提升机:报警代码 > 100,字典类型 tsj_alarm
|
||||||
|
* 返回 JSON 字符串格式:{"alarm":[{id,name,alarm,alarmMsg}]}
|
||||||
|
*/
|
||||||
|
private String buildAlarmPayload() {
|
||||||
|
List<AlarmDto> alarms = new ArrayList<>();
|
||||||
|
|
||||||
|
Map<String, DeviceSrgv> srgvMap = PlcSrgvRead.deviceSrgvMap;
|
||||||
|
if (srgvMap != null) {
|
||||||
|
for (DeviceSrgv d : srgvMap.values()) {
|
||||||
|
Integer code = d.getAlarm();
|
||||||
|
if (code != null && code > 1) {
|
||||||
|
AlarmDto dto = new AlarmDto();
|
||||||
|
dto.setId(d.getDeviceId() == null ? "" : String.valueOf(d.getDeviceId()));
|
||||||
|
dto.setName("四向车");
|
||||||
|
dto.setAlarm(code == null ? "" : String.valueOf(code));
|
||||||
|
dto.setAlarmMsg(DictUtils.getDictLabel("srgv_alarm", code == null ? "" : String.valueOf(code)));
|
||||||
|
alarms.add(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, DeviceSsj> ssjMap = PlcSsjRead.deviceSsjMap;
|
||||||
|
if (ssjMap != null) {
|
||||||
|
for (DeviceSsj d : ssjMap.values()) {
|
||||||
|
Integer code = d.getException();
|
||||||
|
if (code != null && code > 2) {
|
||||||
|
AlarmDto dto = new AlarmDto();
|
||||||
|
dto.setId(d.getDeviceCode() == null ? "" : d.getDeviceCode());
|
||||||
|
dto.setName("输送机");
|
||||||
|
dto.setAlarm(String.valueOf(code));
|
||||||
|
dto.setAlarmMsg(DictUtils.getDictLabel("ssj_ex", String.valueOf(code)));
|
||||||
|
alarms.add(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, DeviceTsj> tsjMap = PlcTsjRead.deviceTsjMap;
|
||||||
|
if (tsjMap != null) {
|
||||||
|
for (Map.Entry<String, DeviceTsj> entry : tsjMap.entrySet()) {
|
||||||
|
String codeId = entry.getKey();
|
||||||
|
DeviceTsj d = entry.getValue();
|
||||||
|
Integer code = d.getSbzt();
|
||||||
|
if (code != null && code > 100) {
|
||||||
|
AlarmDto dto = new AlarmDto();
|
||||||
|
dto.setId(codeId == null ? "" : codeId);
|
||||||
|
dto.setName("提升机");
|
||||||
|
dto.setAlarm(String.valueOf(code));
|
||||||
|
dto.setAlarmMsg(DictUtils.getDictLabel("tsj_alarm", String.valueOf(code)));
|
||||||
|
alarms.add(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Wrapper w = new Wrapper();
|
||||||
|
w.setAlarm(alarms);
|
||||||
|
return objectMapper.writeValueAsString(w);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "{\"alarm\":[]}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class Wrapper {
|
||||||
|
private List<AlarmDto> alarm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class AlarmDto {
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private String alarm;
|
||||||
|
private String alarmMsg;
|
||||||
|
}
|
||||||
|
}
|
||||||
141
ruoyi-admin/src/main/java/com/ruoyi/tcp/GetDeviceSrgvMap.java
Normal file
141
ruoyi-admin/src/main/java/com/ruoyi/tcp/GetDeviceSrgvMap.java
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
package com.ruoyi.tcp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.ruoyi.common.utils.DictUtils;
|
||||||
|
import com.ruoyi.device.srgv.domain.DeviceSrgv;
|
||||||
|
import com.ruoyi.job.domain.MydoJob;
|
||||||
|
import com.ruoyi.job.service.IMydoJobService;
|
||||||
|
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class GetDeviceSrgvMap {
|
||||||
|
private final TcpServerManager manager;
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
private final IMydoJobService mydoJobService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数:注入 TCP 管理器与作业服务,用于管理 2222 端口广播并根据任务号补全物料信息
|
||||||
|
*/
|
||||||
|
public GetDeviceSrgvMap(TcpServerManager manager, IMydoJobService mydoJobService) {
|
||||||
|
this.manager = manager;
|
||||||
|
this.mydoJobService = mydoJobService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化:注册 2222 端口的客户端连接监听,客户端第一次连接时立即发送 AGV 数据
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
manager.setClientListener("port2222", this::handleConnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端连接处理:构建 AGV 数据并写入到 Socket 输出流
|
||||||
|
*/
|
||||||
|
private void handleConnect(Socket socket) {
|
||||||
|
try {
|
||||||
|
String payload = buildAgvPayload();
|
||||||
|
OutputStream os = socket.getOutputStream();
|
||||||
|
os.write(((payload == null ? "" : payload) + "\n").getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.flush();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 周期发送:每 2 秒检查 2222 端口是否有连接,有则广播 AGV 数据,无连接不发送
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 2000)
|
||||||
|
public void send() {
|
||||||
|
Integer count = manager.status().getOrDefault("port2222", 0);
|
||||||
|
if (count != null && count > 0) {
|
||||||
|
String payload = buildAgvPayload();
|
||||||
|
manager.broadcast("port2222", payload == null ? "" : payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 AGV 数据:从 PLC 缓存读取四向车状态并组装 JSON
|
||||||
|
* - 计算在位与状态(基于执行阶段 workPhase)
|
||||||
|
* - 报警码与文案:使用字典 srgv_alarm
|
||||||
|
* - 当任务号有效时,查询 MydoJob 补齐托盘与物料信息
|
||||||
|
* 返回 JSON 字符串格式:{"AGV":[{...}]}
|
||||||
|
*/
|
||||||
|
private String buildAgvPayload() {
|
||||||
|
Map<String, DeviceSrgv> map = PlcSrgvRead.deviceSrgvMap;
|
||||||
|
List<AgvDto> list = new ArrayList<>();
|
||||||
|
if (map != null) {
|
||||||
|
for (DeviceSrgv d : map.values()) {
|
||||||
|
AgvDto dto = new AgvDto();
|
||||||
|
dto.setId(d.getDeviceId() == null ? "" : String.format("%04d", d.getDeviceId()));
|
||||||
|
dto.setTaskNo(d.getTaskNo() == null ? "" : String.valueOf(d.getTaskNo()));
|
||||||
|
dto.setIsThere((d.getWorkPhase() >= 3 && d.getWorkPhase() < 6 ? "1" : "0"));
|
||||||
|
Integer wp = d.getWorkPhase();
|
||||||
|
String status = "0";
|
||||||
|
if (wp != null) {
|
||||||
|
if (wp == 3) status = "1";
|
||||||
|
else if (wp == 6) status = "2";
|
||||||
|
else if (wp == 24) status = "3";
|
||||||
|
}
|
||||||
|
dto.setStatus(status);
|
||||||
|
dto.setLocation(d.getCurrSite() == null ? "" : d.getCurrSite());
|
||||||
|
dto.setAlarm(d.getAlarm() == null ? "" : String.valueOf(d.getAlarm()));
|
||||||
|
dto.setAlarmMsg(DictUtils.getDictLabel("srgv_alarm", d.getAlarm() == null ? "" : String.valueOf(d.getAlarm())));
|
||||||
|
if (d.getTaskNo() != null && d.getTaskNo() > 0) {
|
||||||
|
try {
|
||||||
|
MydoJob job = mydoJobService.selectMydoJobByTaskNo(Long.valueOf(d.getTaskNo()));
|
||||||
|
dto.setTrayCode(job == null || job.getTrayCode() == null ? "" : job.getTrayCode());
|
||||||
|
dto.setProductCode(job == null || job.getProductId() == null ? "" : job.getProductId());
|
||||||
|
dto.setProductName(job == null || job.getProductName() == null ? "" : job.getProductName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
dto.setTrayCode("");
|
||||||
|
dto.setProductCode("");
|
||||||
|
dto.setProductName("");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dto.setTrayCode("");
|
||||||
|
dto.setProductCode("");
|
||||||
|
dto.setProductName("");
|
||||||
|
}
|
||||||
|
list.add(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Wrapper w = new Wrapper();
|
||||||
|
w.setAGV(list);
|
||||||
|
return objectMapper.writeValueAsString(w);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "{\"AGV\":[]}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class Wrapper {
|
||||||
|
private List<AgvDto> AGV;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class AgvDto {
|
||||||
|
private String id;
|
||||||
|
private String taskNo;
|
||||||
|
private String isThere;
|
||||||
|
private String status;
|
||||||
|
private String location;
|
||||||
|
private String alarm;
|
||||||
|
private String alarmMsg;
|
||||||
|
private String trayCode;
|
||||||
|
private String productCode;
|
||||||
|
private String productName;
|
||||||
|
}
|
||||||
|
}
|
||||||
189
ruoyi-admin/src/main/java/com/ruoyi/tcp/GetDeviceSsjMap.java
Normal file
189
ruoyi-admin/src/main/java/com/ruoyi/tcp/GetDeviceSsjMap.java
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
package com.ruoyi.tcp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.ruoyi.common.utils.DictUtils;
|
||||||
|
import com.ruoyi.device.ssj.domain.DeviceSsj;
|
||||||
|
import com.ruoyi.job.domain.MydoJob;
|
||||||
|
import com.ruoyi.job.service.IMydoJobService;
|
||||||
|
import com.ruoyi.schedule.plcRead.ssj.PlcSsjRead;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class GetDeviceSsjMap {
|
||||||
|
private final TcpServerManager manager;
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
private final IMydoJobService mydoJobService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数:注入 TCP 管理器与作业服务,用于管理 3333 端口广播并根据任务号补全物料信息
|
||||||
|
*/
|
||||||
|
public GetDeviceSsjMap(TcpServerManager manager, IMydoJobService mydoJobService) {
|
||||||
|
this.manager = manager;
|
||||||
|
this.mydoJobService = mydoJobService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化:注册 3333 端口的客户端连接监听,客户端第一次连接时立即发送输送机数据
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
manager.setClientListener("port3333", this::handleConnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端连接处理:构建输送机数据并写入到 Socket 输出流
|
||||||
|
*/
|
||||||
|
private void handleConnect(Socket socket) {
|
||||||
|
try {
|
||||||
|
String payload = readConveyorsFromCache();
|
||||||
|
OutputStream os = socket.getOutputStream();
|
||||||
|
os.write(((payload == null ? "" : payload) + "\n").getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.flush();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 周期发送:每 2 秒检查 3333 端口是否有连接,有则广播输送机数据,无连接不发送
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 2000)
|
||||||
|
public void send() {
|
||||||
|
Integer count = manager.status().getOrDefault("port3333", 0);
|
||||||
|
if (count != null && count > 0) {
|
||||||
|
String payload = readConveyorsFromCache();
|
||||||
|
manager.broadcast("port3333", payload == null ? "" : payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 PLC 缓存读取输送机状态并组装 JSON
|
||||||
|
* - 工作模式转换为中文标签
|
||||||
|
* - 报警码与文案:使用字典 ssj_ex
|
||||||
|
* - 当任务号有效时,查询 MydoJob 补齐托盘与物料信息
|
||||||
|
* 返回 JSON 字符串格式:{"ssj":[{...}]}
|
||||||
|
*/
|
||||||
|
private String readConveyorsFromCache() {
|
||||||
|
Map<String, DeviceSsj> map = PlcSsjRead.deviceSsjMap;
|
||||||
|
List<SsjDto> list = new ArrayList<>();
|
||||||
|
if (map == null || map.isEmpty()) {
|
||||||
|
return toJson(list);
|
||||||
|
}
|
||||||
|
for (DeviceSsj d : map.values()) {
|
||||||
|
SsjDto dto = new SsjDto();
|
||||||
|
dto.setId(d.getDeviceCode());
|
||||||
|
Integer cf = d.getModel();
|
||||||
|
String mode = "0";
|
||||||
|
if (cf != null) {
|
||||||
|
if (cf == 1) {
|
||||||
|
mode = "手动";
|
||||||
|
}else if (cf == 2) {
|
||||||
|
mode = "自动";
|
||||||
|
}else {
|
||||||
|
mode = String.valueOf(cf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dto.setWorkingMode(mode);
|
||||||
|
dto.setTaskNo(String.valueOf(d.getTaskNo()));
|
||||||
|
dto.setRotationDirection(calcRotationDirection(d.getTaskNo()));
|
||||||
|
dto.setAlarm(String.valueOf(d.getException()));
|
||||||
|
dto.setAlarmMsg(DictUtils.getDictLabel("ssj_ex", String.valueOf(d.getException())));
|
||||||
|
dto.setDW(String.valueOf(d.getDw()));
|
||||||
|
dto.setKX(String.valueOf(d.getKx()));
|
||||||
|
if (d.getTaskNo() > 0) {
|
||||||
|
try {
|
||||||
|
MydoJob job = mydoJobService.selectMydoJobByTaskNo(Long.valueOf(d.getTaskNo()));
|
||||||
|
dto.setTrayCode(job == null || job.getTrayCode() == null ? "" : job.getTrayCode());
|
||||||
|
dto.setProductCode(job == null || job.getProductId() == null ? "" : job.getProductId());
|
||||||
|
dto.setProductName(job == null || job.getProductName() == null ? "" : job.getProductName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
dto.setTrayCode("");
|
||||||
|
dto.setProductCode("");
|
||||||
|
dto.setProductName("");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dto.setTrayCode("");
|
||||||
|
dto.setProductCode("");
|
||||||
|
dto.setProductName("");
|
||||||
|
}
|
||||||
|
dto.setRunning("");
|
||||||
|
list.add(dto);
|
||||||
|
}
|
||||||
|
return toJson(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算输送机旋转方向:依据作业 inOrOut 字段,>=20 返回 "2",<20 返回 "1"
|
||||||
|
*/
|
||||||
|
private String calcRotationDirection(int taskNo) {
|
||||||
|
try {
|
||||||
|
if (taskNo <= 0) return "0";
|
||||||
|
MydoJob job = mydoJobService.selectMydoJobByTaskNo((long) taskNo);
|
||||||
|
if (job == null || job.getInOrOut() == null) return "0";
|
||||||
|
int val;
|
||||||
|
try {
|
||||||
|
val = Integer.parseInt(job.getInOrOut());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
return val >= 20 ? "2" : "1";
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表对象序列化为 JSON 字符串
|
||||||
|
*/
|
||||||
|
private String toJson(List<SsjDto> list) {
|
||||||
|
try {
|
||||||
|
Wrapper w = new Wrapper();
|
||||||
|
w.setSsj(list);
|
||||||
|
return objectMapper.writeValueAsString(w);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "{\"ssj\":[]}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Wrapper {
|
||||||
|
private List<SsjDto> ssj;
|
||||||
|
/**
|
||||||
|
* 获取输送机列表
|
||||||
|
*/
|
||||||
|
public List<SsjDto> getSsj() { return ssj; }
|
||||||
|
/**
|
||||||
|
* 设置输送机列表
|
||||||
|
*/
|
||||||
|
public void setSsj(List<SsjDto> ssj) { this.ssj = ssj; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class SsjDto {
|
||||||
|
private String id;
|
||||||
|
private String workingMode;
|
||||||
|
private String taskNo;
|
||||||
|
private String rotationDirection;
|
||||||
|
private String alarm;
|
||||||
|
private String alarmMsg;
|
||||||
|
private String DW;
|
||||||
|
private String KX;
|
||||||
|
private String running;
|
||||||
|
private String trayCode;
|
||||||
|
private String productCode;
|
||||||
|
private String productName;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
151
ruoyi-admin/src/main/java/com/ruoyi/tcp/GetDeviceTsjMap.java
Normal file
151
ruoyi-admin/src/main/java/com/ruoyi/tcp/GetDeviceTsjMap.java
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package com.ruoyi.tcp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.ruoyi.common.utils.DictUtils;
|
||||||
|
import com.ruoyi.device.tsj.domain.DeviceTsj;
|
||||||
|
import com.ruoyi.job.domain.MydoJob;
|
||||||
|
import com.ruoyi.job.service.IMydoJobService;
|
||||||
|
import com.ruoyi.schedule.plcRead.tsj.PlcTsjRead;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class GetDeviceTsjMap {
|
||||||
|
private final TcpServerManager manager;
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
private final IMydoJobService mydoJobService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数:注入 TCP 管理器与作业服务,用于管理 4444 端口广播并根据任务号补全物料信息
|
||||||
|
*/
|
||||||
|
public GetDeviceTsjMap(TcpServerManager manager, IMydoJobService mydoJobService) {
|
||||||
|
this.manager = manager;
|
||||||
|
this.mydoJobService = mydoJobService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化:注册 4444 端口的客户端连接监听,客户端第一次连接时立即发送提升机数据
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
manager.setClientListener("port4444", this::handleConnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端连接处理:构建提升机数据并写入到 Socket 输出流
|
||||||
|
*/
|
||||||
|
private void handleConnect(Socket socket) {
|
||||||
|
try {
|
||||||
|
String payload = buildTsjPayload();
|
||||||
|
OutputStream os = socket.getOutputStream();
|
||||||
|
os.write(((payload == null ? "" : payload) + "\n").getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.flush();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 周期发送:每 2 秒检查 4444 端口是否有连接,有则广播提升机数据,无连接不发送
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 2000)
|
||||||
|
public void send() {
|
||||||
|
Integer count = manager.status().getOrDefault("port4444", 0);
|
||||||
|
if (count != null && count > 0) {
|
||||||
|
String payload = buildTsjPayload();
|
||||||
|
manager.broadcast("port4444", payload == null ? "" : payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建提升机数据:从 PLC 缓存读取提升机状态并组装 JSON
|
||||||
|
* - 工作模式转换为中文标签
|
||||||
|
* - 报警码与文案:使用字典 tsj_alarm
|
||||||
|
* - 当任务号有效时,查询 MydoJob 补齐托盘与物料信息
|
||||||
|
* 返回 JSON 字符串格式:{"tsj":[{...}]}
|
||||||
|
*/
|
||||||
|
private String buildTsjPayload() {
|
||||||
|
Map<String, DeviceTsj> map = PlcTsjRead.deviceTsjMap;
|
||||||
|
List<TsjDto> list = new ArrayList<>();
|
||||||
|
if (map != null) {
|
||||||
|
for (Map.Entry<String, DeviceTsj> entry : map.entrySet()) {
|
||||||
|
String code = entry.getKey();
|
||||||
|
DeviceTsj d = entry.getValue();
|
||||||
|
TsjDto dto = new TsjDto();
|
||||||
|
dto.setId(String.valueOf(d.getId()));
|
||||||
|
dto.setLayer(d.getDqcs() == null ? "" : String.valueOf(d.getDqcs()));
|
||||||
|
dto.setTaskNo(d.getRwh() == null ? "" : String.valueOf(d.getRwh()));
|
||||||
|
dto.setZxjd(d.getJdbz() == null ? "" : String.valueOf(d.getJdbz()));
|
||||||
|
Integer cf = d.getCzfs();
|
||||||
|
String mode = "0";
|
||||||
|
if (cf != null) {
|
||||||
|
if (cf == 1) {
|
||||||
|
mode = "手动";
|
||||||
|
}else if (cf == 2) {
|
||||||
|
mode = "自动";
|
||||||
|
}else {
|
||||||
|
mode = String.valueOf(cf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dto.setWorkingMode(mode);
|
||||||
|
dto.setSourceLayer(d.getYch() == null ? "" : String.valueOf(d.getYch()));
|
||||||
|
dto.setTargetLayer(d.getMbch() == null ? "" : String.valueOf(d.getMbch()));
|
||||||
|
dto.setAlarm(d.getSbzt() == null ? "" : String.valueOf(d.getSbzt()));
|
||||||
|
dto.setAlarmMsg(DictUtils.getDictLabel("tsj_alarm", d.getSbzt() == null ? "" : String.valueOf(d.getSbzt())));
|
||||||
|
if (d.getRwh() != null && d.getRwh() > 0) {
|
||||||
|
try {
|
||||||
|
MydoJob job = mydoJobService.selectMydoJobByTaskNo(Long.valueOf(d.getRwh()));
|
||||||
|
dto.setTrayCode(job == null || job.getTrayCode() == null ? "" : job.getTrayCode());
|
||||||
|
dto.setProductCode(job == null || job.getProductId() == null ? "" : job.getProductId());
|
||||||
|
dto.setProductName(job == null || job.getProductName() == null ? "" : job.getProductName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
dto.setTrayCode("");
|
||||||
|
dto.setProductCode("");
|
||||||
|
dto.setProductName("");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dto.setTrayCode("");
|
||||||
|
dto.setProductCode("");
|
||||||
|
dto.setProductName("");
|
||||||
|
}
|
||||||
|
list.add(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Wrapper w = new Wrapper();
|
||||||
|
w.setTsj(list);
|
||||||
|
return objectMapper.writeValueAsString(w);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "{\"tsj\":[]}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class Wrapper {
|
||||||
|
private List<TsjDto> tsj;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class TsjDto {
|
||||||
|
private String id;
|
||||||
|
private String taskNo;
|
||||||
|
private String zxjd;
|
||||||
|
private String workingMode;
|
||||||
|
private String sourceLayer;
|
||||||
|
private String targetLayer;
|
||||||
|
private String alarm;
|
||||||
|
private String alarmMsg;
|
||||||
|
private String trayCode;
|
||||||
|
private String productCode;
|
||||||
|
private String productName;
|
||||||
|
private String layer;
|
||||||
|
}
|
||||||
|
}
|
||||||
149
ruoyi-admin/src/main/java/com/ruoyi/tcp/TcpServer.java
Normal file
149
ruoyi-admin/src/main/java/com/ruoyi/tcp/TcpServer.java
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package com.ruoyi.tcp;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class TcpServer implements Runnable {
|
||||||
|
@Getter
|
||||||
|
private final String name;
|
||||||
|
@Getter
|
||||||
|
private final int port;
|
||||||
|
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
private final Set<Socket> clients = ConcurrentHashMap.newKeySet();
|
||||||
|
private final ExecutorService clientPool = Executors.newCachedThreadPool();
|
||||||
|
@lombok.Setter
|
||||||
|
private volatile java.util.function.Consumer<Socket> clientListener;
|
||||||
|
|
||||||
|
public TcpServer(String name, int port) {
|
||||||
|
this.name = name;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动服务器:在守护线程中运行 ServerSocket 接受客户端连接
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
if (running.compareAndSet(false, true)) {
|
||||||
|
Thread t = new Thread(this, "TcpServer-" + name + "-" + port);
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止服务器:关闭 ServerSocket 与所有客户端连接,终止客户端线程池
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
running.set(false);
|
||||||
|
try {
|
||||||
|
if (serverSocket != null) serverSocket.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
for (Socket s : clients) {
|
||||||
|
try {
|
||||||
|
s.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clients.clear();
|
||||||
|
clientPool.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前已连接的客户端数量
|
||||||
|
*/
|
||||||
|
public int clientCount() {
|
||||||
|
return clients.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 广播消息到所有已连接客户端;写入失败的客户端将被移除
|
||||||
|
*/
|
||||||
|
public void broadcast(String message) {
|
||||||
|
byte[] bytes = (message + "\n").getBytes(StandardCharsets.UTF_8);
|
||||||
|
for (Socket s : clients) {
|
||||||
|
try {
|
||||||
|
OutputStream os = s.getOutputStream();
|
||||||
|
os.write(bytes);
|
||||||
|
os.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
try {
|
||||||
|
s.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
clients.remove(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/**
|
||||||
|
* 服务器主循环:绑定端口,阻塞等待新连接并交由客户端处理线程执行
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
serverSocket = new ServerSocket(port);
|
||||||
|
log.info("TCP server {} started on {}", name, port);
|
||||||
|
while (running.get()) {
|
||||||
|
Socket socket = serverSocket.accept();
|
||||||
|
clients.add(socket);
|
||||||
|
clientPool.execute(() -> handleClient(socket));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (running.get()) {
|
||||||
|
log.error("TCP server {} failed on {} {}", name, port, e.getMessage());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单个客户端处理:
|
||||||
|
* - 调用已注册的客户端监听器进行初次处理
|
||||||
|
* - 读取客户端输入,支持 ping/pong 心跳应答
|
||||||
|
* - 连接结束时关闭资源并移除客户端
|
||||||
|
*/
|
||||||
|
private void handleClient(Socket socket) {
|
||||||
|
if (clientListener != null) {
|
||||||
|
try {
|
||||||
|
clientListener.accept(socket);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error in client listener for {} on port {}", name, port, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null && running.get()) {
|
||||||
|
if ("ping".equalsIgnoreCase(line.trim())) {
|
||||||
|
OutputStream os = socket.getOutputStream();
|
||||||
|
os.write("pong\n".getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
clients.remove(socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.ruoyi.tcp;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.annotation.PreDestroy;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class TcpServerManager {
|
||||||
|
private final TcpServerProperties properties;
|
||||||
|
private final Map<String, TcpServer> servers = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数:注入 TCP 服务器配置(启用标志与端口映射)
|
||||||
|
*/
|
||||||
|
public TcpServerManager(TcpServerProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化:按配置创建并启动各端口的 TCP 服务器
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
if (!properties.isEnabled()) return;
|
||||||
|
for (Map.Entry<String, Integer> e : properties.getChannels().entrySet()) {
|
||||||
|
TcpServer s = new TcpServer(e.getKey(), e.getValue());
|
||||||
|
servers.put(e.getKey(), s);
|
||||||
|
s.start();
|
||||||
|
}
|
||||||
|
log.info("TCP servers initialized {}", properties.getChannels());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁:停止所有 TCP 服务器并清理缓存
|
||||||
|
*/
|
||||||
|
@PreDestroy
|
||||||
|
public void destroy() {
|
||||||
|
for (TcpServer s : servers.values()) {
|
||||||
|
s.stop();
|
||||||
|
}
|
||||||
|
servers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态查询:返回每个通道当前已连接客户端数量
|
||||||
|
*/
|
||||||
|
public Map<String, Integer> status() {
|
||||||
|
Map<String, Integer> map = new HashMap<>();
|
||||||
|
for (Map.Entry<String, TcpServer> e : servers.entrySet()) {
|
||||||
|
map.put(e.getKey(), e.getValue().clientCount());
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 广播消息:向指定通道的所有客户端发送文本消息
|
||||||
|
*/
|
||||||
|
public boolean broadcast(String channel, String message) {
|
||||||
|
TcpServer s = servers.get(channel);
|
||||||
|
if (s == null) return false;
|
||||||
|
s.broadcast(message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置客户端监听器:为指定通道注册客户端连接回调
|
||||||
|
*/
|
||||||
|
public void setClientListener(String channel, java.util.function.Consumer<java.net.Socket> listener) {
|
||||||
|
TcpServer s = servers.get(channel);
|
||||||
|
if (s != null) {
|
||||||
|
s.setClientListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务器实例集合(只读视图)
|
||||||
|
*/
|
||||||
|
public Map<String, TcpServer> getServers() {
|
||||||
|
return Collections.unmodifiableMap(servers);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.ruoyi.tcp;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "tcpserver")
|
||||||
|
public class TcpServerProperties {
|
||||||
|
private boolean enabled = false;
|
||||||
|
private Map<String, Integer> channels = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取是否启用 TCP 服务器功能
|
||||||
|
*/
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否启用 TCP 服务器功能
|
||||||
|
*/
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取通道端口映射(通道名 -> 端口号)
|
||||||
|
*/
|
||||||
|
public Map<String, Integer> getChannels() {
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置通道端口映射(通道名 -> 端口号)
|
||||||
|
*/
|
||||||
|
public void setChannels(Map<String, Integer> channels) {
|
||||||
|
this.channels = channels;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package com.ruoyi.tcp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.ruoyi.common.Constant;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class Wcs2wmsgetEmptyCargoSpace {
|
||||||
|
private final TcpServerManager manager;
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数:注入 TCP 管理器,用于在 1111 端口管理客户端与数据发送
|
||||||
|
*/
|
||||||
|
public Wcs2wmsgetEmptyCargoSpace(TcpServerManager manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化:注册 1111 端口客户端监听,客户端首次连接时触发数据发送
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
manager.setClientListener("port1111", this::handleConnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端连接处理:调用 WMS 接口获取空货位数据,转换为统一 JSON 并写入到 Socket
|
||||||
|
*/
|
||||||
|
private void handleConnect(Socket socket) {
|
||||||
|
try {
|
||||||
|
String url = Constant.WMS_BASE_URL + "twin/getEmptyCargoSpace";
|
||||||
|
log.info("Fetching WMS data from {}", url);
|
||||||
|
String wmsResponse = restTemplate.getForObject(url, String.class);
|
||||||
|
|
||||||
|
JsonNode root = objectMapper.readTree(wmsResponse);
|
||||||
|
List<CargoDto> cargoList = new ArrayList<>();
|
||||||
|
|
||||||
|
// Handle if root is array or wrapped in "data" or "rows"
|
||||||
|
JsonNode arrayNode = null;
|
||||||
|
if (root.isArray()) {
|
||||||
|
arrayNode = root;
|
||||||
|
} else if (root.has("data") && root.get("data").isArray()) {
|
||||||
|
arrayNode = root.get("data");
|
||||||
|
} else if (root.has("rows") && root.get("rows").isArray()) {
|
||||||
|
arrayNode = root.get("rows");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arrayNode != null) {
|
||||||
|
for (JsonNode node : arrayNode) {
|
||||||
|
cargoList.add(mapToDto(node));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If it's a single object, maybe treat as one item?
|
||||||
|
// Or maybe the format is different. For now, log warning if empty.
|
||||||
|
log.warn("WMS response is not an array: {}", wmsResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
CargoResponse response = new CargoResponse();
|
||||||
|
response.setCargo(cargoList);
|
||||||
|
String json = objectMapper.writeValueAsString(response);
|
||||||
|
|
||||||
|
OutputStream os = socket.getOutputStream();
|
||||||
|
os.write((json + "\n").getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.flush();
|
||||||
|
log.info("Sent WMS data to client on port 1111");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to fetch WMS data or send to client", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 映射单条 WMS 返回记录为 DTO:优先取目标字段名,兼容 BasCargoSpace 字段名
|
||||||
|
*/
|
||||||
|
private CargoDto mapToDto(JsonNode node) {
|
||||||
|
CargoDto dto = new CargoDto();
|
||||||
|
// Mapping strategy: try user's target keys first, then BasCargoSpace keys
|
||||||
|
dto.setRow(getText(node, "row", "thePlatoon"));
|
||||||
|
dto.setColumn(getText(node, "column", "theColumn"));
|
||||||
|
dto.setLayer(getText(node, "layer", "theFloor"));
|
||||||
|
dto.setStatus(getText(node, "status"));
|
||||||
|
dto.setTrayCode(getText(node, "trayCode"));
|
||||||
|
dto.setProductCode(getText(node, "productCode"));
|
||||||
|
dto.setProductName(getText(node, "productName"));
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取节点文本:按给定字段名顺序查找首个存在且非空的值
|
||||||
|
*/
|
||||||
|
private String getText(JsonNode node, String... keys) {
|
||||||
|
for (String key : keys) {
|
||||||
|
if (node.has(key) && !node.get(key).isNull()) {
|
||||||
|
return node.get(key).asText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class CargoResponse {
|
||||||
|
private List<CargoDto> cargo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class CargoDto {
|
||||||
|
private String row;
|
||||||
|
private String column;
|
||||||
|
private String layer;
|
||||||
|
private String status;
|
||||||
|
private String trayCode;
|
||||||
|
private String productCode;
|
||||||
|
private String productName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.ruoyi.tcp;
|
||||||
|
|
||||||
|
import com.ruoyi.common.Constant;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class Wcs2wmsgetStockList {
|
||||||
|
private final TcpServerManager manager;
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数:注入 TCP 管理器,用于在 6666 端口管理客户端与数据发送
|
||||||
|
*/
|
||||||
|
public Wcs2wmsgetStockList(TcpServerManager manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化:注册 6666 端口客户端监听,客户端首次连接时触发库存数据发送
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
manager.setClientListener("port6666", this::handleConnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端连接处理:调用 WMS 接口获取库存列表并写入到 Socket 输出流
|
||||||
|
*/
|
||||||
|
private void handleConnect(Socket socket) {
|
||||||
|
try {
|
||||||
|
String url = Constant.WMS_BASE_URL + "twin/getStockList";
|
||||||
|
String payload = restTemplate.getForObject(url, String.class);
|
||||||
|
OutputStream os = socket.getOutputStream();
|
||||||
|
os.write(((payload == null ? "" : payload) + "\n").getBytes(StandardCharsets.UTF_8));
|
||||||
|
os.flush();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 周期发送:每 10 秒检查 6666 端口是否有连接,有则广播库存数据,无连接不发送
|
||||||
|
*/
|
||||||
|
@Scheduled(fixedRate = 10000)
|
||||||
|
public void send() {
|
||||||
|
Integer count = manager.status().getOrDefault("port6666", 0);
|
||||||
|
if (count != null && count > 0) {
|
||||||
|
String url = Constant.WMS_BASE_URL + "twin/getStockList";
|
||||||
|
String payload = restTemplate.getForObject(url, String.class);
|
||||||
|
manager.broadcast("port6666", payload == null ? "" : payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -127,3 +127,26 @@ xss:
|
|||||||
excludes: /system/notice
|
excludes: /system/notice
|
||||||
# 匹配链接
|
# 匹配链接
|
||||||
urlPatterns: /system/*,/monitor/*,/tool/*
|
urlPatterns: /system/*,/monitor/*,/tool/*
|
||||||
|
|
||||||
|
|
||||||
|
# TCP服务器配置
|
||||||
|
tcpserver:
|
||||||
|
# 是否开启TCP服务器
|
||||||
|
enabled: true
|
||||||
|
# 通道配置
|
||||||
|
channels:
|
||||||
|
status: 9000
|
||||||
|
events: 9001
|
||||||
|
alarms: 9002
|
||||||
|
# 货位与库存查询端口
|
||||||
|
port1111: 1111
|
||||||
|
# 数据分析端口
|
||||||
|
port6666: 6666
|
||||||
|
# Srgv端口
|
||||||
|
port2222: 2222
|
||||||
|
# ssj端口
|
||||||
|
port3333: 3333
|
||||||
|
# tsj端口
|
||||||
|
port4444: 4444
|
||||||
|
# 报警端口
|
||||||
|
port5555: 5555
|
||||||
@@ -23,10 +23,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="warehouseId" column="warehouse_id" />
|
<result property="warehouseId" column="warehouse_id" />
|
||||||
<result property="faultFlag" column="fault_flag" />
|
<result property="faultFlag" column="fault_flag" />
|
||||||
<result property="theFloor" column="the_floor" />
|
<result property="theFloor" column="the_floor" />
|
||||||
|
<result property="productId" column="product_id" />
|
||||||
|
<result property="productName" column="product_name" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectMydoJobVo">
|
<sql id="selectMydoJobVo">
|
||||||
select job_id, status, task_no, roadway_id, cargo_space_id, target_roadway_id, target_cargo_space_id, priority, execution_phase, in_or_out, create_time, start_time, finish_time, entrance_id, exit_id, warehouse_id, fault_flag,the_floor from mydo_job_wcs
|
select job_id, status, task_no, roadway_id, cargo_space_id, target_roadway_id, target_cargo_space_id, priority, execution_phase, in_or_out, create_time, start_time, finish_time, entrance_id, exit_id, warehouse_id, fault_flag,the_floor,product_id,product_name from mydo_job_wcs
|
||||||
</sql>
|
</sql>
|
||||||
<sql id="selectMydoJobFinishVo">
|
<sql id="selectMydoJobFinishVo">
|
||||||
select job_id, status, task_no, roadway_id, cargo_space_id, target_roadway_id, target_cargo_space_id, priority, execution_phase, in_or_out, create_time, start_time, finish_time, entrance_id, exit_id, warehouse_id, fault_flag,the_floor from mydo_job_wcs_finish
|
select job_id, status, task_no, roadway_id, cargo_space_id, target_roadway_id, target_cargo_space_id, priority, execution_phase, in_or_out, create_time, start_time, finish_time, entrance_id, exit_id, warehouse_id, fault_flag,the_floor from mydo_job_wcs_finish
|
||||||
@@ -51,6 +53,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
|
<if test="warehouseId != null and warehouseId != ''"> and warehouse_id = #{warehouseId}</if>
|
||||||
<if test="faultFlag != null and faultFlag != ''"> and fault_flag = #{faultFlag}</if>
|
<if test="faultFlag != null and faultFlag != ''"> and fault_flag = #{faultFlag}</if>
|
||||||
<if test="theFloor != null and theFloor != ''"> and the_floor = #{theFloor}</if>
|
<if test="theFloor != null and theFloor != ''"> and the_floor = #{theFloor}</if>
|
||||||
|
<if test="productId != null and productId != ''"> and product_id = #{productId}</if>
|
||||||
|
<if test="productName != null and productName != ''"> and product_name = #{productName}</if>
|
||||||
|
|
||||||
</where>
|
</where>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
Reference in New Issue
Block a user