From 82909efe6b2ba1f9588dbee568ba7f6164befb93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=A6=E9=B8=BF=E5=B1=95?= <18031053041@163.com> Date: Fri, 24 Jul 2026 18:49:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=BC=E5=85=A5=E7=89=A9=E6=96=99BUG?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/mhd/system/api/BmsServiceFeign.java | 2 +- .../com/mhd/system/api/OmsServiceFeign.java | 2 +- .../com/mhd/system/api/WlhyServiceFeign.java | 2 +- .../com/mhd/system/api/WmsServiceFeign.java | 2 +- .../com/mhd/system/api/YmsServiceFeign.java | 2 +- .../MaterialBaseInfoApplicationService.java | 241 +++++++++++++++++- .../materialBaseInfo/MaterialBaseInfoApi.java | 10 +- mhd_wms/src/main/resources/bootstrap.yml | 8 +- 8 files changed, 250 insertions(+), 19 deletions(-) diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/BmsServiceFeign.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/BmsServiceFeign.java index e18484e8d..91f2f8a11 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/BmsServiceFeign.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/BmsServiceFeign.java @@ -18,7 +18,7 @@ import java.util.Set; /** * bms财务结算系统feign调用接口 */ -@FeignClient(contextId = "BmsService", value = ServiceNameConstants.BMS_SERVICE, url = "http://10.102.192.3:8019",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) +@FeignClient(contextId = "BmsService", value = ServiceNameConstants.BMS_SERVICE, url = "http://10.33.0.109:8019",fallbackFactory = RemoteBmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) public interface BmsServiceFeign { diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/OmsServiceFeign.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/OmsServiceFeign.java index e34bb9cd4..03d48d2f4 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/OmsServiceFeign.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/OmsServiceFeign.java @@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.RequestBody; import java.util.List; import java.util.concurrent.CompletableFuture; -@FeignClient(contextId = "OmsService", value = ServiceNameConstants.OMS_SERVICE,url = "http://10.102.192.4:8017", fallbackFactory = RemoteOmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) +@FeignClient(contextId = "OmsService", value = ServiceNameConstants.OMS_SERVICE,url = "http://10.33.0.99:8017", fallbackFactory = RemoteOmsFeignFallbackFactory.class, configuration = FeignAutoConfiguration.class) public interface OmsServiceFeign { diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WlhyServiceFeign.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WlhyServiceFeign.java index e4a817238..ba9a45148 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WlhyServiceFeign.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WlhyServiceFeign.java @@ -26,7 +26,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; -@FeignClient(contextId = "commonWlhyServiceFeign",value = ServiceNameConstants.WLHY_SERVICE,url = "http://10.102.192.30:8014",configuration = FeignAutoConfiguration.class) +@FeignClient(contextId = "commonWlhyServiceFeign",value = ServiceNameConstants.WLHY_SERVICE,url = "http://10.33.0.129:8014",configuration = FeignAutoConfiguration.class) public interface WlhyServiceFeign { /** diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java index 743be7c29..688e5ea7c 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/WmsServiceFeign.java @@ -21,7 +21,7 @@ import java.util.Map; * @description: TODO * @date 2024/5/10 8:58 **/ -@FeignClient(contextId = "remoteWmsServiceFeign",value = ServiceNameConstants.WMS_SERVICE, url = "http://10.102.192.3:8016", fallbackFactory = RemoteWmsFallbackFactory.class, configuration = FeignAutoConfiguration.class) +@FeignClient(contextId = "remoteWmsServiceFeign",value = ServiceNameConstants.WMS_SERVICE, url = "http://10.33.0.109:8016", fallbackFactory = RemoteWmsFallbackFactory.class, configuration = FeignAutoConfiguration.class) public interface WmsServiceFeign { /** diff --git a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/YmsServiceFeign.java b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/YmsServiceFeign.java index dfac7337f..48d76d01e 100644 --- a/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/YmsServiceFeign.java +++ b/mhd-api/mhd-api-system/src/main/java/com/mhd/system/api/YmsServiceFeign.java @@ -9,7 +9,7 @@ import com.mhd.system.api.feign.FeignAutoConfiguration; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; -@FeignClient(contextId = "YmsService", value = ServiceNameConstants.YMS_SERVICE,fallbackFactory = RemoteBmsFeignFallbackFactory.class,url = "http://10.102.192.4:8018", configuration = FeignAutoConfiguration.class) +@FeignClient(contextId = "YmsService", value = ServiceNameConstants.YMS_SERVICE,fallbackFactory = RemoteBmsFeignFallbackFactory.class,url = "http://10.33.0.99:8018", configuration = FeignAutoConfiguration.class) public interface YmsServiceFeign { /** diff --git a/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java b/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java index 401e4e028..7dc41f623 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java +++ b/mhd_wms/src/main/java/com/mhd/wms/application/service/materialBaseInfo/MaterialBaseInfoApplicationService.java @@ -49,12 +49,15 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import java.io.File; +import java.io.FileOutputStream; import java.io.InputStream; import java.util.*; import java.util.stream.Collectors; @@ -436,8 +439,237 @@ public class MaterialBaseInfoApplicationService { return materialBaseInfoDomainService.getInfoByNoOrderReceipt(materialBaseInfoDO); } + /** - * 导入物料主数据(仅导入主表字段) + * 导入物料主数据(异步入口) + * + *

接口层调用此方法,将文件保存到临时目录后立即返回"异步处理中", + * 实际导入由 {@link #doImportData(File, String, LoginUser)} 异步执行, + * 避免大数据量时接口超时。

+ */ + public AjaxResult importDataAsync(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new ServiceException("上传文件不能为空"); + } + String fileName = file.getOriginalFilename(); + if (org.apache.commons.lang3.StringUtils.isBlank(fileName) + || (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx"))) { + throw new ServiceException("请上传xls或xlsx格式文件"); + } + + // 保存文件到临时目录 + String tmpDir = System.getProperty("java.io.tmpdir") + File.separator + "material_import"; + File dir = new File(tmpDir); + if (!dir.exists()) { + dir.mkdirs(); + } + String tmpFileName = System.currentTimeMillis() + "_" + fileName; + File tmpFile = new File(dir, tmpFileName); + try (FileOutputStream fos = new FileOutputStream(tmpFile)) { + fos.write(file.getBytes()); + } catch (Exception e) { + throw new ServiceException("文件保存失败:" + e.getMessage()); + } + + LoginUser loginUser = SecurityUtils.getLoginUser(); + // 异步执行导入 + doImportData(tmpFile, fileName, loginUser); + + return AjaxResult.success("导入任务已提交,正在后台异步处理,请稍后查看导入结果日志"); + } + + /** + * 异步执行物料主数据导入 + * + *

@Async 确保在独立线程中执行,不阻塞接口响应。 + * 每行独立事务,避免大事务锁表导致性能问题。

+ */ + @Async + public void doImportData(File tmpFile, String fileName, LoginUser loginUser) { + long startTime = System.currentTimeMillis(); + log.info("=== 物料主数据异步导入开始,文件:{},大小:{}KB ===", fileName, tmpFile.length() / 1024); + try { + List rows; + try (InputStream is = new java.io.FileInputStream(tmpFile)) { + rows = MaterialBaseInfoImportExcelUtil.importRows(is); + } catch (Exception e) { + log.error("物料主数据异步导入失败 - 文件解析异常:{}", e.getMessage(), e); + return; + } + + if (CollectionUtils.isEmpty(rows)) { + log.warn("物料主数据异步导入结束 - 导入数据为空"); + return; + } + + Long loginOrgId = null; + Long loginTopOrgId = null; + if (loginUser != null && loginUser.getUserPo() != null) { + loginOrgId = loginUser.getUserPo().getOrganizationId(); + loginTopOrgId = loginUser.getUserPo().getTopOrganizationId(); + } + + StringBuilder errors = new StringBuilder(); + Set duplicateInFile = new HashSet<>(); + int successCount = 0; + int totalCount = rows.size(); + + // 包装规格缓存:避免 5000 行数据对 system 服务发起 5000 次重复远程调用 + Map> packCache = new HashMap<>(); + + for (int i = 0; i < rows.size(); i++) { + MaterialBaseInfoDTO row = rows.get(i); + int rowNum = i + 2; + if (row == null) { + continue; + } + try { + if (StringUtils.isBlank(row.getMaterialCode())) { + errors.append("第").append(rowNum).append("行:物料编码不能为空;"); + continue; + } + if (StringUtils.isBlank(row.getMaterialName())) { + errors.append("第").append(rowNum).append("行:物料名称不能为空;"); + continue; + } + if (row.getNullify() == null) { + row.setNullify(2); + } + if (row.getCommon() == null) { + row.setCommon(2); + } + if (StringUtils.isBlank(row.getHsCode()) && StringUtils.isNotBlank(row.getHsCodeImport())) { + row.setHsCode(row.getHsCodeImport().trim()); + } + normalizeImportBarCodeWhenDuplicateOfHs(row); + fillIdsFromExistingMasterData(row); + fillOrganizationFromShipperIfNeeded(row); + if (!isValidOrgId(row.getOrganizationId()) && isValidOrgId(loginOrgId)) { + row.setOrganizationId(loginOrgId); + } + if (row.getTopOrganizationId() == null && isValidOrgId(loginTopOrgId)) { + row.setTopOrganizationId(loginTopOrgId); + } + fillOrganizationName(row, loginUser); + + String shipperCode = StringUtils.defaultString(row.getShipperCode(), ""); + String duplicateKey = row.getOrganizationId() + "|" + shipperCode + "|" + row.getMaterialCode(); + if (!duplicateInFile.add(duplicateKey)) { + errors.append("第").append(rowNum).append("行:文件内存在重复物料(组织+货主编码+物料编码);"); + continue; + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MaterialBaseInfo::getDelFlag, 1) + .eq(MaterialBaseInfo::getOrganizationId, row.getOrganizationId()) + .eq(MaterialBaseInfo::getMaterialCode, row.getMaterialCode()); + if (StringUtils.isBlank(row.getShipperCode())) { + wrapper.and(w -> w.isNull(MaterialBaseInfo::getShipperCode).or().eq(MaterialBaseInfo::getShipperCode, "")); + } else { + wrapper.eq(MaterialBaseInfo::getShipperCode, row.getShipperCode()); + } + if (materialBaseInfoService.count(wrapper) > 0) { + errors.append("第").append(rowNum).append("行:物料已存在(组织+货主编码+物料编码);"); + continue; + } + fillClassifyTypeAndUnit(row); + + // 包装规格查询(带本地缓存,避免 5000 行对 system 服务发起 5000 次远程调用) + String packCacheKey = row.getOrganizationId() + "|" + StringUtils.defaultString(row.getPackName()); + Map cachedPack = packCache.get(packCacheKey); + if (cachedPack == null) { + Object result = systemServiceFeign.getPackList(null, row.getPackName(), row.getOrganizationId()); + if (result == null) { + // 缓存空结果,避免重复查询不存在的包装规格 + packCache.put(packCacheKey, Collections.emptyMap()); + errors.append("第").append(rowNum).append("行:包装规格查询无返回,请稍后重试或检查系统服务;"); + continue; + } + Map resultMap = JSON.parseObject(JSONObject.toJSONString(result), new TypeReference>() {}); + List> rowsPack = extractTableDataRows(resultMap); + if (CollectionUtils.isEmpty(rowsPack)) { + packCache.put(packCacheKey, Collections.emptyMap()); + if (StringUtils.isNotBlank(row.getPackName())) { + errors.append("第").append(rowNum).append("行:包装规格「").append(row.getPackName()).append("」不存在或未在系统中维护;"); + } else { + errors.append("第").append(rowNum).append("行:包装规格未填写,或系统中无匹配的包装数据;"); + } + continue; + } + cachedPack = rowsPack.get(0); + packCache.put(packCacheKey, cachedPack); + } + if (cachedPack.isEmpty()) { + errors.append("第").append(rowNum).append("行:包装规格不存在(来自缓存);"); + continue; + } + if (cachedPack == null) { + errors.append("第").append(rowNum).append("行:包装规格查询结果为空;"); + continue; + } + String packCode = cachedPack.get("packCode") == null ? "" : String.valueOf(cachedPack.get("packCode")); + row.setPackCode(packCode); + String packId = cachedPack.get("packId") == null ? "" : String.valueOf(cachedPack.get("packId")); + if (StringUtils.isBlank(packId) || !StringUtils.isNumeric(packId)) { + errors.append("第").append(rowNum).append("行:包装规格数据异常(缺少包装ID);"); + continue; + } + row.setPackId(Long.valueOf(packId)); + String unitName = cachedPack.get("unitName") == null ? "" : String.valueOf(cachedPack.get("unitName")); + row.setUnitCode("EA"); + row.setUnitName(unitName); + + // 每行独立事务写入 + MaterialBaseInfoDO materialBaseInfoDO = new MaterialBaseInfoDO(); + BeanUtils.copyProperties(row, materialBaseInfoDO); + materialBaseInfoDO.setMaterialGoodsRule(buildImportMaterialGoodsRule(row)); + materialBaseInfoDO.setMaterialCommon(null); + materialBaseInfoDO.setMaterialWarehouseControl(null); + materialBaseInfoDO.setMaterialBarCodeList(buildImportBarCodeList(row)); + insert(materialBaseInfoDO); + Long materialBaseInfoId = materialBaseInfoDO.getMaterialBaseInfoId(); + + MaterialCommonDO materialCommonDO = materialBaseInfoDO.getMaterialCommon(); + if (materialCommonDO != null) { + materialCommonDO.setMaterialBaseInfoId(materialBaseInfoId); + materialCommonDomainService.insert(materialCommonDO); + } + MaterialWarehouseControlDO materialWarehouseControlDO = materialBaseInfoDO.getMaterialWarehouseControl(); + if (materialWarehouseControlDO != null) { + materialWarehouseControlDO.setMaterialBaseInfoId(materialBaseInfoId); + materialWarehouseControlDomainService.insert(materialWarehouseControlDO); + } + materialInventoryWarningDomainService.batchInsertOrUpdate(materialBaseInfoDO); + materialBarCodeDomainService.batchInsertOrUpdate(materialBaseInfoDO); + successCount++; + } catch (Exception e) { + log.error("物料主数据异步导入 - 第{}行处理异常", rowNum, e); + errors.append("第").append(rowNum).append("行:处理异常-").append(e.getMessage()).append(";"); + } + } + + long costTime = System.currentTimeMillis() - startTime; + if (errors.length() > 0) { + log.warn("=== 物料主数据异步导入完成,文件:{},总数:{},成功:{},失败:{},耗时:{}ms,失败原因:{} ===", + fileName, totalCount, successCount, totalCount - successCount, costTime, errors); + } else { + log.info("=== 物料主数据异步导入完成,文件:{},总数:{},成功:{},耗时:{}ms ===", + fileName, totalCount, successCount, costTime); + } + } finally { + // 清理临时文件 + try { + if (tmpFile.exists()) { + tmpFile.delete(); + } + } catch (Exception e) { + log.warn("物料主数据异步导入 - 临时文件清理失败:{}", e.getMessage()); + } + } + } + + /** + * 导入物料主数据(仅导入主表字段)- 原同步方法保留,供其他场景调用 */ @Transactional(rollbackFor = Exception.class) public AjaxResult importData(MultipartFile file) { @@ -496,7 +728,6 @@ public class MaterialBaseInfoApplicationService { row.setHsCode(row.getHsCodeImport().trim()); } normalizeImportBarCodeWhenDuplicateOfHs(row); - // 客户模板通常只有「货主名称」无「货主id」:须先按名称解析货主,再回填组织;勿提前写入登录组织以免组织校验拦截货主匹配 fillIdsFromExistingMasterData(row); fillOrganizationFromShipperIfNeeded(row); if (!isValidOrgId(row.getOrganizationId()) && isValidOrgId(loginOrgId)) { @@ -567,25 +798,20 @@ public class MaterialBaseInfoApplicationService { materialBaseInfoDO.setMaterialCommon(null); materialBaseInfoDO.setMaterialWarehouseControl(null); materialBaseInfoDO.setMaterialBarCodeList(buildImportBarCodeList(row)); - // 商品规则由 buildImportMaterialGoodsRule + insert() 统一写入,勿再单独 insert,避免 MATERIAL_GOODS_RULE 重复 insert(materialBaseInfoDO); Long materialBaseInfoId = materialBaseInfoDO.getMaterialBaseInfoId(); - //公共信息 MaterialCommonDO materialCommonDO = materialBaseInfoDO.getMaterialCommon(); if (materialCommonDO != null) { materialCommonDO.setMaterialBaseInfoId(materialBaseInfoId); materialCommonDomainService.insert(materialCommonDO); } - //仓库控制信息 MaterialWarehouseControlDO materialWarehouseControlDO = materialBaseInfoDO.getMaterialWarehouseControl(); if (materialWarehouseControlDO != null) { materialWarehouseControlDO.setMaterialBaseInfoId(materialBaseInfoId); materialWarehouseControlDomainService.insert(materialWarehouseControlDO); } - //库存预警 materialInventoryWarningDomainService.batchInsertOrUpdate(materialBaseInfoDO); - //条码信息 materialBarCodeDomainService.batchInsertOrUpdate(materialBaseInfoDO); successCount++; } @@ -596,6 +822,7 @@ public class MaterialBaseInfoApplicationService { return AjaxResult.success("导入成功,共" + successCount + "条"); } + /** * 使用已存在主数据按名称/编码反查并回填ID */ diff --git a/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/materialBaseInfo/MaterialBaseInfoApi.java b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/materialBaseInfo/MaterialBaseInfoApi.java index c780d3ea3..252bb8088 100644 --- a/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/materialBaseInfo/MaterialBaseInfoApi.java +++ b/mhd_wms/src/main/java/com/mhd/wms/interfaces/facadeApi/materialBaseInfo/MaterialBaseInfoApi.java @@ -106,12 +106,16 @@ public class MaterialBaseInfoApi extends BaseController { } /** - * 导入物料主数据 + * 导入物料主数据(异步执行) + * + *

数据量大时同步导入会导致接口超时,改为异步模式: + * 接口立即返回"已提交异步处理",后台线程执行实际导入, + * 导入结果通过日志输出。

*/ - @ApiOperation("导入物料主数据") + @ApiOperation("导入物料主数据(异步)") @PostMapping("/importData") public AjaxResult importData(@RequestParam("file") MultipartFile file) { - return materialBaseInfoApplicationService.importData(file); + return materialBaseInfoApplicationService.importDataAsync(file); } @ApiOperation("物料管理更新推送信息") diff --git a/mhd_wms/src/main/resources/bootstrap.yml b/mhd_wms/src/main/resources/bootstrap.yml index fc7380c20..39eb710cb 100644 --- a/mhd_wms/src/main/resources/bootstrap.yml +++ b/mhd_wms/src/main/resources/bootstrap.yml @@ -14,18 +14,18 @@ spring: nacos: discovery: # server-addr: 127.0.0.1:8848 -# server-addr: 10.33.0.129:6010 + server-addr: 10.33.0.129:6010 # 线上测试环境配置 容器名+端口号 - server-addr: 10.102.192.30:6848 +# server-addr: 10.102.192.30:6848 username: nacos password: manhuoda@2023 #线上正式环境 # server-addr: 10.102.192.105:6848 config: # server-addr: 127.0.0.1:8848 -# server-addr: 10.33.0.129:6010 + server-addr: 10.33.0.129:6010 # 线上测试环境配置 容器名+端口号 - server-addr: 10.102.192.30:6848 +# server-addr: 10.102.192.30:6848 username: nacos password: manhuoda@2023 #线上正式环境