diff --git a/src/main/java/com/gaotao/modules/base/dao/PrintTaskDao.java b/src/main/java/com/gaotao/modules/base/dao/PrintTaskDao.java new file mode 100644 index 0000000..eb53db0 --- /dev/null +++ b/src/main/java/com/gaotao/modules/base/dao/PrintTaskDao.java @@ -0,0 +1,28 @@ +package com.gaotao.modules.base.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gaotao.modules.base.entity.PrintTask; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 打印任务队列 Dao + */ +@Mapper +public interface PrintTaskDao extends BaseMapper { + + /** + * 获取待执行的打印任务(使用READPAST避免锁等待) + * + * @param limit 获取数量 + * @return 待执行任务列表 + */ + @Select("SELECT TOP ${limit} * FROM print_task WITH (READPAST) " + + "WHERE task_status = 'PENDING' " + + "ORDER BY created_date ASC") + List selectPendingTasksWithReadPast(@Param("limit") int limit); +} + diff --git a/src/main/java/com/gaotao/modules/base/entity/PrintTask.java b/src/main/java/com/gaotao/modules/base/entity/PrintTask.java new file mode 100644 index 0000000..5c8bc76 --- /dev/null +++ b/src/main/java/com/gaotao/modules/base/entity/PrintTask.java @@ -0,0 +1,119 @@ +package com.gaotao.modules.base.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 打印任务队列实体类 + * + *

核心字段说明:

+ * + * + *

使用说明:

