Compare commits
2 Commits
5726239eda
...
c59df81b38
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c59df81b38 | ||
|
|
66449b0482 |
@@ -0,0 +1,66 @@
|
||||
package top.wms.admin.weighManage.model.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 创建或修改任务工单信息参数
|
||||
*
|
||||
* @author zc
|
||||
* @since 2026/03/03 17:09
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建或修改任务工单信息参数")
|
||||
public class WorkOrderInfoReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 工单主键id
|
||||
*/
|
||||
@Schema(description = "工单主键id")
|
||||
private Long workOrderId;
|
||||
|
||||
/**
|
||||
* 物料主键id
|
||||
*/
|
||||
@Schema(description = "物料主键id")
|
||||
private Long materialId;
|
||||
|
||||
/**
|
||||
* 称重次数
|
||||
*/
|
||||
@Schema(description = "称重次数")
|
||||
private int weightTime;
|
||||
|
||||
/**
|
||||
* 物料数量
|
||||
*/
|
||||
@Schema(description = "物料数量")
|
||||
private BigDecimal quantity;
|
||||
|
||||
/**
|
||||
* 称重重量
|
||||
*/
|
||||
@Schema(description = "称重重量")
|
||||
private BigDecimal weight;
|
||||
|
||||
/**
|
||||
* 计算重量
|
||||
*/
|
||||
@Schema(description = "计算重量")
|
||||
private BigDecimal calculatedWeight;
|
||||
|
||||
/**
|
||||
* 抓拍的图片
|
||||
*/
|
||||
@Schema(description = "抓拍的图片")
|
||||
private String imgUrl;
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建或修改任务工单信息参数
|
||||
@@ -25,23 +26,20 @@ public class WorkOrderReq implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 总重量
|
||||
* 物料主键id
|
||||
*/
|
||||
@Schema(description = "总重量")
|
||||
@NotNull(message = "总重量不能为空")
|
||||
private BigDecimal totalWeight;
|
||||
@Schema(description = "物料主键id")
|
||||
private Long materialId;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@Schema(description = "创建人")
|
||||
@NotNull(message = "创建人不能为空")
|
||||
private Long createUser;
|
||||
private List<WorkOrderInfoReq> workOrderInfos;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(description = "创建时间")
|
||||
@NotNull(message = "创建时间不能为空")
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -23,4 +23,12 @@ public interface WorkOrderService extends BaseService<WorkOrderResp, WorkOrderRe
|
||||
* @return 任务工单详情信息
|
||||
*/
|
||||
List<WorkOrderInfoResp> getDetail(Long id);
|
||||
|
||||
/**
|
||||
* 创建任务工单
|
||||
*
|
||||
* @param req 创建任务工单参数
|
||||
* @return 任务工单信息
|
||||
*/
|
||||
WorkOrderResp addWorKerOrder(WorkOrderReq req);
|
||||
}
|
||||
@@ -50,4 +50,13 @@ public class WorkOrderServiceImpl extends BaseServiceImpl<WorkOrderMapper, WorkO
|
||||
return baseMapper.getDetail(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkOrderResp addWorKerOrder(WorkOrderReq req) {
|
||||
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -53,6 +53,16 @@
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>1.12.780</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fazecast</groupId>
|
||||
<artifactId>jSerialComm</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.websocket</groupId>
|
||||
<artifactId>jakarta.websocket-api</artifactId>
|
||||
<version>2.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<finalName>${project.parent.name}</finalName>
|
||||
|
||||
@@ -67,6 +67,19 @@
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>1.12.780</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fazecast</groupId>
|
||||
<artifactId>jSerialComm</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- pom.xml -->
|
||||
<dependency>
|
||||
<groupId>jakarta.websocket</groupId>
|
||||
<artifactId>jakarta.websocket-api</artifactId>
|
||||
<version>2.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package top.wms.admin.config.webSocket;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
import top.wms.admin.controller.weighManage.ah.ScaleWebSocketHandler;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class webSocket implements WebSocketConfigurer {
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
// 注册WebSocket端点,允许所有跨域请求
|
||||
registry.addHandler(new ScaleWebSocketHandler(), "/ws/scale").setAllowedOrigins("*");
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import java.util.List;
|
||||
@Tag(name = "任务工单信息管理 API")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@CrudRequestMapping(value = "/weighManage/workOrder", api = {Api.PAGE, Api.ADD, Api.DELETE, Api.EXPORT})
|
||||
@CrudRequestMapping(value = "/weighManage/workOrder", api = {Api.PAGE, Api.DELETE, Api.EXPORT})
|
||||
public class WorkOrderController extends BaseController<WorkOrderService, WorkOrderResp, WorkOrderResp, WorkOrderQuery, WorkOrderReq> {
|
||||
|
||||
@Log(ignore = true)
|
||||
@@ -38,4 +38,11 @@ public class WorkOrderController extends BaseController<WorkOrderService, WorkOr
|
||||
return baseService.getDetail(id);
|
||||
}
|
||||
|
||||
|
||||
@SaCheckPermission("workOrder:record:add")
|
||||
@PostMapping
|
||||
public WorkOrderResp addWorKerOrder(@RequestBody WorkOrderReq req) {
|
||||
return baseService.addWorKerOrder(req);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
package top.wms.admin.controller.weighManage.ah;
|
||||
|
||||
import com.fazecast.jSerialComm.SerialPort;
|
||||
import com.fazecast.jSerialComm.SerialPortDataListener;
|
||||
import com.fazecast.jSerialComm.SerialPortEvent;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 安衡电子秤连接类
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class AHDZCConnect {
|
||||
|
||||
private static final String PORT_NAME = "COM5";
|
||||
private static final int BAUD_RATE = 9600;
|
||||
private static final int DATA_BITS = 8;
|
||||
private static final int STOP_BITS = 1;
|
||||
private static final int TIMEOUT = 2000;
|
||||
private static final Charset SCALE_CHARSET = Charset.forName("GBK");
|
||||
|
||||
private static final long HEALTH_CHECK_INTERVAL = 5000;
|
||||
private static final long DATA_TIMEOUT = 10000;
|
||||
private static final int MAX_RECONNECT_DELAY = 30000;
|
||||
private static final int INITIAL_RECONNECT_DELAY = 1000;
|
||||
private static final int BUFFER_SIZE = 256;
|
||||
|
||||
private volatile SerialPort serialPort;
|
||||
private final AtomicBoolean isConnecting = new AtomicBoolean(false);
|
||||
private final AtomicBoolean isRunning = new AtomicBoolean(true);
|
||||
private final AtomicLong lastDataTime = new AtomicLong(System.currentTimeMillis());
|
||||
private final AtomicInteger reconnectAttempts = new AtomicInteger(0);
|
||||
private final AtomicInteger currentReconnectDelay = new AtomicInteger(INITIAL_RECONNECT_DELAY);
|
||||
|
||||
private final byte[] readBuffer = new byte[BUFFER_SIZE];
|
||||
|
||||
private ScheduledExecutorService executorService;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 项目启动时初始化并启动服务
|
||||
ScaleService();
|
||||
start();
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
// 项目关闭时停止服务
|
||||
stop();
|
||||
}
|
||||
|
||||
public void ScaleService() {
|
||||
executorService = Executors.newScheduledThreadPool(2, r -> {
|
||||
Thread t = new Thread(r, "ScaleService-Worker");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "Shutdown-Hook"));
|
||||
}
|
||||
|
||||
public void start() {
|
||||
log.info("========================================");
|
||||
log.info("启动电子秤连接线程,串口: " + PORT_NAME + " | 波特率: " + BAUD_RATE);
|
||||
log.info("========================================");
|
||||
|
||||
startHealthCheck();
|
||||
startConnectionMonitor();
|
||||
if (connect()) {
|
||||
log.info("电子秤连接成功!");
|
||||
} else {
|
||||
log.info("电子秤连接失败,将自动尝试重连...");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean connect() {
|
||||
if (!isRunning.get()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isConnecting.compareAndSet(false, true)) {
|
||||
log.error("[连接] 已有连接任务正在执行,跳过");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
closeSerialPort();
|
||||
|
||||
SerialPort newPort = SerialPort.getCommPort(PORT_NAME);
|
||||
newPort.setBaudRate(BAUD_RATE);
|
||||
newPort.setNumDataBits(DATA_BITS);
|
||||
newPort.setNumStopBits(STOP_BITS);
|
||||
newPort.setParity(SerialPort.NO_PARITY);
|
||||
newPort.setComPortTimeouts(TIMEOUT, TIMEOUT, TIMEOUT);
|
||||
|
||||
if (!newPort.openPort()) {
|
||||
int attempts = reconnectAttempts.incrementAndGet();
|
||||
int delay = calculateReconnectDelay();
|
||||
log.info("[连接失败] 无法打开串口 {} (第{}次尝试,下次重试间隔: {}ms)", PORT_NAME, attempts, delay);
|
||||
return false;
|
||||
}
|
||||
|
||||
serialPort = newPort;
|
||||
reconnectAttempts.set(0);
|
||||
currentReconnectDelay.set(INITIAL_RECONNECT_DELAY);
|
||||
lastDataTime.set(System.currentTimeMillis());
|
||||
|
||||
log.info("[连接成功] 串口 {} 已打开", PORT_NAME);
|
||||
|
||||
startDataListener();
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("[连接异常] {}", e.getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
isConnecting.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateReconnectDelay() {
|
||||
int delay = currentReconnectDelay.get();
|
||||
int newDelay = Math.min(delay * 2, MAX_RECONNECT_DELAY);
|
||||
currentReconnectDelay.set(newDelay);
|
||||
return newDelay;
|
||||
}
|
||||
|
||||
private void closeSerialPort() {
|
||||
SerialPort oldPort = serialPort;
|
||||
serialPort = null;
|
||||
|
||||
if (oldPort != null) {
|
||||
try {
|
||||
if (oldPort.isOpen()) {
|
||||
oldPort.closePort();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[关闭串口] 异常: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startDataListener() {
|
||||
SerialPort port = serialPort;
|
||||
if (port == null || !port.isOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
port.addDataListener(new SerialPortDataListener() {
|
||||
@Override
|
||||
public int getListeningEvents() {
|
||||
return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialEvent(SerialPortEvent event) {
|
||||
if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SerialPort currentPort = serialPort;
|
||||
if (currentPort == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesAvailable = currentPort.bytesAvailable();
|
||||
if (bytesAvailable <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesToRead = Math.min(bytesAvailable, BUFFER_SIZE);
|
||||
int numRead = currentPort.readBytes(readBuffer, bytesToRead);
|
||||
|
||||
if (numRead > 0) {
|
||||
lastDataTime.set(System.currentTimeMillis());
|
||||
String data = new String(readBuffer, 0, numRead, SCALE_CHARSET);
|
||||
|
||||
// 解析数据,提取数字和重量单位
|
||||
String weightStr = data.trim();
|
||||
// 使用正则表达式提取数字和重量单位
|
||||
Pattern pattern = java.util.regex.Pattern.compile("[-+]?\\d*\\.?\\d+\\s*[a-zA-Z]*");
|
||||
Matcher matcher = pattern.matcher(weightStr);
|
||||
|
||||
if (matcher.find()) {
|
||||
try {
|
||||
String weightWithUnit = matcher.group().trim();
|
||||
log.info("[解析后] 重量: {}", weightWithUnit);
|
||||
// 发送提取的数字和重量单位给前端
|
||||
ScaleWebSocketHandler.sendMessage(weightWithUnit);
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("[解析异常] 无法解析重量数据: {}", e.getMessage());
|
||||
// 发送原始数据作为备选
|
||||
ScaleWebSocketHandler.sendMessage("电子秤异常: " + weightStr);
|
||||
}
|
||||
} else {
|
||||
log.warn("[解析警告] 未找到重量数据: {}", weightStr);
|
||||
// 发送原始数据作为备选
|
||||
ScaleWebSocketHandler.sendMessage("电子秤异常: " + weightStr);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[读取异常] {}", e.getMessage());
|
||||
triggerReconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startHealthCheck() {
|
||||
log.info("[健康检查] 启动,间隔 {} 秒", HEALTH_CHECK_INTERVAL / 1000);
|
||||
|
||||
executorService.scheduleAtFixedRate(() -> {
|
||||
if (!isRunning.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SerialPort port = serialPort;
|
||||
|
||||
if (port == null) {
|
||||
log.error("[健康检查] 串口对象为空");
|
||||
if (reconnectAttempts.get() < 10) {
|
||||
connect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isOpen = port.isOpen();
|
||||
// log.error("[健康检查] 串口状态: {}", isOpen ? "已打开" : "已关闭");
|
||||
|
||||
if (!isOpen) {
|
||||
log.info("[健康检查] 检测到串口关闭,尝试重连...");
|
||||
connect();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[健康检查异常] {}", e.getMessage());
|
||||
}
|
||||
}, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void startConnectionMonitor() {
|
||||
log.info("[连接监控] 启动,间隔 3 秒");
|
||||
|
||||
executorService.scheduleAtFixedRate(() -> {
|
||||
if (!isRunning.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long timeSinceLastData = currentTime - lastDataTime.get();
|
||||
|
||||
if (timeSinceLastData > DATA_TIMEOUT) {
|
||||
SerialPort port = serialPort;
|
||||
|
||||
if (port != null && port.isOpen()) {
|
||||
try {
|
||||
int bytesAvailable = port.bytesAvailable();
|
||||
|
||||
if (bytesAvailable < 0) {
|
||||
log.info("[连接监控] 检测到连接已断开,尝试重连...");
|
||||
triggerReconnect();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[连接监控] 检测到连接异常: {}", e.getMessage());
|
||||
triggerReconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[连接监控异常] {}", e.getMessage());
|
||||
}
|
||||
}, 3000, 3000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void triggerReconnect() {
|
||||
if (isConnecting.compareAndSet(false, true)) {
|
||||
isConnecting.set(false);
|
||||
connect();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (!isRunning.compareAndSet(true, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("========================================");
|
||||
log.info("停止电子秤线程...");
|
||||
log.info("========================================");
|
||||
|
||||
try {
|
||||
if (executorService != null && !executorService.isShutdown()) {
|
||||
executorService.shutdown();
|
||||
if (!executorService.awaitTermination(3, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
if (executorService != null) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
closeSerialPort();
|
||||
|
||||
log.info("线程已完全停止");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package top.wms.admin.controller.weighManage.ah;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
@Slf4j
|
||||
public class ScaleWebSocketHandler extends TextWebSocketHandler {
|
||||
// 线程安全的连接集合
|
||||
private static final CopyOnWriteArraySet<WebSocketSession> sessions = new CopyOnWriteArraySet<>();
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||
sessions.add(session);
|
||||
log.info("客户端连接成功,当前连接数: {}", sessions.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session,
|
||||
org.springframework.web.socket.CloseStatus status) throws Exception {
|
||||
sessions.remove(session);
|
||||
log.info("客户端断开连接,当前连接数: {}", sessions.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送数据给所有连接的客户端
|
||||
*/
|
||||
public static void sendMessage(String message) {
|
||||
for (WebSocketSession session : sessions) {
|
||||
if (session.isOpen()) {
|
||||
try {
|
||||
session.sendMessage(new TextMessage(message));
|
||||
} catch (IOException e) {
|
||||
log.error("发送消息失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,6 +225,7 @@ auth:
|
||||
- /auth/user/info
|
||||
- /auth/logout
|
||||
- /system/user/password
|
||||
- /ws/scale
|
||||
|
||||
--- ### 服务器配置
|
||||
server:
|
||||
|
||||
Reference in New Issue
Block a user