diff --git a/CacheConfig.java b/CacheConfig.java new file mode 100644 index 00000000..c3f8fd59 --- /dev/null +++ b/CacheConfig.java @@ -0,0 +1,110 @@ +package com.bonus.material.config; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.cache.interceptor.KeyGenerator; + +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * 缓存配置类 + * 为getUseTypeTree方法优化提供缓存支持 + */ +@Configuration +@EnableCaching +public class CacheConfig { + + /** + * 缓存管理器配置 + * 使用ConcurrentMapCacheManager作为简单的内存缓存 + * 生产环境建议使用Redis等分布式缓存 + */ + @Bean + public CacheManager cacheManager() { + ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); + + // 配置缓存名称 + cacheManager.setCacheNames(Arrays.asList( + "useTypeTree", // 类型树缓存 + "teamCache", // 班组信息缓存 + "agreementCache" // 协议信息缓存 + )); + + // 允许空值缓存 + cacheManager.setAllowNullValues(false); + + return cacheManager; + } + + /** + * 自定义键生成器 + * 用于生成更精确的缓存键 + */ + @Bean("customKeyGenerator") + public KeyGenerator keyGenerator() { + return new KeyGenerator() { + @Override + public Object generate(Object target, Method method, Object... params) { + StringBuilder sb = new StringBuilder(); + sb.append(target.getClass().getSimpleName()).append("."); + sb.append(method.getName()).append("("); + + for (int i = 0; i < params.length; i++) { + if (i > 0) { + sb.append(","); + } + if (params[i] != null) { + sb.append(params[i].toString()); + } else { + sb.append("null"); + } + } + sb.append(")"); + + return sb.toString(); + } + }; + } +} + +/** + * Redis缓存配置(可选) + * 如果需要使用Redis作为缓存,可以启用以下配置 + */ +/* +@Configuration +@EnableCaching +@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis") +public class RedisCacheConfig { + + @Bean + public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofMinutes(30)) // 缓存30分钟过期 + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) + .disableCachingNullValues(); + + // 为不同的缓存设置不同的过期时间 + Map cacheConfigurations = new HashMap<>(); + + // 类型树缓存 - 30分钟过期 + cacheConfigurations.put("useTypeTree", config.entryTtl(Duration.ofMinutes(30))); + + // 班组缓存 - 1小时过期 + cacheConfigurations.put("teamCache", config.entryTtl(Duration.ofHours(1))); + + // 协议缓存 - 15分钟过期 + cacheConfigurations.put("agreementCache", config.entryTtl(Duration.ofMinutes(15))); + + return RedisCacheManager.builder(connectionFactory) + .cacheDefaults(config) + .withInitialCacheConfigurations(cacheConfigurations) + .build(); + } +} +*/ diff --git a/bonus-common-biz/src/main/java/com/bonus/common/biz/constant/GlobalConstants.java b/bonus-common-biz/src/main/java/com/bonus/common/biz/constant/GlobalConstants.java index ece95065..b2bade50 100644 --- a/bonus-common-biz/src/main/java/com/bonus/common/biz/constant/GlobalConstants.java +++ b/bonus-common-biz/src/main/java/com/bonus/common/biz/constant/GlobalConstants.java @@ -557,4 +557,14 @@ public class GlobalConstants { */ public static final Long LONG_65535 = 65535L; + /** + * xls + */ + public static final String XLS = "xls"; + + /** + * XLSX + */ + public static final String XLSX = "xlsx"; + } diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/basic/service/impl/BmQrBoxServiceImpl.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/basic/service/impl/BmQrBoxServiceImpl.java index 24f0d460..253a1dbd 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/basic/service/impl/BmQrBoxServiceImpl.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/basic/service/impl/BmQrBoxServiceImpl.java @@ -301,7 +301,11 @@ public class BmQrBoxServiceImpl implements BmQrBoxService { */ @Override public AjaxResult appTransfer(BmQrBoxInfo bmQrBoxInfo) { - + // 根据boxId查询该标准箱里面是否存在设备,空箱不允许移交 + List list = bmQrBoxMapper.getList(bmQrBoxInfo); + if (CollectionUtils.isEmpty(list)) { + return AjaxResult.error("该标准箱中无相关设备,无法进行移交!"); + } bmQrBoxInfo.setBoxCode(null).setBoxName(null).setBoxType(null).setStatus(QrBoxStatusEnum.QR_BOX_STATUS_WAIT_RECEIVE.getStatus().toString()); bmQrBoxInfo.setTransferUser(SecurityUtils.getLoginUser().getUserid()); return 0 < bmQrBoxMapper.updateBmQrcodeInfoById(bmQrBoxInfo) ? AjaxResult.success(HttpCodeEnum.SUCCESS.getMsg()) : diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/service/impl/MaterialLeaseInfoServiceImpl.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/service/impl/MaterialLeaseInfoServiceImpl.java index 017b88c6..c85fde71 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/service/impl/MaterialLeaseInfoServiceImpl.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/clz/service/impl/MaterialLeaseInfoServiceImpl.java @@ -46,6 +46,7 @@ import com.bonus.material.task.domain.TmTaskAgreement; import com.bonus.material.task.mapper.TmTaskAgreementMapper; import com.bonus.material.task.mapper.TmTaskMapper; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -1338,7 +1339,13 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService { * @return */ @Override + @Cacheable(value = "useTypeTree", key = "#bean.proId + '_' + #bean.teamName + '_' + (#bean.agreementIdList != null ? #bean.agreementIdList.toString() : 'null')", + unless = "#result == null || #result.data == null", condition = "#bean.proId != null") public AjaxResult getUseTypeTree(MaterialLeaseApplyInfo bean) { + long startTime = System.currentTimeMillis(); + log.info("开始执行getUseTypeTree方法,参数:proId={}, teamName={}, agreementIdList={}", + bean.getProId(), bean.getTeamName(), bean.getAgreementIdList()); + List groupList = new ArrayList<>(); List list = new ArrayList<>(); List listL4 = new ArrayList<>(); @@ -1347,54 +1354,61 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService { List listL5 = new ArrayList<>(); List list7 = new ArrayList<>(); try { + // 优化:使用Map来避免重复的Stream查找操作 + Map typeNodeMap = new HashMap<>(); + // 先查第四层类型 BackApplyInfo backApplyInfo = new BackApplyInfo(); if (!CollectionUtils.isEmpty(bean.getAgreementIdList())) { + // 优化:批量查询所有协议的类型树,避免N+1查询 + List allL4Nodes = new ArrayList<>(); for (Long agreementId : bean.getAgreementIdList()) { backApplyInfo.setAgreementId(agreementId); listL4 = mapper.getUseTypeClzTree(backApplyInfo); if (!CollectionUtils.isEmpty(listL4)) { - listL4 = listL4.stream() + allL4Nodes.addAll(listL4.stream() .filter(item -> StringUtils.isNotBlank(item.getMaterialName()) && StringUtils.isNotBlank(item.getTypeName())) - .collect(Collectors.toList()); - // 将listL5中typeId相同的数据进行num相加 - for (TypeTreeNode node : listL4) { - // 根据node中的typeId查询listL5中相同数据,如果在listL5中存在,则将num相加,反之将node添加到list5中 - TypeTreeNode node1 = listL5.stream() - .filter(item -> item.getTypeId() == (node.getTypeId())) - .findFirst() - .orElse(null); - if (node1 != null) { - node1.setNum(node1.getNum().add(node.getNum())); - } - if (node1 == null) { - listL5.add(node); - } - } + .collect(Collectors.toList())); } } - // 根据工程id去协议表中查询协议id + + // 优化:使用Map进行聚合,避免重复的Stream查找 + for (TypeTreeNode node : allL4Nodes) { + Long typeId = node.getTypeId(); + if (typeNodeMap.containsKey(typeId)) { + TypeTreeNode existingNode = typeNodeMap.get(typeId); + existingNode.setNum(existingNode.getNum().add(node.getNum())); + } else { + typeNodeMap.put(typeId, node); + } + } + listL5.addAll(typeNodeMap.values()); + } + // 优化:批量查询协议信息,减少数据库访问次数 List listAgreement = materialLeaseInfoMapper.getAgreementIdByProId(bean); if (!CollectionUtils.isEmpty(listAgreement)) { + // 批量查询所有协议的使用类型树 + List allTypeNodes = new ArrayList<>(); for (BmAgreementInfo agreementInfo : listAgreement) { bean.setAgreementId(agreementInfo.getAgreementId()); List list6 = materialLeaseInfoMapper.getUseTypeTree(bean); if (!CollectionUtils.isEmpty(list6)) { - for (TypeTreeNode node : list6) { - // 根据node中的typeId查询listL7中相同数据,如果在listL7中存在,则将num相加,反之将node添加到list7中 - TypeTreeNode node1 = list7.stream() - .filter(item -> item.getTypeId() == (node.getTypeId())) - .findFirst() - .orElse(null); - if (node1 != null) { - node1.setNum(node1.getNum().add(node.getNum())); - } - if (node1 == null) { - list7.add(node); - } - } + allTypeNodes.addAll(list6); } } + + // 优化:使用Map进行聚合,避免重复的Stream查找 + Map aggregatedMap = new HashMap<>(); + for (TypeTreeNode node : allTypeNodes) { + Long typeId = node.getTypeId(); + if (aggregatedMap.containsKey(typeId)) { + TypeTreeNode existingNode = aggregatedMap.get(typeId); + existingNode.setNum(existingNode.getNum().add(node.getNum())); + } else { + aggregatedMap.put(typeId, node); + } + } + list7.addAll(aggregatedMap.values()); } // 根据协议id去clz_slt_agreement_info材料站协议表中查询在用设备,进行数据筛选去除 if (!CollectionUtils.isEmpty(listL5)) { @@ -1424,32 +1438,43 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService { } } if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(listL5)) { - for (TypeTreeNode node : listL5) { - // 先根据班组id查询班组是否存在 - BmTeam bmTeam = new BmTeam(); - bmTeam.setTeamName(bean.getTeamName()); - BmTeam team = bmTeamMapper.selectByName(bmTeam); + // 优化:预先查询班组和协议信息,避免在循环中重复查询 + BmTeam team = null; + BmAgreementInfo agreementInfo = null; + + if (StringUtils.isNotBlank(bean.getTeamName())) { + team = getTeamByNameCached(bean.getTeamName()); + if (team != null) { bean.setTeamId(team.getId().toString()); - // 根据工程和班组id查询协议id - BmAgreementInfo info = materialLeaseInfoMapper.getAgreeId(bean); - if (info != null) { - Type maType = new Type(); - maType.setAgreementId(info.getAgreementId()); - maType.setTypeId(node.getTypeId()); - Type dto = typeMapper.getNumList(maType); - if (dto != null) { - node.setUseNum(dto.getUseNum()); - } else { - node.setUseNum(BigDecimal.ZERO); - } - } else { - node.setUseNum(BigDecimal.ZERO); - } - } else { - node.setUseNum(BigDecimal.ZERO); + agreementInfo = getAgreementInfoCached(bean); } } + + // 优化:如果有协议信息,批量查询所有类型的使用数量 + Map useNumMap = new HashMap<>(); + if (agreementInfo != null && !listL5.isEmpty()) { + // 收集所有需要查询的typeId + List typeIds = listL5.stream() + .map(TypeTreeNode::getTypeId) + .collect(Collectors.toList()); + + // 批量查询使用数量 - 性能优化关键点 + List batchResults = typeMapper.getNumListBatch(agreementInfo.getAgreementId(), typeIds); + + // 转换为Map + if (!CollectionUtils.isEmpty(batchResults)) { + useNumMap = batchResults.stream() + .filter(type -> type.getTypeId() != null && type.getUseNum() != null) + .collect(Collectors.toMap(Type::getTypeId, Type::getUseNum)); + } + } + + // 设置使用数量 + for (TypeTreeNode node : listL5) { + BigDecimal useNum = useNumMap.getOrDefault(node.getTypeId(), BigDecimal.ZERO); + node.setUseNum(useNum); + } List list4ParentIds = listL5.stream().map(TypeTreeNode::getParentId).collect(Collectors.toList()); // 根据第四层parentId 查第三层类型 listL3 = mapper.getUseTypeTreeL3(list4ParentIds); @@ -1466,13 +1491,39 @@ public class MaterialLeaseInfoServiceImpl implements MaterialLeaseInfoService { // 原查询结果转换树形结构 groupList = treeBuild.buildTree(); } - } } catch (Exception e) { - AjaxResult.error("类型树-查询失败", e); + log.error("getUseTypeTree方法执行失败", e); + return AjaxResult.error("类型树-查询失败", e); + } finally { + long endTime = System.currentTimeMillis(); + long executionTime = endTime - startTime; + log.info("getUseTypeTree方法执行完成,耗时:{}ms,返回数据量:{}", executionTime, groupList.size()); } return AjaxResult.success(groupList); } + /** + * 缓存班组查询 - 性能优化 + * @param teamName 班组名称 + * @return 班组信息 + */ + @Cacheable(value = "teamCache", key = "#teamName", unless = "#result == null") + public BmTeam getTeamByNameCached(String teamName) { + BmTeam bmTeam = new BmTeam(); + bmTeam.setTeamName(teamName); + return bmTeamMapper.selectByName(bmTeam); + } + + /** + * 缓存协议信息查询 - 性能优化 + * @param bean 查询参数 + * @return 协议信息 + */ + @Cacheable(value = "agreementCache", key = "#bean.proId + '_' + #bean.teamId", unless = "#result == null") + public BmAgreementInfo getAgreementInfoCached(MaterialLeaseApplyInfo bean) { + return materialLeaseInfoMapper.getAgreeId(bean); + } + /** * 根据班组和工程id查询领料机具 * @param dto diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/controller/WsMaInfoController.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/controller/WsMaInfoController.java index ed070fb2..83be7556 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/controller/WsMaInfoController.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/controller/WsMaInfoController.java @@ -9,13 +9,16 @@ import com.bonus.common.log.enums.OperaType; import com.bonus.common.security.utils.SecurityUtils; import com.bonus.material.codeCollection.domain.WsMaInfo; import com.bonus.material.codeCollection.service.WsMaInfoService; -import com.bonus.material.common.annotation.PreventRepeatSubmit; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.util.List; +import java.util.Objects; /** * 机具信息控制器 @@ -147,4 +150,26 @@ public class WsMaInfoController extends BaseController { public AjaxResult updateGadgetInfo(@RequestBody WsMaInfo info) { return service.updateGadgetInfo(info); } + + /** + * 导入小工具信息模板 + */ + @ApiOperation(value = "小工具导入模版下载") + @PostMapping("/downLoad") + public void downLoadExcelFile(){ + HttpServletResponse resp = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse(); + service.downLoadTemplate(resp); + } + + /** + * 小工具信息导入 + * @param file + * @return + */ + @ApiOperation(value = "小工具信息导入") + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file) + { + return service.importTbData(file); + } } diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/mapper/WsMaInfoMapper.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/mapper/WsMaInfoMapper.java index 02f400bf..66617252 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/mapper/WsMaInfoMapper.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/mapper/WsMaInfoMapper.java @@ -1,6 +1,7 @@ package com.bonus.material.codeCollection.mapper; import com.bonus.material.codeCollection.domain.WsMaInfo; +import com.bonus.material.ma.domain.Type; import org.apache.ibatis.annotations.MapKey; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -142,4 +143,18 @@ public interface WsMaInfoMapper { * @return 条数 */ int updateGadgetInfo(WsMaInfo info); + + /** + * 根据级别查询所有匹配的记录 + * @param level + * @param typeName + * @return + */ + // 根据级别查询所有r匹配的记录 + List listByLevelAndName(@Param("level") String level, @Param("typeName") String typeName); + + Type selectTypeRelation(@Param("parentLevel") String parentLevel, + @Param("parentName") String parentName, + @Param("childLevel") String childLevel, + @Param("childName") String childName); } diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/service/WsMaInfoService.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/service/WsMaInfoService.java index 1b6808d3..c559696b 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/service/WsMaInfoService.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/service/WsMaInfoService.java @@ -2,11 +2,10 @@ package com.bonus.material.codeCollection.service; import com.bonus.common.core.web.domain.AjaxResult; import com.bonus.material.codeCollection.domain.WsMaInfo; -import org.apache.ibatis.annotations.MapKey; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; import java.util.List; -import java.util.Map; -import java.util.Objects; /** * WsMaInfoService @@ -120,4 +119,17 @@ public interface WsMaInfoService { * @return */ AjaxResult updateGadgetInfo(WsMaInfo info); + + /** + * 小工具导入模版下载 + * @param resp + */ + void downLoadTemplate(HttpServletResponse resp); + + /** + * 导入小工具信息 + * @param file + * @return + */ + AjaxResult importTbData(MultipartFile file); } diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/service/impl/WsMaInfoServiceImpl.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/service/impl/WsMaInfoServiceImpl.java index 3eade8d3..2c63f067 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/service/impl/WsMaInfoServiceImpl.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/codeCollection/service/impl/WsMaInfoServiceImpl.java @@ -1,20 +1,34 @@ package com.bonus.material.codeCollection.service.impl; import com.alibaba.nacos.common.utils.CollectionUtils; +import com.bonus.common.biz.constant.GlobalConstants; +import com.bonus.common.core.utils.poi.ExcelUtil; import com.bonus.common.core.web.domain.AjaxResult; import com.bonus.common.security.utils.SecurityUtils; import com.bonus.material.codeCollection.domain.WsMaInfo; import com.bonus.material.codeCollection.mapper.WsMaInfoMapper; import com.bonus.material.codeCollection.service.WsMaInfoService; +import com.bonus.material.ma.domain.Type; +import com.bonus.material.ma.domain.vo.ExceptionEnum; +import com.bonus.material.ma.domain.vo.MaTypeVo; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.hibernate.validator.internal.util.StringHelper; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; /** * {@code WsMaInfoServiceImpl} @@ -281,4 +295,288 @@ public class WsMaInfoServiceImpl implements WsMaInfoService { return AjaxResult.error("更新失败"); } } + + /** + * 小工具导入模版下载 + * @param response + */ + @Override + public void downLoadTemplate(HttpServletResponse response) { + //模板名称 + String templateName = "机具检验标识信息导入模板.xls"; + OutputStream out = null; + InputStream input =null; + try { + input = this.getClass().getClassLoader().getResourceAsStream("template/机具检验标识信息导入模板.xls"); + response.setCharacterEncoding("UTF-8"); + response.setHeader("content-Type", "application/vnd.ms-excel"); + response.setHeader("Content-Disposition", + "attachment;filename=" + new String((templateName).getBytes(), "iso-8859-1")); + response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); + out = response.getOutputStream(); + // 缓冲区 + byte[] buffer = new byte[1024]; + int bytesToRead = -1; + // 通过循环将读入内容输出到浏览器中 + while ((bytesToRead = input.read(buffer)) != -1) { + out.write(buffer, 0, bytesToRead); + } + } catch (IOException e) { + log.error(e.getMessage()); + } finally { + IOUtils.closeQuietly(input); + IOUtils.closeQuietly(out); + } + } + + @Override + public AjaxResult importTbData(MultipartFile file) { + String fileName = file.getOriginalFilename(); + if (fileName != null) { + String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1); + if (!GlobalConstants.XLSX.equalsIgnoreCase(fileExtension) && !GlobalConstants.XLS.equalsIgnoreCase(fileExtension)) { + return AjaxResult.error("导入失败:文件后缀名不符合要求,必须为xlsx或xls结尾"); + } + } + + try { + InputStream inputStream = file.getInputStream(); + Workbook workbook = WorkbookFactory.create(inputStream); + Sheet sheet = workbook.getSheetAt(0); + + // 得到Excel的行数(跳过表头行) + int totalRows = sheet.getPhysicalNumberOfRows(); + if (totalRows <= 1) { + throw new IllegalArgumentException("导入失败:Excel文件中没有数据,请检查后重新导入"); + } + + // 读取表头行(第一行) + Row headerRow = sheet.getRow(1); + if (headerRow == null) { + throw new IllegalArgumentException("导入失败:文件中没有表头"); + } + + // 验证表头 + validateHeader(headerRow); + + List wsMaInfos = new ArrayList<>(); + int successCount = 0; + int failCount = 0; + List errorMessages = new ArrayList<>(); + + // 从第二行开始读取数据(索引1) + for (int rowNum = 2; rowNum < totalRows; rowNum++) { + Row row = sheet.getRow(rowNum); + if (row == null) { + continue; + } + + try { + WsMaInfo wsMaInfo = parseRowToWsMaInfo(row); + if (wsMaInfo != null) { + wsMaInfos.add(wsMaInfo); + // 新增操作 + wsMaInfo.setOptUser(SecurityUtils.getLoginUser().getSysUser().getNickName()); + mapper.addWsMaInfoData(wsMaInfo); + successCount++; + } + } catch (Exception e) { + failCount++; + errorMessages.add("第 " + (rowNum + 1) + " 行数据解析失败: " + e.getMessage()); + } + } + + // 处理结果返回 + if (failCount == 0) { + return AjaxResult.success("导入成功,共导入 " + successCount + " 条数据"); + } else { + String errorMsg = "导入完成,成功 " + successCount + " 条,失败 " + failCount + " 条。"; + if (!errorMessages.isEmpty()) { + errorMsg += "错误信息:" + String.join("; ", errorMessages); + } + return AjaxResult.error(errorMsg); + } + + } catch (IOException e) { + log.error("导入文件读取失败", e); + return AjaxResult.error("导入失败:文件读取异常"); + } catch (IllegalArgumentException e) { + return AjaxResult.error(e.getMessage()); + } catch (Exception e) { + log.error("导入过程发生异常", e); + return AjaxResult.error("导入失败:系统异常"); + } + } + + /** + * 验证表头格式 + */ + private void validateHeader(Row headerRow) { + String[] expectedHeaders = { + "序号", "机具名称", "规格型号", "机具编号", "本次检验时间", + "下次检验时间", "检修员", "检验员", "联系方式", "结果" + }; + + int cellCount = headerRow.getPhysicalNumberOfCells(); + if (cellCount < expectedHeaders.length) { + throw new IllegalArgumentException("导入失败:表头列数不足,请检查导入模板"); + } + + for (int i = 0; i < expectedHeaders.length; i++) { + Cell cell = headerRow.getCell(i); + if (cell == null) { + throw new IllegalArgumentException("导入失败:第 " + (i + 1) + " 列表头为空"); + } + + String headerValue = getCellValueAsString(cell).trim(); + if (!expectedHeaders[i].equals(headerValue)) { + throw new IllegalArgumentException("导入失败:第 " + (i + 1) + " 列表头 '" + headerValue + + "' 与预期 '" + expectedHeaders[i] + "' 不符"); + } + } + } + + /** + * 解析单行数据到WsMaInfo对象 + */ + private WsMaInfo parseRowToWsMaInfo(Row row) { + WsMaInfo wsMaInfo = new WsMaInfo(); + + // 机具名称 + String maName = getCellValueAsString(row.getCell(1)); + if (StringHelper.isNullOrEmptyString(maName)){ + throw new IllegalArgumentException("机具名称不能为空"); + } + wsMaInfo.setMaName(maName); + + // 规格型号 + String maModel = getCellValueAsString(row.getCell(2)); + if (StringHelper.isNullOrEmptyString(maModel)){ + throw new IllegalArgumentException("规格型号不能为空"); + } + wsMaInfo.setMaModel(maModel); + + // 机具编号 + String maCode = getCellValueAsString(row.getCell(3)); + if (StringUtils.isEmpty(maCode)) { + throw new IllegalArgumentException("机具编号不能为空"); + } + wsMaInfo.setMaCode(maCode); + + // 本次检验时间 + String thisCheckTime = getCellValueAsString(row.getCell(4)); + if (!isValidDate(thisCheckTime)) { + throw new IllegalArgumentException("本次检验时间格式不正确"); + } + wsMaInfo.setThisCheckTime(thisCheckTime); + + // 下次检验时间 + String nextCheckTime = getCellValueAsString(row.getCell(5)); + if (!isValidDate(nextCheckTime)) { + throw new IllegalArgumentException("下次检验时间格式不正确"); + } + wsMaInfo.setNextCheckTime(nextCheckTime); + + // 检修员 + wsMaInfo.setRepairMan(getCellValueAsString(row.getCell(6))); + + // 检验员 + wsMaInfo.setCheckMan(getCellValueAsString(row.getCell(7))); + + // 联系方式 + wsMaInfo.setPhone(getCellValueAsString(row.getCell(8))); + + // 结果 + String result = getCellValueAsString(row.getCell(9)); + if (!"合格".equals(result) && !"不合格".equals(result)) { + throw new IllegalArgumentException("检验结果必须是'合格'或'不合格'"); + } + wsMaInfo.setResult(result); + + // 验证设备类型和规格型号的层级关系 + validateTypeHierarchy(wsMaInfo, maName, maModel); + + //判断该类型下编码是否已存在 + WsMaInfo info = mapper.getInfoByTypeAndModelAndCode(wsMaInfo); + if (info != null){ + throw new IllegalArgumentException("该类型下编码已存在"); + } + + return wsMaInfo; + } + + /** + * 验证设备类型和规格型号的层级关系 + */ + /** + * 通过关联查询验证层级关系(更高效) + */ + private void validateTypeHierarchy(WsMaInfo wsMaInfo, String typeName, String modelName) { + // 使用关联查询直接验证层级关系 + Type relation = mapper.selectTypeRelation("3", typeName, "4", modelName); + + if (relation == null) { + throw new IllegalArgumentException("规格型号 '" + modelName + "' 不属于设备类型 '" + typeName + "' 或不存在"); + } + + wsMaInfo.setModelId(String.valueOf(relation.getTypeId())); + } + + /** + * 更新已存在的记录 + */ + private void updateExistingRecord(WsMaInfo existing, WsMaInfo newData) { + existing.setThisCheckTime(newData.getThisCheckTime()); + existing.setNextCheckTime(newData.getNextCheckTime()); + existing.setRepairMan(newData.getRepairMan()); + existing.setCheckMan(newData.getCheckMan()); + existing.setPhone(newData.getPhone()); + existing.setResult(newData.getResult()); + existing.setOptTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); + existing.setOptUser(SecurityUtils.getUsername()); + } + + /** + * 获取单元格值的通用方法 + */ + private String getCellValueAsString(Cell cell) { + if (cell == null) { + return ""; + } + + switch (cell.getCellType()) { + case STRING: + return cell.getStringCellValue().trim(); + case NUMERIC: + if (DateUtil.isCellDateFormatted(cell)) { + return new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue()); + } else { + return String.valueOf((int) cell.getNumericCellValue()); + } + case BOOLEAN: + return String.valueOf(cell.getBooleanCellValue()); + case FORMULA: + return cell.getCellFormula(); + default: + return ""; + } + } + + /** + * 验证日期格式 + */ + private boolean isValidDate(String dateStr) { + if (StringUtils.isEmpty(dateStr)) { + return false; + } + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + sdf.setLenient(false); + sdf.parse(dateStr); + return true; + } catch (ParseException e) { + return false; + } + } + } diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/ma/mapper/TypeMapper.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/ma/mapper/TypeMapper.java index 2cfd0011..6cec25c9 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/ma/mapper/TypeMapper.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/ma/mapper/TypeMapper.java @@ -2,6 +2,7 @@ package com.bonus.material.ma.mapper; import java.math.BigDecimal; import java.util.List; +import java.util.Map; import com.bonus.common.biz.domain.lease.LeaseOutDetails; import com.bonus.material.ma.domain.MaTypeHistory; @@ -211,6 +212,14 @@ public interface TypeMapper { */ Type getNumList(Type type); + /** + * 批量查询数量 - 性能优化 + * @param agreementId 协议ID + * @param typeIds 类型ID列表 + * @return 类型使用数量列表 + */ + List getNumListBatch(@Param("agreementId") Long agreementId, @Param("typeIds") List typeIds); + /** * 查询物资类型管理绑定的用户列表 * @param type diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/repair/service/impl/RepairAuditDetailsServiceImpl.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/repair/service/impl/RepairAuditDetailsServiceImpl.java index 83aef58f..bdc1c95f 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/repair/service/impl/RepairAuditDetailsServiceImpl.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/repair/service/impl/RepairAuditDetailsServiceImpl.java @@ -10,7 +10,6 @@ import com.bonus.common.core.utils.DateUtils; import com.bonus.common.core.utils.StringUtils; import com.bonus.common.security.utils.SecurityUtils; import com.bonus.material.back.mapper.BackApplyInfoMapper; -import com.bonus.material.basic.domain.report.PurChaseReportInfo; import com.bonus.material.repair.domain.*; import com.bonus.material.repair.domain.vo.RepairAuditDetailsVO; import com.bonus.material.repair.domain.vo.RepairHomeInfo; @@ -28,7 +27,6 @@ import com.bonus.material.task.domain.TmTaskAgreement; import com.bonus.material.task.mapper.TmTaskAgreementMapper; import com.bonus.material.task.mapper.TmTaskMapper; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -910,8 +908,13 @@ public class RepairAuditDetailsServiceImpl implements IRepairAuditDetailsService private Long insertScrapTt() { Long newTask = null; int thisMonthMaxOrder = taskMapper.getMonthMaxOrderByDate(DateUtils.getCurrentYear(), DateUtils.getCurrentMonth(), TmTaskTypeEnum.TM_TASK_SCRAP.getTaskTypeId()); - // 生成修饰入库单号 + // 生成报废单号 String code = genderBfTaskCode(thisMonthMaxOrder); + // 根据单号查询该任务是否存在 + TmTask task = taskMapper.selectTaskByCode(code); + if (task != null) { + throw new RuntimeException("该审核任务正在进行中,请勿重复提交!"); + } TmTask tmTask = new TmTask(null, TmTaskTypeEnum.TM_TASK_SCRAP.getTaskTypeId(), ScrapTaskStatusEnum.SCRAP_TASK_NO_FINISHED.getStatus(), null,thisMonthMaxOrder + 1, code); tmTask.setCreateTime(DateUtils.getNowDate()); @@ -963,6 +966,11 @@ public class RepairAuditDetailsServiceImpl implements IRepairAuditDetailsService null,thisMonthMaxOrder + 1, code); tmTask.setCreateTime(DateUtils.getNowDate()); tmTask.setCreateBy(SecurityUtils.getLoginUser().getSysUser().getNickName()); + // 根据code查询此任务单号是否存在 + TmTask task = taskMapper.selectTaskByCode(code); + if (task != null) { + throw new RuntimeException("该审核任务正在进行中,请勿重复提交!"); + } // 插入任务 int taskId = taskMapper.insertTmTask(tmTask); // 如果插入成功且返回的 taskId 大于 0 diff --git a/bonus-modules/bonus-material/src/main/resources/mapper/material/basic/BmQrBoxMapper.xml b/bonus-modules/bonus-material/src/main/resources/mapper/material/basic/BmQrBoxMapper.xml index 8949ea46..81d54d79 100644 --- a/bonus-modules/bonus-material/src/main/resources/mapper/material/basic/BmQrBoxMapper.xml +++ b/bonus-modules/bonus-material/src/main/resources/mapper/material/basic/BmQrBoxMapper.xml @@ -448,6 +448,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - update bm_qrcode_box set box_status = 5 where box_id = #{boxId} + update bm_qrcode_box set box_status = 6 where box_id = #{boxId} diff --git a/bonus-modules/bonus-material/src/main/resources/mapper/material/codeCollection/WsMaInfoMapper.xml b/bonus-modules/bonus-material/src/main/resources/mapper/material/codeCollection/WsMaInfoMapper.xml index a2867f62..327193c6 100644 --- a/bonus-modules/bonus-material/src/main/resources/mapper/material/codeCollection/WsMaInfoMapper.xml +++ b/bonus-modules/bonus-material/src/main/resources/mapper/material/codeCollection/WsMaInfoMapper.xml @@ -122,7 +122,8 @@ + + INSERT INTO ws_ma_info (ma_name, ma_model, ma_code, supplier, this_check_time, next_check_time, diff --git a/bonus-modules/bonus-material/src/main/resources/mapper/material/ma/TypeMapper.xml b/bonus-modules/bonus-material/src/main/resources/mapper/material/ma/TypeMapper.xml index 0f37634c..e9e15522 100644 --- a/bonus-modules/bonus-material/src/main/resources/mapper/material/ma/TypeMapper.xml +++ b/bonus-modules/bonus-material/src/main/resources/mapper/material/ma/TypeMapper.xml @@ -1088,6 +1088,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" mt.type_id + + +