+ * + */ +@Data +@TableName("print_task") +public class PrintTask implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 站点编码 + */ + @TableField("site") + private String site; + + /** + * 打印机IP地址 + */ + @TableField("printer_ip") + private String printerIp; + + /** + * ZPL打印代码 + */ + @TableField("zpl_code") + private String zplCode; + + /** + * RFID标签标识 + */ + @TableField("rfid_flag") + private String rfidFlag; + + /** + * 打印份数 + */ + @TableField("copies") + private Integer copies; + + /** + * 标签数据(JSON格式) + */ + @TableField("label_data") + private String labelData; + + /** + * 任务状态:PENDING=待执行, PROCESSING=执行中, SUCCESS=成功, FAILED=失败 + */ + @TableField("task_status") + private String taskStatus; + + /** + * 重试次数 + */ + @TableField("retry_count") + private Integer retryCount; + + /** + * 错误信息 + */ + @TableField("error_message") + private String errorMessage; + + /** + * 创建人 + */ + @TableField("created_by") + private String createdBy; + + /** + * 创建时间 + */ + @TableField("created_date") + private Date createdDate; + + /** + * 开始执行时间 + */ + @TableField("started_date") + private Date startedDate; + + /** + * 完成时间 + */ + @TableField("completed_date") + private Date completedDate; +} + diff --git a/src/main/java/com/gaotao/modules/base/service/Impl/PrintTaskServiceImpl.java b/src/main/java/com/gaotao/modules/base/service/Impl/PrintTaskServiceImpl.java new file mode 100644 index 0000000..6a582c1 --- /dev/null +++ b/src/main/java/com/gaotao/modules/base/service/Impl/PrintTaskServiceImpl.java @@ -0,0 +1,94 @@ +package com.gaotao.modules.base.service.Impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gaotao.modules.base.dao.PrintTaskDao; +import com.gaotao.modules.base.entity.PrintTask; +import com.gaotao.modules.base.service.PrintTaskService; +import com.google.gson.Gson; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 打印任务队列服务实现类 + */ +@Slf4j +@Service +public class PrintTaskServiceImpl extends ServiceImpl implements PrintTaskService { + + private static final Gson gson = new Gson(); + + @Override + @Transactional + public Long addPrintTask(String printerIp, String zplCode, String rfidFlag, + Integer copies, Map labelData, + String site, String createdBy) { + PrintTask task = new PrintTask(); + task.setSite(site); + task.setPrinterIp(printerIp); + task.setZplCode(zplCode); + task.setRfidFlag(rfidFlag); + task.setCopies(copies != null && copies > 0 ? copies : 1); + task.setLabelData(labelData != null ? gson.toJson(labelData) : null); + task.setTaskStatus("PENDING"); + task.setRetryCount(0); + task.setCreatedBy(createdBy); + task.setCreatedDate(new Date()); + + boolean saved = this.save(task); + if (!saved) { + log.error("添加打印任务失败:printerIp={}, zplCode前50字符={}", + printerIp, zplCode.substring(0, Math.min(50, zplCode.length()))); + throw new RuntimeException("添加打印任务失败"); + } + + log.info("✓ 打印任务已加入队列:taskId={}, printerIp={}, copies={}", + task.getId(), printerIp, task.getCopies()); + return task.getId(); + } + + @Override + public List getPendingTasks(int limit) { + // 使用原生SQL + READPAST提示,避免锁等待 + // READPAST: 跳过被其他事务锁定的行,确保不阻塞INSERT操作 + return this.baseMapper.selectPendingTasksWithReadPast(limit); + } + + @Override + @Transactional + public boolean markAsProcessing(Long taskId) { + return this.lambdaUpdate() + .set(PrintTask::getTaskStatus, "PROCESSING") + .set(PrintTask::getStartedDate, new Date()) + .eq(PrintTask::getId, taskId) + .eq(PrintTask::getTaskStatus, "PENDING") // 乐观锁:只更新PENDING状态的任务 + .update(); + } + + @Override + @Transactional + public boolean markAsSuccess(Long taskId) { + return this.lambdaUpdate() + .set(PrintTask::getTaskStatus, "SUCCESS") + .set(PrintTask::getCompletedDate, new Date()) + .eq(PrintTask::getId, taskId) + .update(); + } + + @Override + @Transactional + public boolean markAsFailed(Long taskId, String errorMessage) { + return this.lambdaUpdate() + .set(PrintTask::getTaskStatus, "FAILED") + .set(PrintTask::getErrorMessage, errorMessage) + .set(PrintTask::getCompletedDate, new Date()) + .setSql("retry_count = retry_count + 1") + .eq(PrintTask::getId, taskId) + .update(); + } +} + diff --git a/src/main/java/com/gaotao/modules/base/service/Impl/ReportLabelListServiceImpl.java b/src/main/java/com/gaotao/modules/base/service/Impl/ReportLabelListServiceImpl.java index fdc7cbe..57b242d 100644 --- a/src/main/java/com/gaotao/modules/base/service/Impl/ReportLabelListServiceImpl.java +++ b/src/main/java/com/gaotao/modules/base/service/Impl/ReportLabelListServiceImpl.java @@ -9,6 +9,7 @@ import com.gaotao.modules.base.dao.ReportLabelListMapper; import com.gaotao.modules.base.entity.*; import com.gaotao.modules.base.service.BaseService; import com.gaotao.modules.base.service.LabelDataProcessorService; +import com.gaotao.modules.base.service.PrintTaskService; import com.gaotao.modules.base.service.ReportLabelListService; import com.gaotao.modules.handlingunit.entity.HandlingUnit; @@ -47,6 +48,9 @@ public class ReportLabelListServiceImpl extends ServiceImpl 0 ? printRequest.getCopies() : 1; - // 5. 循环发送每个ZPL到打印机并保存标签记录 + // 5. 将所有打印任务添加到队列中(不直接打印) int totalLabels = zplCodeList.size(); + List taskIds = new ArrayList<>(); + for (int i = 0; i < totalLabels; i++) { String zplCode = zplCodeList.get(i); - log.info("正在打印第 {} / {} 张标签", i + 1, totalLabels); - - // 为每个ZPL代码设置到printRequest中(用于日志记录) - printRequest.setZplCode(zplCode); Map labelData = i < labelDataList.size() ? labelDataList.get(i) : new HashMap<>(); - // 发送ZPL到打印机 - sendZplToPrinterAndGetResult(printerIP, zplCode, requestedCopies, rfidFlag,labelData); + + // 如果是RFID标签,在这里组装完整的RFID ZPL(包含写EPC指令) + if ("Y".equals(rfidFlag)) { + zplCode = buildRfidZpl(zplCode, labelData); + log.info("已组装RFID ZPL,unit_id={}", labelData.get("unit_id")); + } + + // 添加到打印任务队列 + Long taskId = printTaskService.addPrintTask( + printerIP, + zplCode, + rfidFlag, + requestedCopies, + labelData, + printRequest.getSite() != null ? printRequest.getSite() : "DEFAULT", + printRequest.getUserId() != null ? printRequest.getUserId().toString() : "SYSTEM" + ); + taskIds.add(taskId); + + log.info("打印任务已加入队列 {} / {}, taskId={}", i + 1, totalLabels, taskId); } - // 6. 记录打印日志 - log.info("所有标签打印任务执行成功,共打印 {} 张标签,每张 {} 份", totalLabels, requestedCopies); + + // 6. 记录日志 + log.info("✓ 所有打印任务已加入队列,共 {} 个任务,任务ID: {}", totalLabels, taskIds); } catch (Exception e) { System.err.println("RFID标签打印失败: " + e.getMessage()); throw new RuntimeException("RFID标签打印失败: " + e.getMessage()); @@ -468,6 +489,41 @@ public class ReportLabelListServiceImpl extends ServiceImpl labelData) { + // 1. 获取EPC(unit_id) + String epc = getStringValue(labelData, "unit_id", ""); + if (epc.isEmpty()) { + log.warn("RFID标签缺少unit_id,使用原始ZPL"); + return originalZplCode; + } + + // 补齐到24位,不足的在前面加0 + epc = String.format("%24s", epc).replace(' ', '0'); + log.debug("构建RFID ZPL,EPC(24位): {}", epc); + + // 2. 清理原始ZPL代码 + String cleanedZplCode = cleanZplForSinglePrint(originalZplCode); + + // 3. 组装包含RFID指令的完整ZPL + String rfidZpl = "^XA\n" + + "^PQ1\n" + // 打印1张 + "^RS8\n" + // 设置RFID参数 + "^RFR,H,0,12,2^FN1^FS\n" + // 读取TID + "^HV1^FN1^FS\n" + // 打印TID + "^RFW,H,2,6,6^FD" + epc + "^FS\n" + // 写入EPC + cleanedZplCode; + + log.debug("RFID ZPL已组装完成,长度: {}", rfidZpl.length()); + return rfidZpl; + } + /** * 清理ZPL代码并设置指定的打印份数 */ @@ -639,17 +695,38 @@ public class ReportLabelListServiceImpl extends ServiceImpl taskIds = new ArrayList<>(); + for (int i = 0; i < totalLabels; i++) { String zplCode = zplCodeList.get(i); - log.info("正在打印第 {} / {} 张标签", i + 1, totalLabels); Map labelData = i < labelDataList.size() ? labelDataList.get(i) : new HashMap<>(); - // 发送ZPL到打印机 - sendZplToPrinterAndGetResult(printerIP, zplCode, requestedCopies, rfidFlag,labelData); + + // 如果是RFID标签,在这里组装完整的RFID ZPL(包含写EPC指令) + if ("Y".equals(rfidFlag)) { + zplCode = buildRfidZpl(zplCode, labelData); + log.info("已组装RFID ZPL(通用),unit_id={}", labelData.get("unit_id")); + } + + // 添加到打印任务队列 + Long taskId = printTaskService.addPrintTask( + printerIP, + zplCode, + rfidFlag, + requestedCopies, + labelData, + printRequest.getSite() != null ? printRequest.getSite() : "DEFAULT", + printRequest.getUserId() != null ? printRequest.getUserId().toString() : "SYSTEM" + ); + taskIds.add(taskId); + + log.info("打印任务已加入队列 {} / {}, taskId={}", i + 1, totalLabels, taskId); } - // 6. 记录打印日志 - log.info("所有标签打印任务执行成功,共打印 {} 张标签,每张 {} 份", totalLabels, requestedCopies); + + // 6. 记录日志 + log.info("✓ 所有打印任务已加入队列,共 {} 个任务,任务ID: {}", totalLabels, taskIds); } catch (Exception e) { System.err.println("RFID标签打印失败: " + e.getMessage()); throw new RuntimeException("RFID标签打印失败: " + e.getMessage()); diff --git a/src/main/java/com/gaotao/modules/base/service/PrintTaskService.java b/src/main/java/com/gaotao/modules/base/service/PrintTaskService.java new file mode 100644 index 0000000..f7e3fdf --- /dev/null +++ b/src/main/java/com/gaotao/modules/base/service/PrintTaskService.java @@ -0,0 +1,63 @@ +package com.gaotao.modules.base.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gaotao.modules.base.entity.PrintTask; + +import java.util.List; +import java.util.Map; + +/** + * 打印任务队列服务接口 + */ +public interface PrintTaskService extends IService { + + /** + * 添加打印任务到队列 + * + * @param printerIp 打印机IP + * @param zplCode ZPL代码 + * @param rfidFlag RFID标识 + * @param copies 打印份数 + * @param labelData 标签数据 + * @param site 站点 + * @param createdBy 创建人 + * @return 任务ID + */ + Long addPrintTask(String printerIp, String zplCode, String rfidFlag, + Integer copies, Map labelData, + String site, String createdBy); + + /** + * 获取待执行的打印任务(FIFO顺序) + * + * @param limit 获取任务数量 + * @return 待执行任务列表 + */ + List getPendingTasks(int limit); + + /** + * 更新任务为执行中 + * + * @param taskId 任务ID + * @return 是否成功 + */ + boolean markAsProcessing(Long taskId); + + /** + * 更新任务为成功 + * + * @param taskId 任务ID + * @return 是否成功 + */ + boolean markAsSuccess(Long taskId); + + /** + * 更新任务为失败 + * + * @param taskId 任务ID + * @param errorMessage 错误信息 + * @return 是否成功 + */ + boolean markAsFailed(Long taskId, String errorMessage); +} + diff --git a/src/main/java/com/gaotao/modules/base/task/PrintTaskScheduler.java b/src/main/java/com/gaotao/modules/base/task/PrintTaskScheduler.java new file mode 100644 index 0000000..744a7f9 --- /dev/null +++ b/src/main/java/com/gaotao/modules/base/task/PrintTaskScheduler.java @@ -0,0 +1,171 @@ +package com.gaotao.modules.base.task; + +import com.gaotao.modules.base.entity.PrintTask; +import com.gaotao.modules.base.service.PrintTaskService; +import com.google.gson.Gson; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.OutputStream; +import java.net.Socket; +import java.util.List; +import java.util.Map; + +/** + * 打印任务定时调度器 + * + *

功能说明:

+ *
    + *
  • 每秒扫描一次打印任务队列
  • + *
  • 按FIFO顺序(created_date)取出PENDING状态的任务
  • + *
  • 执行打印任务并更新状态
  • + *
  • 失败任务记录错误信息
  • + *
+ * + *

并发控制:

+ *
    + *
  • 使用数据库乐观锁(markAsProcessing时检查状态)
  • + *
  • 确保同一任务不会被重复执行
  • + *
+ */ +@Slf4j +@Component +public class PrintTaskScheduler { + + @Autowired + private PrintTaskService printTaskService; + + private static final Gson gson = new Gson(); + + @Value("${dashboard.push.enabled:true}") + private boolean dashboardPushEnabled; + + // 每秒执行一次 + @Scheduled(fixedDelay = 500) + public void processPrintTasks() { + try { + // 1. 获取待执行的任务(每次处理10个) + List pendingTasks = printTaskService.getPendingTasks(10); + + if (pendingTasks.isEmpty()) { + return; // 没有待处理任务 + } + + log.info("【打印队列】发现 {} 个待执行任务", pendingTasks.size()); + + // 2. 按FIFO顺序逐个执行 + for (PrintTask task : pendingTasks) { + processTask(task); + } + + } catch (Exception e) { + log.error("打印任务调度器执行异常: {}", e.getMessage(), e); + } + } + + /** + * 处理单个打印任务 + */ + private void processTask(PrintTask task) { + Long taskId = task.getId(); + String printerIp = task.getPrinterIp(); + + try { + // 1. 标记为执行中(乐观锁,防止并发重复执行) + boolean marked = printTaskService.markAsProcessing(taskId); + if (!marked) { + log.debug("任务 {} 已被其他线程处理,跳过", taskId); + return; + } + + log.info("【打印队列】开始执行任务 taskId={}, 打印机={}, 份数={}", + taskId, printerIp, task.getCopies()); + + // 2. 解析标签数据 + Map labelData = null; + if (task.getLabelData() != null && !task.getLabelData().trim().isEmpty()) { + labelData = gson.fromJson(task.getLabelData(), Map.class); + } + + // 3. 执行打印 + boolean success = executePrint( + printerIp, + task.getZplCode(), + task.getCopies(), + task.getRfidFlag(), + labelData + ); + + // 4. 更新任务状态 + if (success) { + printTaskService.markAsSuccess(taskId); + log.info("【打印队列】✓ 任务完成 taskId={}, 打印机={}", taskId, printerIp); + } else { + printTaskService.markAsFailed(taskId, "打印失败,未返回成功标识"); + log.error("【打印队列】✗ 任务失败 taskId={}, 打印机={}", taskId, printerIp); + } + + } catch (Exception e) { + log.error("【打印队列】✗ 任务执行异常 taskId={}, 错误: {}", taskId, e.getMessage(), e); + printTaskService.markAsFailed(taskId, "执行异常: " + e.getMessage()); + } + } + + /** + * 执行打印(发送ZPL到打印机) + * 注意:ZPL代码已在入队时组装好(包括RFID指令),这里只需要直接发送 + * + * @param printerIp 打印机IP + * @param zplCode ZPL代码(已包含RFID指令) + * @param copies 打印份数 + * @param rfidFlag RFID标识(用于日志) + * @param labelData 标签数据(未使用,保留以备后续扩展) + * @return 是否成功 + */ + private boolean executePrint(String printerIp, String zplCode, Integer copies, + String rfidFlag, Map labelData) { + Socket socket = null; + try { + // 1. 连接打印机 + socket = new Socket(printerIp, 9100); + socket.setSoTimeout(10000); // 10秒超时 + + OutputStream os = socket.getOutputStream(); + + int actualCopies = copies != null && copies > 0 ? copies : 1; + + // 2. 直接发送ZPL代码(已在入队时组装好,包括RFID指令) + log.debug("开始发送ZPL到打印机 {}, 份数: {}, RFID: {}", printerIp, actualCopies, rfidFlag); + + for (int i = 0; i < actualCopies; i++) { + os.write(zplCode.getBytes("UTF-8")); + os.flush(); + + // RFID标签需要等待处理时间 + if ("Y".equals(rfidFlag)) { + Thread.sleep(1000); // RFID标签等待1秒 + log.debug("RFID标签打印完成 {} / {}", i + 1, actualCopies); + } + } + + log.debug("✓ ZPL已发送到打印机 {}, 份数: {}", printerIp, actualCopies); + return true; + + } catch (Exception e) { + log.error("发送ZPL到打印机失败: printerIp={}, error={}", printerIp, e.getMessage(), e); + return false; + } finally { + if (socket != null) { + try { + socket.close(); + } catch (Exception e) { + log.warn("关闭socket失败: {}", e.getMessage()); + } + } + } + } +} +