库位修改以及批量导入 不能重复的校验

This commit is contained in:
2026-04-23 10:44:24 +08:00
parent 0671f405f6
commit 26bc599e26
3 changed files with 177 additions and 19 deletions

View File

@@ -99,5 +99,18 @@ public interface PcdeDetailMapper
String selectEncodedIdByPcodeAndWarehouse(@Param("pcode") String pcode, String selectEncodedIdByPcodeAndWarehouse(@Param("pcode") String pcode,
@Param("warehouseCode") String warehouseCode); @Param("warehouseCode") String warehouseCode);
/**
* 新增前查重
* 同一仓库、同一场景、同一库位编码,不允许重复
*/
public PcdeDetail selectByWarehouseSceneAndPcode(PcdeDetail pcdeDetail);
/**
* 修改前查重(排除自己)
*/
public PcdeDetail selectRepeatByWarehouseSceneAndPcode(PcdeDetail pcdeDetail);
/**
* 批量查已存在
*/
List<PcdeDetail> selectExistsForImport(@Param("list") List<PcdeDetail> list);
} }

View File

@@ -61,6 +61,15 @@ public class PcdeDetailServiceImpl implements IPcdeDetailService
*/ */
@Override @Override
public int insertPcdeDetail(PcdeDetail pcdeDetail) { public int insertPcdeDetail(PcdeDetail pcdeDetail) {
pcdeDetail.setWarehouseCode(pcdeDetail.getWarehouseCode().trim());
pcdeDetail.setScene(pcdeDetail.getScene().trim());
pcdeDetail.setPcode(pcdeDetail.getPcode().trim());
// 查重:同一仓库、同一场景、同一库位编码
PcdeDetail exist = pcdeDetailMapper.selectByWarehouseSceneAndPcode(pcdeDetail);
if (exist != null)
{
throw new RuntimeException("当前仓库、当前场景下已存在该库位编码,请勿重复新增");
}
// 原始 locationCode // 原始 locationCode
String locationCode = pcdeDetail.getPcode(); String locationCode = pcdeDetail.getPcode();
@@ -86,6 +95,16 @@ public class PcdeDetailServiceImpl implements IPcdeDetailService
@Override @Override
public int updatePcdeDetail(PcdeDetail pcdeDetail) public int updatePcdeDetail(PcdeDetail pcdeDetail)
{ {
pcdeDetail.setWarehouseCode(pcdeDetail.getWarehouseCode().trim());
pcdeDetail.setScene(pcdeDetail.getScene().trim());
pcdeDetail.setPcode(pcdeDetail.getPcode().trim());
// 查重:排除自己
PcdeDetail exist = pcdeDetailMapper.selectRepeatByWarehouseSceneAndPcode(pcdeDetail);
if (exist != null)
{
throw new RuntimeException("当前仓库、当前场景下已存在该库位编码,请勿重复修改");
}
return pcdeDetailMapper.updatePcdeDetail(pcdeDetail); return pcdeDetailMapper.updatePcdeDetail(pcdeDetail);
} }
@@ -155,49 +174,96 @@ public class PcdeDetailServiceImpl implements IPcdeDetailService
final Date now = DateUtils.getNowDate(); final Date now = DateUtils.getNowDate();
// 1) 清洗 + 本地生成 encodedId逐字符→Unicode→HEX大写→拼接 // 1. 清洗数据
List<PcdeDetail> cleaned = new ArrayList<>(pcdeList.size()); List<PcdeDetail> cleaned = new ArrayList<>(pcdeList.size());
// 文件内去重:同一仓库+同一场景+同一库位编码
Set<String> importKeySet = new HashSet<>();
int innerRepeatCount = 0;
for (PcdeDetail d : pcdeList) { for (PcdeDetail d : pcdeList) {
if (d == null) continue; if (d == null) {
String p = d.getPcode() == null ? null : d.getPcode().trim();
if (StringUtils.isEmpty(p)) {
// 跳过空 pcode
continue; continue;
} }
d.setPcode(p);
d.setEncodedId(toHexStringFromPcode(p));
// 审计字段与你实体保持一致createdBy/createdAt/updatedBy/updatedAt String warehouseCode = d.getWarehouseCode() == null ? null : d.getWarehouseCode().trim();
String scene = d.getScene() == null ? null : d.getScene().trim();
String pcode = d.getPcode() == null ? null : d.getPcode().trim();
if (StringUtils.isEmpty(warehouseCode) || StringUtils.isEmpty(scene) || StringUtils.isEmpty(pcode)) {
continue;
}
String repeatKey = warehouseCode + "_" + scene + "_" + pcode;
if (importKeySet.contains(repeatKey)) {
innerRepeatCount++;
continue;
}
importKeySet.add(repeatKey);
d.setWarehouseCode(warehouseCode);
d.setScene(scene);
d.setPcode(pcode);
d.setEncodedId(toHexStringFromPcode(pcode));
d.setCreatedBy(operName); d.setCreatedBy(operName);
d.setCreatedAt(now); d.setCreatedAt(now);
d.setUpdatedBy(operName); d.setUpdatedBy(operName);
d.setUpdatedAt(now); d.setUpdatedAt(now);
// 业务兜底 if (d.getIsDelete() == null) {
if (d.getIsDelete() == null) d.setIsDelete("0"); d.setIsDelete("0");
}
cleaned.add(d); cleaned.add(d);
} }
if (cleaned.isEmpty()) { if (cleaned.isEmpty()) {
throw new ServiceException("有效的库位编号为空,请检查导入文件!"); throw new ServiceException("有效的导入数据为空,请检查导入文件!");
} }
// 2) 批量写库(分片,避免 SQL 过大;配合 uk_pcode + INSERT IGNORE 实现幂等忽略) // 2. 批量查数据库已存在数据
List<PcdeDetail> existList = pcdeDetailMapper.selectExistsForImport(cleaned);
Set<String> dbExistKeySet = new HashSet<>();
if (!CollectionUtils.isEmpty(existList)) {
for (PcdeDetail item : existList) {
String key = item.getWarehouseCode() + "_" + item.getScene() + "_" + item.getPcode();
dbExistKeySet.add(key);
}
}
// 3. 过滤数据库中已存在的数据
List<PcdeDetail> needInsertList = new ArrayList<>();
int dbRepeatCount = 0;
for (PcdeDetail d : cleaned) {
String key = d.getWarehouseCode() + "_" + d.getScene() + "_" + d.getPcode();
if (dbExistKeySet.contains(key)) {
dbRepeatCount++;
continue;
}
needInsertList.add(d);
}
if (needInsertList.isEmpty()) {
return String.format("导入完成:共读取 %d 条,文件内重复 %d 条,数据库已存在 %d 条,本次成功导入 0 条。",
pcdeList.size(), innerRepeatCount, dbRepeatCount);
}
// 4. 分批插入
final int batchSize = 1000; final int batchSize = 1000;
int totalTried = cleaned.size();
int totalInserted = 0; int totalInserted = 0;
for (int i = 0; i < cleaned.size(); i += batchSize) { for (int i = 0; i < needInsertList.size(); i += batchSize) {
int end = Math.min(i + batchSize, cleaned.size()); int end = Math.min(i + batchSize, needInsertList.size());
List<PcdeDetail> slice = cleaned.subList(i, end); List<PcdeDetail> slice = needInsertList.subList(i, end);
int affected = pcdeDetailMapper.batchInsertIgnore(slice); int affected = pcdeDetailMapper.batchInsertIgnore(slice);
totalInserted += affected; totalInserted += affected;
} }
int skipped = totalTried - totalInserted; return String.format("导入完成:共读取 %d 条,文件内重复 %d 条,数据库已存在 %d 条,成功导入 %d 条。",
return String.format("导入完成:共读取 %d 条,成功导入 %d 条,跳过(可能重复) %d 条。", pcdeList.size(), innerRepeatCount, dbRepeatCount, totalInserted);
totalTried, totalInserted, skipped);
} }
/** 将 pcode 按字符转大写十六进制(与新增接口保持一致) */ /** 将 pcode 按字符转大写十六进制(与新增接口保持一致) */

