From 51cae14152b05172771867b3dbac8a6a2b5dc182 Mon Sep 17 00:00:00 2001 From: yangk Date: Fri, 3 Jul 2026 14:32:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(rfid):=20=E6=B7=BB=E5=8A=A0RFID=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E6=89=B9=E9=87=8F=E5=BB=BA=E6=A1=A3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增RfidTagBatchCreateRequest、RfidTagBatchCreateResult和RfidTagBatchCreateRow域对象 - 实现batchCreateRfidTags方法支持批量创建RFID标签 - 重构导入逻辑使用统一的批量建档规则处理 - 添加重复EPC检测和行级错误处理机制 - 支持PDA等非Excel入口复用建档规则 - 完善单元测试验证批量建档功能 --- .../domain/RfidTagBatchCreateRequest.java | 54 +++++++ .../domain/RfidTagBatchCreateResult.java | 94 ++++++++++++ .../asset/domain/RfidTagBatchCreateRow.java | 104 +++++++++++++ .../asset/service/IAmsRfidTagService.java | 10 ++ .../service/impl/AmsRfidTagServiceImpl.java | 137 +++++++++++++----- .../impl/AmsRfidTagServiceImplTest.java | 41 ++++++ 6 files changed, 404 insertions(+), 36 deletions(-) create mode 100644 ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateRequest.java create mode 100644 ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateResult.java create mode 100644 ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateRow.java diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateRequest.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateRequest.java new file mode 100644 index 0000000..e8a212a --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateRequest.java @@ -0,0 +1,54 @@ +package com.ruoyi.asset.domain; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * RFID标签批量建档请求 + * + * @author Yangk + */ +public class RfidTagBatchCreateRequest implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** EPC编码列表 */ + private List epcCodes = new ArrayList<>(); + + /** 建档备注 */ + private String remark; + + /** 操作登录账号 */ + private String operName; + + public List getEpcCodes() + { + return epcCodes; + } + + public void setEpcCodes(List epcCodes) + { + this.epcCodes = epcCodes; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public String getOperName() + { + return operName; + } + + public void setOperName(String operName) + { + this.operName = operName; + } +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateResult.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateResult.java new file mode 100644 index 0000000..20ff627 --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateResult.java @@ -0,0 +1,94 @@ +package com.ruoyi.asset.domain; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * RFID标签批量建档结果 + * + * @author Yangk + */ +public class RfidTagBatchCreateResult implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 成功数量 */ + private int successCount; + + /** 重复数量 */ + private int duplicateCount; + + /** 非重复失败数量 */ + private int failureCount; + + /** 行级结果 */ + private List rows = new ArrayList<>(); + + public void addSuccess(RfidTagBatchCreateRow row) + { + row.setSuccess(true); + rows.add(row); + successCount++; + } + + public void addDuplicate(RfidTagBatchCreateRow row) + { + row.setSuccess(false); + row.setDuplicate(true); + rows.add(row); + duplicateCount++; + } + + public void addFailure(RfidTagBatchCreateRow row) + { + row.setSuccess(false); + rows.add(row); + failureCount++; + } + + public int getSuccessCount() + { + return successCount; + } + + public void setSuccessCount(int successCount) + { + this.successCount = successCount; + } + + public int getDuplicateCount() + { + return duplicateCount; + } + + public void setDuplicateCount(int duplicateCount) + { + this.duplicateCount = duplicateCount; + } + + public int getFailureCount() + { + return failureCount; + } + + public void setFailureCount(int failureCount) + { + this.failureCount = failureCount; + } + + public int getTotalFailureCount() + { + return duplicateCount + failureCount; + } + + public List getRows() + { + return rows; + } + + public void setRows(List rows) + { + this.rows = rows; + } +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateRow.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateRow.java new file mode 100644 index 0000000..270f18d --- /dev/null +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/domain/RfidTagBatchCreateRow.java @@ -0,0 +1,104 @@ +package com.ruoyi.asset.domain; + +import java.io.Serializable; + +/** + * RFID标签批量建档行级结果 + * + * @author Yangk + */ +public class RfidTagBatchCreateRow implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 行号 */ + private Integer rowNumber; + + /** EPC编码 */ + private String epcCode; + + /** 是否成功 */ + private boolean success; + + /** 是否重复 */ + private boolean duplicate; + + /** 标签ID */ + private Long tagId; + + /** 标签编码 */ + private String tagCode; + + /** 行级提示 */ + private String message; + + public Integer getRowNumber() + { + return rowNumber; + } + + public void setRowNumber(Integer rowNumber) + { + this.rowNumber = rowNumber; + } + + public String getEpcCode() + { + return epcCode; + } + + public void setEpcCode(String epcCode) + { + this.epcCode = epcCode; + } + + public boolean isSuccess() + { + return success; + } + + public void setSuccess(boolean success) + { + this.success = success; + } + + public boolean isDuplicate() + { + return duplicate; + } + + public void setDuplicate(boolean duplicate) + { + this.duplicate = duplicate; + } + + public Long getTagId() + { + return tagId; + } + + public void setTagId(Long tagId) + { + this.tagId = tagId; + } + + public String getTagCode() + { + return tagCode; + } + + public void setTagCode(String tagCode) + { + this.tagCode = tagCode; + } + + public String getMessage() + { + return message; + } + + public void setMessage(String message) + { + this.message = message; + } +} diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsRfidTagService.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsRfidTagService.java index c8b4dec..0374598 100644 --- a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsRfidTagService.java +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/IAmsRfidTagService.java @@ -2,6 +2,8 @@ package com.ruoyi.asset.service; import java.util.List; import com.ruoyi.asset.domain.AmsRfidTag; +import com.ruoyi.asset.domain.RfidTagBatchCreateRequest; +import com.ruoyi.asset.domain.RfidTagBatchCreateResult; /** * RFID标签Service接口 @@ -52,6 +54,14 @@ public interface IAmsRfidTagService */ public String importAmsRfidTag(List tagList, String operName); + /** + * 批量建档RFID标签 + * + * @param request 批量建档请求 + * @return 批量建档结果 + */ + public RfidTagBatchCreateResult batchCreateRfidTags(RfidTagBatchCreateRequest request); + /** * 新增RFID标签 * diff --git a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsRfidTagServiceImpl.java b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsRfidTagServiceImpl.java index 62f075a..d96c8ae 100644 --- a/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsRfidTagServiceImpl.java +++ b/ruoyi-asset/src/main/java/com/ruoyi/asset/service/impl/AmsRfidTagServiceImpl.java @@ -11,6 +11,9 @@ import java.util.Set; import com.ruoyi.asset.constant.RfidBindStatus; import com.ruoyi.asset.constant.RfidTagStatus; import com.ruoyi.asset.domain.RfidBindingContext; +import com.ruoyi.asset.domain.RfidTagBatchCreateRequest; +import com.ruoyi.asset.domain.RfidTagBatchCreateResult; +import com.ruoyi.asset.domain.RfidTagBatchCreateRow; import com.ruoyi.asset.service.IAssetLifecycleService; import com.ruoyi.asset.service.IRfidBindingService; import com.ruoyi.common.constant.UserConstants; @@ -139,39 +142,80 @@ public class AmsRfidTagServiceImpl implements IAmsRfidTagService { throw new ServiceException("导入RFID标签数据不能为空!"); } - if (tagList.size() > IMPORT_MAX_ROWS) + RfidTagBatchCreateRequest request = new RfidTagBatchCreateRequest(); + request.setEpcCodes(collectImportEpcCodes(tagList)); + request.setOperName(operName); + return formatImportMessage(batchCreateRfidTags(request)); + } + + /** + * 批量建档RFID标签 + * + * @param request 批量建档请求 + * @return 批量建档结果 + */ + @Override + public RfidTagBatchCreateResult batchCreateRfidTags(RfidTagBatchCreateRequest request) + { + validateBatchCreateRequest(request); + RfidTagBatchCreateResult result = new RfidTagBatchCreateResult(); + Map firstRowByEpc = new LinkedHashMap<>(); + Set existingEpcCodes = selectExistingEpcCodes(request.getEpcCodes()); + + // 批量收货要求有效 EPC 先建档,无效 EPC 逐行反馈给操作者。 + for (int i = 0; i < request.getEpcCodes().size(); i++) { - throw new ServiceException("单次最多导入" + IMPORT_MAX_ROWS + "条RFID标签,请拆分文件后再导入"); + int rowNumber = i + 1; + RfidTagBatchCreateRow row = new RfidTagBatchCreateRow(); + row.setRowNumber(rowNumber); + row.setEpcCode(StringUtils.trim(request.getEpcCodes().get(i))); + try + { + AmsRfidTag tag = buildBatchCreateTag(request, rowNumber, firstRowByEpc, existingEpcCodes); + insertAmsRfidTag(tag); + row.setEpcCode(tag.getEpcCode()); + row.setTagId(tag.getTagId()); + row.setTagCode(tag.getTagCode()); + row.setMessage("建档成功"); + result.addSuccess(row); + } + catch (Exception e) + { + row.setMessage(e.getMessage()); + if (isDuplicateFailure(e.getMessage())) + { + result.addDuplicate(row); + } + else + { + result.addFailure(row); + } + } } + return result; + } + + private String formatImportMessage(RfidTagBatchCreateResult result) + { int successNum = 0; int failureNum = 0; StringBuilder successMsg = new StringBuilder(); StringBuilder failureMsg = new StringBuilder(); - Map firstRowByEpc = new LinkedHashMap<>(); - Set existingEpcCodes = selectExistingEpcCodes(tagList); - - // 故意不包整批事务:有效行入库、无效行汇总提示;编码规则内部事务负责锁定流水规则行。 - for (int i = 0; i < tagList.size(); i++) + for (RfidTagBatchCreateRow row : result.getRows()) { - AmsRfidTag importTag = tagList.get(i); - int rowNumber = i + 1; - String rowName = "第" + rowNumber + "条"; - try + if (row.isSuccess()) { - // 批量收货场景要求合法行先建档,行级异常只汇总提示,不回滚其它有效标签。 - AmsRfidTag tag = buildImportTag(importTag, rowNumber, firstRowByEpc, existingEpcCodes, operName); - insertAmsRfidTag(tag); successNum++; - successMsg.append("
" + successNum + "、EPC " + tag.getEpcCode() - + " 导入成功,标签编码:" + tag.getTagCode()); + successMsg.append("
" + successNum + "、EPC " + row.getEpcCode() + + " 导入成功,标签编码:" + row.getTagCode()); } - catch (Exception e) + else { failureNum++; - failureMsg.append("
" + failureNum + "、" + rowName + " 导入失败:" + e.getMessage()); + failureMsg.append("
" + failureNum + "、第" + row.getRowNumber() + + "条 导入失败:" + row.getMessage()); } } - if (failureNum > 0) { failureMsg.insert(0, "共 " + failureNum + " 条数据不符合RFID标签规则,错误如下:"); @@ -366,15 +410,23 @@ public class AmsRfidTagServiceImpl implements IAmsRfidTagService } } - private AmsRfidTag buildImportTag(AmsRfidTag importTag, int rowNumber, - Map firstRowByEpc, - Set existingEpcCodes, String operName) + private void validateBatchCreateRequest(RfidTagBatchCreateRequest request) { - if (StringUtils.isNull(importTag)) + if (StringUtils.isNull(request) || request.getEpcCodes() == null || request.getEpcCodes().isEmpty()) { - throw new ServiceException("RFID标签不能为空"); + throw new ServiceException("RFID标签批量建档数据不能为空!"); } - String epcCode = StringUtils.trim(importTag.getEpcCode()); + if (request.getEpcCodes().size() > IMPORT_MAX_ROWS) + { + throw new ServiceException("单次最多导入" + IMPORT_MAX_ROWS + "条RFID标签,请拆分文件后再导入"); + } + } + + private AmsRfidTag buildBatchCreateTag(RfidTagBatchCreateRequest request, int rowNumber, + Map firstRowByEpc, + Set existingEpcCodes) + { + String epcCode = StringUtils.trim(request.getEpcCodes().get(rowNumber - 1)); if (StringUtils.isEmpty(epcCode)) { throw new ServiceException("EPC编码不能为空"); @@ -395,13 +447,14 @@ public class AmsRfidTagServiceImpl implements IAmsRfidTagService AmsRfidTag tag = new AmsRfidTag(); tag.setEpcCode(epcCode); - tag.setCreateBy(operName); + tag.setRemark(request.getRemark()); + tag.setCreateBy(request.getOperName()); return tag; } - private Set selectExistingEpcCodes(List tagList) + private Set selectExistingEpcCodes(List epcCodeList) { - List epcCodes = collectCandidateEpcCodes(tagList); + List epcCodes = collectCandidateEpcCodes(epcCodeList); if (epcCodes.isEmpty()) { return new HashSet<>(); @@ -423,23 +476,35 @@ public class AmsRfidTagServiceImpl implements IAmsRfidTagService return existingEpcCodes; } - private List collectCandidateEpcCodes(List tagList) + private List collectImportEpcCodes(List tagList) { - Set epcCodes = new LinkedHashSet<>(); + List epcCodes = new ArrayList<>(); for (AmsRfidTag tag : tagList) { - if (StringUtils.isNotNull(tag)) + epcCodes.add(StringUtils.isNull(tag) ? null : tag.getEpcCode()); + } + return epcCodes; + } + + private List collectCandidateEpcCodes(List epcCodeList) + { + Set epcCodes = new LinkedHashSet<>(); + for (String rawEpcCode : epcCodeList) + { + String epcCode = StringUtils.trim(rawEpcCode); + if (StringUtils.isNotEmpty(epcCode)) { - String epcCode = StringUtils.trim(tag.getEpcCode()); - if (StringUtils.isNotEmpty(epcCode)) - { - epcCodes.add(epcCode); - } + epcCodes.add(epcCode); } } return new ArrayList<>(epcCodes); } + private boolean isDuplicateFailure(String message) + { + return message != null && (message.contains("重复") || message.contains("已存在")); + } + private void validateIdentityChange(AmsRfidTag current, AmsRfidTag incoming) { if (StringUtils.equals(RfidTagStatus.VOID, current.getTagStatus()) diff --git a/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsRfidTagServiceImplTest.java b/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsRfidTagServiceImplTest.java index 07c325d..b44045f 100644 --- a/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsRfidTagServiceImplTest.java +++ b/ruoyi-asset/src/test/java/com/ruoyi/asset/service/impl/AmsRfidTagServiceImplTest.java @@ -1,6 +1,7 @@ package com.ruoyi.asset.service.impl; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -18,6 +19,8 @@ import com.ruoyi.asset.constant.RfidBindStatus; import com.ruoyi.asset.constant.RfidTagStatus; import com.ruoyi.asset.domain.AmsRfidTag; import com.ruoyi.asset.domain.RfidBindingContext; +import com.ruoyi.asset.domain.RfidTagBatchCreateRequest; +import com.ruoyi.asset.domain.RfidTagBatchCreateResult; import com.ruoyi.asset.mapper.AmsRfidTagMapper; import com.ruoyi.asset.service.IAssetLifecycleService; import com.ruoyi.asset.service.IRfidBindingService; @@ -191,6 +194,44 @@ class AmsRfidTagServiceImplTest assertNull(tagCaptor.getValue().getAssetId()); } + /** 通用批量建档返回结构化行级结果,供PDA等非Excel入口复用同一套建档规则 */ + @Test + void batchCreateShouldReturnStructuredRowsAndPersistValidRows() + { + AmsRfidTag existingTag = new AmsRfidTag(); + existingTag.setEpcCode("EPC-OLD"); + when(amsRfidTagMapper.selectAmsRfidTagByEpcCodes(anyList())).thenReturn(List.of(existingTag)); + when(sysCodeRuleService.nextCode("RFID_TAG")).thenReturn("BQ20260702000002"); + when(amsRfidTagMapper.insertAmsRfidTag(any(AmsRfidTag.class))).thenReturn(1); + + RfidTagBatchCreateRequest request = new RfidTagBatchCreateRequest(); + request.setEpcCodes(List.of(" EPC-NEW ", "EPC-NEW", " ", "EPC-OLD")); + request.setRemark("PDA批量建档"); + request.setOperName("admin"); + + RfidTagBatchCreateResult result = service.batchCreateRfidTags(request); + + assertEquals(1, result.getSuccessCount()); + assertEquals(2, result.getDuplicateCount()); + assertEquals(1, result.getFailureCount()); + assertEquals(3, result.getTotalFailureCount()); + assertEquals(4, result.getRows().size()); + assertTrue(result.getRows().get(0).isSuccess()); + assertEquals("EPC-NEW", result.getRows().get(0).getEpcCode()); + assertEquals("BQ20260702000002", result.getRows().get(0).getTagCode()); + assertFalse(result.getRows().get(1).isSuccess()); + assertTrue(result.getRows().get(1).isDuplicate()); + assertTrue(result.getRows().get(1).getMessage().contains("首次出现于第1条")); + assertTrue(result.getRows().get(2).getMessage().contains("EPC编码不能为空")); + assertTrue(result.getRows().get(3).isDuplicate()); + + ArgumentCaptor tagCaptor = ArgumentCaptor.forClass(AmsRfidTag.class); + verify(amsRfidTagMapper).insertAmsRfidTag(tagCaptor.capture()); + assertEquals("EPC-NEW", tagCaptor.getValue().getEpcCode()); + assertEquals("PDA批量建档", tagCaptor.getValue().getRemark()); + assertEquals("admin", tagCaptor.getValue().getCreateBy()); + } + /** 单次导入设置上限,避免超大Excel长时间占用请求和编码规则流水锁 */ @Test void importShouldRejectTooManyRows()