feat(rfid): 添加RFID标签批量建档功能

- 新增RfidTagBatchCreateRequest、RfidTagBatchCreateResult和RfidTagBatchCreateRow域对象
- 实现batchCreateRfidTags方法支持批量创建RFID标签
- 重构导入逻辑使用统一的批量建档规则处理
- 添加重复EPC检测和行级错误处理机制
- 支持PDA等非Excel入口复用建档规则
- 完善单元测试验证批量建档功能
main
yangk 3 days ago
parent 5c3a7f6ce3
commit 51cae14152

@ -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<String> epcCodes = new ArrayList<>();
/** 建档备注 */
private String remark;
/** 操作登录账号 */
private String operName;
public List<String> getEpcCodes()
{
return epcCodes;
}
public void setEpcCodes(List<String> 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;
}
}

@ -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<RfidTagBatchCreateRow> 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<RfidTagBatchCreateRow> getRows()
{
return rows;
}
public void setRows(List<RfidTagBatchCreateRow> rows)
{
this.rows = rows;
}
}

@ -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;
}
}

@ -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;
/**
* RFIDService
@ -52,6 +54,14 @@ public interface IAmsRfidTagService
*/
public String importAmsRfidTag(List<AmsRfidTag> tagList, String operName);
/**
* RFID
*
* @param request
* @return
*/
public RfidTagBatchCreateResult batchCreateRfidTags(RfidTagBatchCreateRequest request);
/**
* RFID
*

@ -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<String, Integer> firstRowByEpc = new LinkedHashMap<>();
Set<String> 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<String, Integer> firstRowByEpc = new LinkedHashMap<>();
Set<String> 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("<br/>" + successNum + "、EPC " + tag.getEpcCode()
+ " 导入成功,标签编码:" + tag.getTagCode());
successMsg.append("<br/>" + successNum + "、EPC " + row.getEpcCode()
+ " 导入成功,标签编码:" + row.getTagCode());
}
catch (Exception e)
else
{
failureNum++;
failureMsg.append("<br/>" + failureNum + "、" + rowName + " 导入失败:" + e.getMessage());
failureMsg.append("<br/>" + 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<String, Integer> firstRowByEpc,
Set<String> 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<String, Integer> firstRowByEpc,
Set<String> 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<String> selectExistingEpcCodes(List<AmsRfidTag> tagList)
private Set<String> selectExistingEpcCodes(List<String> epcCodeList)
{
List<String> epcCodes = collectCandidateEpcCodes(tagList);
List<String> epcCodes = collectCandidateEpcCodes(epcCodeList);
if (epcCodes.isEmpty())
{
return new HashSet<>();
@ -423,23 +476,35 @@ public class AmsRfidTagServiceImpl implements IAmsRfidTagService
return existingEpcCodes;
}
private List<String> collectCandidateEpcCodes(List<AmsRfidTag> tagList)
private List<String> collectImportEpcCodes(List<AmsRfidTag> tagList)
{
Set<String> epcCodes = new LinkedHashSet<>();
List<String> epcCodes = new ArrayList<>();
for (AmsRfidTag tag : tagList)
{
if (StringUtils.isNotNull(tag))
epcCodes.add(StringUtils.isNull(tag) ? null : tag.getEpcCode());
}
return epcCodes;
}
private List<String> collectCandidateEpcCodes(List<String> epcCodeList)
{
Set<String> 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())

@ -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<AmsRfidTag> 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()

Loading…
Cancel
Save