View File

@@ -382,5 +382,84 @@
#{id} #{id}
</foreach> </foreach>
</delete> </delete>
<!-- 新增前查重 -->
<select id="selectByWarehouseSceneAndPcode" parameterType="PcdeDetail" resultMap="PcdeDetailResult">
SELECT
id,
pcode,
scene,
parent_warehouse_code,
parent_warehouse_name,
warehouse_code,
warehouse_name,
encoded_id,
tag,
remark,
is_delete,
created_by,
created_at,
updated_by,
updated_at
FROM pcde_detail
WHERE is_delete = 0
AND warehouse_code = #{warehouseCode}
AND scene = #{scene}
AND pcode = #{pcode}
LIMIT 1
</select>
<!-- 修改前查重排除当前id -->
<select id="selectRepeatByWarehouseSceneAndPcode" parameterType="PcdeDetail" resultMap="PcdeDetailResult">
SELECT
id,
pcode,
scene,
parent_warehouse_code,
parent_warehouse_name,
warehouse_code,
warehouse_name,
encoded_id,
tag,
remark,
is_delete,
created_by,
created_at,
updated_by,
updated_at
FROM pcde_detail
WHERE is_delete = 0
AND warehouse_code = #{warehouseCode}
AND scene = #{scene}
AND pcode = #{pcode}
AND id != #{id}
LIMIT 1
</select>
<select id="selectExistsForImport" resultMap="PcdeDetailResult">
SELECT
id,
pcode,
scene,
parent_warehouse_code,
parent_warehouse_name,
warehouse_code,
warehouse_name,
encoded_id,
tag,
remark,
is_delete,
created_by,
created_at,
updated_by,
updated_at
FROM pcde_detail
WHERE is_delete = '0'
AND
<foreach collection="list" item="item" separator=" OR " open="(" close=")">
(
warehouse_code = #{item.warehouseCode}
AND scene = #{item.scene}
AND pcode = #{item.pcode}
)
</foreach>
</select>
</mapper> </mapper>