From 0e3276f61832ce4e97cbd35875a35312d157a7bd Mon Sep 17 00:00:00 2001 From: mashuai Date: Mon, 17 Mar 2025 18:11:07 +0800 Subject: [PATCH] 11 --- .../device/controller/DevInfoController.java | 28 +- .../device/domain/vo/DevTemplateVo.java | 83 +++++ .../material/device/mapper/DevInfoMapper.java | 7 + .../device/service/DevInfoService.java | 14 + .../service/impl/DevInfoServiceImpl.java | 297 +++++++++++++++++- .../mapper/material/device/DevInfoMapper.xml | 13 + .../resources/template/MaDevTemplate.xlsx | Bin 0 -> 12310 bytes 7 files changed, 436 insertions(+), 6 deletions(-) create mode 100644 bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/domain/vo/DevTemplateVo.java create mode 100644 bonus-modules/bonus-material-mall/src/main/resources/template/MaDevTemplate.xlsx diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/controller/DevInfoController.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/controller/DevInfoController.java index e5d6d69..b537cd8 100644 --- a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/controller/DevInfoController.java +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/controller/DevInfoController.java @@ -7,6 +7,7 @@ import com.bonus.common.core.utils.poi.ExcelUtil; import com.bonus.common.core.web.controller.BaseController; import com.bonus.common.core.web.domain.AjaxResult; import com.bonus.common.core.web.page.TableDataInfo; +import com.bonus.common.security.annotation.RequiresPermissions; import com.bonus.common.security.utils.SecurityUtils; import com.bonus.material.device.domain.DevInfo; import com.bonus.material.device.domain.dto.DevInfoImpDto; @@ -26,6 +27,7 @@ import javax.validation.Valid; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Objects; /** @@ -212,7 +214,7 @@ public class DevInfoController extends BaseController { util.exportExcel(response, list, "设备信息数据"); } - @ApiOperation(value = "装备批量录入") + /*@ApiOperation(value = "装备批量录入") @PostMapping("/importData") public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception { String fileName = file.getOriginalFilename(); @@ -232,7 +234,7 @@ public class DevInfoController extends BaseController { Long userId = SecurityUtils.getLoginUser().getUserid(); String message = devInfoService.importMaProp(maPropInfoList, updateSupport, userId); return success(message); - } + }*/ @ApiOperation(value = "装备模版下载") @@ -241,4 +243,26 @@ public class DevInfoController extends BaseController { HttpServletResponse resp = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); devInfoService.downLoadTemplate(resp); } + + /** + * 设备批量导入模版下载 + */ + @ApiOperation(value = "设备批量导入模版下载") + @PostMapping("/downLoadDev") + public void downLoadDev(){ + HttpServletResponse resp = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse(); + devInfoService.downLoadDev(resp); + } + + /** + * 设备信息导入 + * @param file + * @return + */ + @ApiOperation(value = "设备信息导入") + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file) + { + return devInfoService.importTbPeople(file); + } } diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/domain/vo/DevTemplateVo.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/domain/vo/DevTemplateVo.java new file mode 100644 index 0000000..60e3c51 --- /dev/null +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/domain/vo/DevTemplateVo.java @@ -0,0 +1,83 @@ +package com.bonus.material.device.domain.vo; + +import com.bonus.common.core.annotation.Excel; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.List; + + +/** + * @author mashuai + * @date 2025/03/17 17:11 + */ +@EqualsAndHashCode(callSuper = false) +@Data +@ToString +public class DevTemplateVo { + + @ApiModelProperty(value = "装备名称") + @Excel(name = "装备名称") + private String deviceName; + + @ApiModelProperty(value = "类型id") + private Long typeId; + + @ApiModelProperty(value = "装备类别") + @Excel(name = "装备类目") + private String typeName; + + @Excel(name = "装备品牌") + @ApiModelProperty(value = "设备品牌") + private String brand; + + @Excel(name = "出厂日期") + @ApiModelProperty(value = "出厂日期") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private String productionDate; + + @ApiModelProperty(value = "联系人") + @Excel(name = "联系人") + private String person; + + @ApiModelProperty(value = "联系电话") + @Excel(name = "联系电话") + private String personPhone; + + @Excel(name = "上架数量(编码类型设备默传1)") + @ApiModelProperty(value = "上架数量") + private Integer deviceCount; + + @Excel(name = "唯一标识符") + @ApiModelProperty(value = "设备唯一标识符") + private String identifyCode; + + @ApiModelProperty(value = "检修人") + @Excel(name = "检修人") + private String checkMan; + + @ApiModelProperty(value = "检测日期") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd") + @Excel(name = "检修时间(例:2025-03-17)") + private String checkDate; + + @ApiModelProperty(value = "下次检测日期") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd") + @Excel(name = "下次检测时间(例:2025-03-17)") + private String nextCheckDate; + + @ApiModelProperty(value = "装备单位") + private String unitName; + + @ApiModelProperty(value = "设备天租价") + private Float dayLeasePrice; + + @ApiModelProperty(value = "装备证书详情列表") + private List dtoList; +} diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/mapper/DevInfoMapper.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/mapper/DevInfoMapper.java index f49bea8..0f3d43c 100644 --- a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/mapper/DevInfoMapper.java +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/mapper/DevInfoMapper.java @@ -243,5 +243,12 @@ public interface DevInfoMapper { DevInfoVo getBuyCompanyList(DevInfoVo infoVo); DevInfoVo selectDeptList(String companyId); + + /** + * 根据装备类型名称查询 + * @param typeName + * @return + */ + DevInfoVo selectDevTypeByName(String typeName); } diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/DevInfoService.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/DevInfoService.java index 3ec495c..66b76c2 100644 --- a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/DevInfoService.java +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/DevInfoService.java @@ -8,6 +8,7 @@ import com.bonus.material.device.domain.DevInfo; import com.bonus.material.device.domain.dto.DevInfoImpDto; import com.bonus.material.device.domain.dto.InfoMotionDto; import com.bonus.material.device.domain.vo.DevInfoVo; +import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.util.List; @@ -116,4 +117,17 @@ public interface DevInfoService { * @return */ List getLeaseDevList(DevInfoVo devInfo); + + /** + * 设备批量导入模版下载 + * @param resp + */ + void downLoadDev(HttpServletResponse resp); + + /** + * 设备信息导入 + * @param file + * @return + */ + AjaxResult importTbPeople(MultipartFile file); } diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/impl/DevInfoServiceImpl.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/impl/DevInfoServiceImpl.java index cadaf0f..c6a9d16 100644 --- a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/impl/DevInfoServiceImpl.java +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/impl/DevInfoServiceImpl.java @@ -2,6 +2,7 @@ package com.bonus.material.device.service.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.PhoneUtil; import com.bonus.common.biz.constant.MaterialConstants; import com.bonus.common.biz.domain.*; import com.bonus.common.biz.enums.HttpCodeEnum; @@ -12,6 +13,8 @@ import com.bonus.common.core.utils.DateUtils; import com.bonus.common.core.utils.StringUtils; import com.bonus.common.core.utils.bean.BeanUtils; import com.bonus.common.core.utils.bean.BeanValidators; +import com.bonus.common.core.utils.encryption.Sm4Utils; +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.book.domain.BookCarInfoDto; @@ -20,10 +23,7 @@ import com.bonus.material.device.domain.MaDevQc; import com.bonus.material.device.domain.Table; import com.bonus.material.device.domain.dto.DevInfoImpDto; import com.bonus.material.device.domain.dto.InfoMotionDto; -import com.bonus.material.device.domain.vo.DevInfoPropertyVo; -import com.bonus.material.device.domain.vo.DevInfoVo; -import com.bonus.material.device.domain.vo.DevNameVo; -import com.bonus.material.device.domain.vo.LeaseVo; +import com.bonus.material.device.domain.vo.*; import com.bonus.material.device.mapper.BmFileInfoMapper; import com.bonus.material.device.mapper.DevInfoMapper; import com.bonus.material.device.mapper.MaDevQcMapper; @@ -35,10 +35,13 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.dao.DataAccessException; 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 javax.servlet.http.HttpServletResponse; @@ -47,6 +50,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.*; import java.util.stream.Collectors; @@ -870,6 +876,289 @@ public class DevInfoServiceImpl implements DevInfoService { return devInfoMapper.getLeaseDevList(devInfo); } + /** + * 设备批量导入模版下载 + * @param response + */ + @Override + public void downLoadDev(HttpServletResponse response) { + //模板名称 + String templateName = "MaDevTemplate.xlsx"; + OutputStream out = null; + InputStream input =null; + try { + input = this.getClass().getClassLoader().getResourceAsStream("template/MaDevTemplate.xlsx"); + 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); + } + } + + /** + * 设备信息导入 + * @param file + * @return + */ + @Override + public AjaxResult importTbPeople(MultipartFile file) { + String fileName = file.getOriginalFilename(); + if (fileName != null) { + String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1); + if (!MaterialConstants.XLSX.equalsIgnoreCase(fileExtension)) { + // 文件后缀名不符合要求 + return AjaxResult.error("导入失败:文件后缀名不符合要求,必须为xlsx结尾"); + } + } + try { + InputStream inputStream = file.getInputStream(); + Workbook workbook = new XSSFWorkbook(inputStream); + Sheet sheet = workbook.getSheetAt(0); + // 得到Excel的行数 + int totalRows = sheet.getPhysicalNumberOfRows(); + // 检查是否有行数 + if (totalRows <= 1) { + throw new IllegalArgumentException("导入失败:Excel文件中没有数据,请检查后重新导入"); + } + // 读取第一行表头 + Row headerRow = sheet.getRow(0); + if (headerRow == null) { + throw new IllegalArgumentException("导入失败:文件中没有表头"); + } + // 获取表头的列数 + int totalCells = headerRow.getPhysicalNumberOfCells(); + // 预期的表头列数为11列,可以根据实际需求修改这个条件 + if (totalCells != 11) { + throw new IllegalArgumentException("导入失败:表头列数与预期不符,请检查导入模板"); + } + // 获取数据行数 + int rowCount = sheet.getLastRowNum() + 1; + if (rowCount > 50) { + throw new IllegalArgumentException("导入失败:数据总条数不能超过 50 条"); + } + // 读取表头内容并验证每一列 + extractedText(headerRow, totalCells); + //读取Excel表格数据,做非空及格式判断 + //extractedCell(sheet, totalRows, totalCells); + ExcelUtil util = new ExcelUtil<>(DevTemplateVo.class); + List maDevList = util.importExcel(file.getInputStream()); + List templateVos = new ArrayList<>(); + List dtoList = new ArrayList<>(); + // 判断装备类目是否为空,查询装备id及价格 + if (!CollectionUtils.isEmpty(maDevList)) { + for (DevTemplateVo devTemplateVo : maDevList) { + if (StringUtils.isNotBlank(devTemplateVo.getTypeName())) { + // 根据装备类目查询装备id及价格 + DevInfoVo devType = devInfoMapper.selectDevTypeByName(devTemplateVo.getTypeName()); + if (devType != null) { + devTemplateVo.setTypeId(Long.valueOf(devType.getId())); + devTemplateVo.setTypeName(devType.getTypeName()); + devTemplateVo.setDayLeasePrice(devType.getDayLeasePrice()); + devTemplateVo.setUnitName(devType.getUnitName()); + if ("0".equals(devType.getManageType())) { + devTemplateVo.setDeviceCount(1); + } + } + } + } + // 对maDevList通过装备名称进行分组 + Map> map = maDevList.stream().collect(Collectors.groupingBy(DevTemplateVo::getDeviceName)); + for (Map.Entry> entry : map.entrySet()) { + // 如果map的数量大于1,则进行遍历 + if (entry.getValue().size() > 1) { + DevTemplateVo devTemplateVo = entry.getValue().get(0); + DevTemplateVo dto = new DevTemplateVo(); + List devTemplateVos = entry.getValue(); + for (DevTemplateVo templateVo : devTemplateVos) { + dto.setIdentifyCode(StringUtils.isNotBlank(templateVo.getIdentifyCode()) ? templateVo.getIdentifyCode() : null); + dto.setCheckMan(StringUtils.isNotBlank(templateVo.getCheckMan()) ? templateVo.getCheckMan() : null); + dto.setCheckDate(StringUtils.isNotBlank(templateVo.getCheckDate()) ? templateVo.getCheckDate() : null); + dto.setNextCheckDate(StringUtils.isNotBlank(templateVo.getNextCheckDate()) ? templateVo.getNextCheckDate() : null); + dtoList.add(dto); + } + devTemplateVo.setDtoList(dtoList); + templateVos.add(devTemplateVo); + } else { + DevTemplateVo devTemplateVo = entry.getValue().get(0); + templateVos.add(devTemplateVo); + } + } + } + return AjaxResult.success(templateVos); + } catch (IOException e) { + e.printStackTrace(); + } + return AjaxResult.error(HttpCodeEnum.FAIL.getCode(), HttpCodeEnum.FAIL.getMsg()); + } + + /** + * 读取Excel表格数据,做非空判断 + * @param sheet + * @param totalRows + * @param totalCells + */ + private void extractedCell(Sheet sheet, int totalRows, int totalCells) { + //读取Excel表格数据,做非空判断 + // 循环Excel行数 + DataFormatter dataFormatter = new DataFormatter(); + for (int r = 1; r < totalRows; r++) { + Row row = sheet.getRow(r); + // 循环Excel列数 + for (int c = 0; c < totalCells; c++) { + String cellValue = dataFormatter.formatCellValue(row.getCell(c)); + switch (c) { + case 0: + checkBlank(cellValue, r, c); + break; + case 3: + checkDate(cellValue, r, c); + break; + case 4: + checkBlank(cellValue, r, c); + checkPhone(cellValue, r, c); + break; + default: + throw new IllegalArgumentException( + String.format("第 %d 行,第 %d 列超出范围,请检查后重新导入", r + 1, c + 1)); + } + } + } + } + + /** + * 检查日期格式 + * @param cellValue + * @param rowIndex + * @param colIndex + */ + private void checkDate(String cellValue, int rowIndex, int colIndex) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + try { + // 尝试解析日期字符串 + LocalDate.parse(cellValue, formatter); + System.out.println(true); + } catch (DateTimeParseException e) { + // 解析失败,说明日期格式不符合要求 + if (StringUtils.isBlank(cellValue)) { + throw new IllegalArgumentException( + String.format("第 %d 行,第 %d 列出厂日期不符合格式要求,请检查后重新导入", rowIndex + 1, colIndex + 1)); + } + } + } + + /** + * 检查数据是否为空 + * @param cellValue + * @param rowIndex + * @param colIndex + */ + private void checkBlank(String cellValue, int rowIndex, int colIndex) { + if (StringUtils.isBlank(cellValue)) { + throw new IllegalArgumentException( + String.format("第 %d 行,第 %d 列数据为空,请检查后重新导入", rowIndex + 1, colIndex + 1)); + } + } + + /** + * 检查电话号码格式 + * @param cellValue + * @param rowIndex + * @param colIndex + */ + private void checkPhone(String cellValue, int rowIndex, int colIndex) { + if (!PhoneUtil.isMobile(cellValue)) { + throw new IllegalArgumentException( + String.format("第 %d 行,第 %d 列电话号码格式不正确,请检查后重新导入", rowIndex + 1, colIndex + 1)); + } + } + + /** + * 读取Excel表格数据,做非空及格式判断 + * @param headerRow + * @param totalCells + */ + private void extractedText(Row headerRow, int totalCells) { + for (int cellNum = 0; cellNum < totalCells; cellNum++) { + Cell cell = headerRow.getCell(cellNum); + // 获取单元格内容并去除首尾空格 + String headerValue = cell.getStringCellValue().trim(); + // 根据列索引进行验证 + switch (cellNum) { + case 0: + if (!"装备名称".equals(headerValue)) { + throw new IllegalArgumentException("第 " + (cellNum + 1) + " 列表头列名与预期不符,请检查导入模板"); + } + break; + case 1: + if (!"装备类目".equals(headerValue)) { + throw new IllegalArgumentException("第 " + (cellNum + 1) + " 列表头列名与预期不符,请检查导入模板"); + } + break; + case 2: + if (!"装备品牌".equals(headerValue)) { + throw new IllegalArgumentException("第 " + (cellNum + 1) + " 列表头列名与预期不符,请检查导入模板"); + } + break; + case 3: + if (!"出厂日期(例:2025-03-17)".equals(headerValue)) { + throw new IllegalArgumentException("第 " + (cellNum + 1) + " 列表头列名与预期不符,请检查导入模板"); + } + break; + case 4: + if (!"联系人".equals(headerValue)) { + throw new IllegalArgumentException("第 " + (cellNum + 1) + " 列表头列名与预期不符,请检查导入模板"); + } + break; + case 5: + if (!"联系电话".equals(headerValue)) { + throw new IllegalArgumentException("第 " + (cellNum + 1) + " 列表头列名与预期不符,请检查导入模板"); + } + break; + case 6: + if (!"上架数量(编码类型设备默传1)".equals(headerValue)) { + throw new IllegalArgumentException("第 " + (cellNum + 1) + " 列表头列名与预期不符,请检查导入模板"); + } + break; + case 7: + if (!"唯一标识符".equals(headerValue)) { + throw new IllegalArgumentException("第 " + (cellNum + 1) + " 列表头列名与预期不符,请检查导入模板"); + } + break; + case 8: + if (!"检修人".equals(headerValue)) { + throw new IllegalArgumentException("第 " + (cellNum + 1) + " 列表头列名与预期不符,请检查导入模板"); + } + break; + case 9: + if (!"检修时间(例:2025-03-17)".equals(headerValue)) { + throw new IllegalArgumentException("第 " + (cellNum + 1) + " 列表头列名与预期不符,请检查导入模板"); + } + break; + case 10: + if (!"下次检测时间(例:2025-03-17)".equals(headerValue)) { + throw new IllegalArgumentException("第 " + (cellNum + 1) + " 列表头列名与预期不符,请检查导入模板"); + } + break; + default: + break; + } + } + } + @Override public void insertOutType(String devInfo) { ObjectMapper objectMapper = new ObjectMapper(); diff --git a/bonus-modules/bonus-material-mall/src/main/resources/mapper/material/device/DevInfoMapper.xml b/bonus-modules/bonus-material-mall/src/main/resources/mapper/material/device/DevInfoMapper.xml index 057658c..daf4887 100644 --- a/bonus-modules/bonus-material-mall/src/main/resources/mapper/material/device/DevInfoMapper.xml +++ b/bonus-modules/bonus-material-mall/src/main/resources/mapper/material/device/DevInfoMapper.xml @@ -1196,4 +1196,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" d.dept_id = #{companyId} AND d.del_flag = '0' + diff --git a/bonus-modules/bonus-material-mall/src/main/resources/template/MaDevTemplate.xlsx b/bonus-modules/bonus-material-mall/src/main/resources/template/MaDevTemplate.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ff3cd35da3d108ff6b8b289624c98e36acf14dae GIT binary patch literal 12310 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 literal 0 HcmV?d00001