feat(printer): 实现打印机任务WebSocket实时推送功能

- 新增PrinterTaskPushService接口及实现,负责任务推送逻辑
- 在PrinterServiceImpl中集成WebSocket推送,在任务创建和审核通过时主动推送
- 新增WebSocket配置类和处理器,支持打印机通过WebSocket连接接收任务
- 实现连接管理器,维护打印机在线状态并支持心跳保活
- 添加相关模型类如WsMessage、WsMessageType等,规范通信协议
- 在PrinterMapper中增加查询待处理任务列表的方法
- 完善异常处理和日志记录,确保推送可靠性
This commit is contained in:
2025-12-01 09:59:21 +08:00
parent 1de760fc87
commit d590286b13
13 changed files with 710 additions and 1 deletions

View File

@@ -0,0 +1,28 @@
package com.ycwl.basic.service.printer;
import com.ycwl.basic.model.printer.resp.PrintTaskResp;
import java.util.List;
/**
* 打印机任务推送服务
*/
public interface PrinterTaskPushService {
/**
* 推送任务到打印机
*
* @param printerId 打印机ID
* @param taskId 任务ID
* @return 是否推送成功
*/
boolean pushTaskToPrinter(Integer printerId, Integer taskId);
/**
* 获取打印机的待处理任务列表
*
* @param printerId 打印机ID
* @return 待处理任务列表
*/
List<PrintTaskResp> getPendingTasksByPrinterId(Integer printerId);
}

View File

@@ -67,6 +67,7 @@ import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.mobile.WxPayService;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.service.printer.PrinterService;
import com.ycwl.basic.service.printer.PrinterTaskPushService;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.utils.ApiResponse;
@@ -140,6 +141,8 @@ public class PrinterServiceImpl implements PrinterService {
private FaceService faceService;
@Autowired
private DeviceRepository deviceRepository;
@Autowired
private PrinterTaskPushService taskPushService;
// 用于优先打印的线程池,核心线程数根据实际情况调整
private final ExecutorService preferPrintExecutor = Executors.newFixedThreadPool(
@@ -961,6 +964,17 @@ public class PrinterServiceImpl implements PrinterService {
task.setCreateTime(new Date());
task.setUpdateTime(new Date());
printTaskMapper.insertTask(task);
// ========== WebSocket 推送任务 ==========
// 只推送立即可处理的任务(status=0),待审核任务(status=4)等审核通过后再推送
if (initialStatus == TASK_STATUS_PENDING) {
try {
taskPushService.pushTaskToPrinter(printer.getId(), task.getId());
} catch (Exception e) {
log.error("推送任务失败: printerId={}, taskId={}", printer.getId(), task.getId(), e);
// 推送失败不影响任务创建,任务会通过 HTTP 轮询获取
}
}
}
});
}
@@ -1001,7 +1015,21 @@ public class PrinterServiceImpl implements PrinterService {
return 0;
}
// 将状态从4(待审核)改为0(待处理)
return printTaskMapper.batchUpdateStatus(taskIds, TASK_STATUS_PENDING);
int count = printTaskMapper.batchUpdateStatus(taskIds, TASK_STATUS_PENDING);
// ========== WebSocket 推送审核通过的任务 ==========
for (Integer taskId : taskIds) {
try {
PrintTaskEntity task = printTaskMapper.selectById(taskId);
if (task != null && task.getPrinterId() != null) {
taskPushService.pushTaskToPrinter(task.getPrinterId(), taskId);
}
} catch (Exception e) {
log.error("推送审核任务失败: taskId={}", taskId, e);
}
}
return count;
}
/**

View File

@@ -0,0 +1,110 @@
package com.ycwl.basic.service.printer.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ycwl.basic.mapper.PrinterMapper;
import com.ycwl.basic.model.pc.printer.entity.PrintTaskEntity;
import com.ycwl.basic.model.printer.resp.PrintTaskResp;
import com.ycwl.basic.service.printer.PrinterTaskPushService;
import com.ycwl.basic.websocket.manager.PrinterConnectionManager;
import com.ycwl.basic.websocket.model.WsMessage;
import com.ycwl.basic.websocket.model.WsMessageType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.util.List;
/**
* 打印机任务推送服务实现
*/
@Slf4j
@Service
public class PrinterTaskPushServiceImpl implements PrinterTaskPushService {
private static final int TASK_STATUS_PENDING = 0;
private static final int TASK_STATUS_PROCESSING = 3;
@Autowired
private PrinterMapper printerMapper;
@Autowired
private PrinterConnectionManager connectionManager;
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean pushTaskToPrinter(Integer printerId, Integer taskId) {
try {
// 1. 检查打印机是否在线
if (!connectionManager.isOnline(printerId)) {
log.debug("打印机不在线,跳过推送: printerId={}, taskId={}", printerId, taskId);
return false;
}
// 2. 获取 WebSocket 连接
WebSocketSession session = connectionManager.getSession(printerId);
if (session == null || !session.isOpen()) {
log.warn("打印机连接不可用: printerId={}, taskId={}", printerId, taskId);
return false;
}
// 3. 执行 CAS 操作,确保任务只被推送一次
int updatedRows = printerMapper.compareAndSetTaskStatus(
taskId, TASK_STATUS_PENDING, TASK_STATUS_PROCESSING
);
if (updatedRows != 1) {
log.debug("任务已被获取,跳过推送: printerId={}, taskId={}", printerId, taskId);
return false;
}
// 4. 查询任务详情
PrintTaskEntity taskEntity = printerMapper.getTaskById(taskId);
if (taskEntity == null) {
log.error("任务不存在: taskId={}", taskId);
return false;
}
// 5. 转换为 PrintTaskResp
PrintTaskResp task = new PrintTaskResp();
BeanUtils.copyProperties(taskEntity, task);
task.setStatus(TASK_STATUS_PROCESSING);
// 6. 通过 WebSocket 推送任务
WsMessage<PrintTaskResp> message = WsMessage.create(WsMessageType.TASK_PUSH, task);
String json = objectMapper.writeValueAsString(message);
session.sendMessage(new TextMessage(json));
log.info("任务推送成功: printerId={}, taskId={}", printerId, taskId);
return true;
} catch (Exception e) {
log.error("任务推送失败: printerId={}, taskId={}", printerId, taskId, e);
// 推送失败时,恢复任务状态为待处理
try {
printerMapper.compareAndSetTaskStatus(
taskId, TASK_STATUS_PROCESSING, TASK_STATUS_PENDING
);
} catch (Exception ex) {
log.error("恢复任务状态失败: taskId={}", taskId, ex);
}
return false;
}
}
@Override
public List<PrintTaskResp> getPendingTasksByPrinterId(Integer printerId) {
// 直接查询该打印机所有待处理任务 (status=0),最多返回 100 个
List<PrintTaskResp> tasks = printerMapper.listPendingTasksByPrinterId(printerId);
if (tasks.size() >= 100) {
log.warn("待处理任务过多,已达上限: printerId={}, count={}", printerId, tasks.size());
}
return tasks;
}
}