1、新增 组织贴皮分类名称创建接口、获取组织贴皮分类名称列表接口、导入模板excel分类名称栏自动填入组织分类名称列表能力、导入校验组织分类名称能力;2、完善贴皮管理单元测试;

This commit is contained in:
gaoqr
2026-01-23 17:42:50 +08:00
parent 7b919d7adb
commit b09892a349
22 changed files with 674 additions and 86 deletions
@@ -7,6 +7,8 @@ import com.cf.imes.framework.common.exception.ErrorCode;
* 生产执行服务错误码枚举 使用 1-003-000-000 段
*/
public class ErrorCodeConstants {
private ErrorCodeConstants() {
}
// ========== 生产单开料排单
public static final ErrorCode PLAN_NOT_EXISTS = new ErrorCode(1_003_107_000, "生产单开料排单不存在");
@@ -177,4 +179,8 @@ public class ErrorCodeConstants {
public static final ErrorCode VENEER_IMPORT_DIGITAL_TYPE_ERROR = new ErrorCode(1_003_009_004, "veneer.import.digital.type.error");
public static final ErrorCode VENEER_IMPORT_POSITIVE_NUMBER_ERROR = new ErrorCode(1_003_009_005, "veneer.import.positive.number.error");
public static final ErrorCode VENEER_IMPORT_NUMBER_LENGTH_ERROR = new ErrorCode(1_003_009_006, "veneer.import.number.length.error");
public static final ErrorCode VENEER_IMPORT_TEMPLATE_DOWNLOAD_ERROR = new ErrorCode(1_003_009_007, "veneer.import.template.download.error");
public static final ErrorCode VENEER_IMPORT_ORG_CLASSIFICATION_NAME_NOT_EXIST = new ErrorCode(1_003_009_008, "veneer.import.org.classification.name.not.exist");
public static final ErrorCode VENEER_ORG_CLASSIFICATION_NAME_EXIST = new ErrorCode(1_003_009_009, "veneer.org.classification.name.exist");
public static final ErrorCode VENEER_ORG_CLASSIFICATION_NAME_EMPTY = new ErrorCode(1_003_009_010, "veneer.org.classification.name.empty");
}
@@ -6,7 +6,8 @@ import com.cf.imes.framework.common.pojo.PageParam;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.excel.core.util.ExcelUtils;
import com.cf.imes.framework.operatelog.core.annotations.OperateLog;
import com.cf.imes.module.executor.constants.file.TemplateFilePathConstants;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameSaveReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerImportExcelVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPageReqVO;
@@ -14,13 +15,11 @@ import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO;
import com.cf.imes.module.executor.service.veneer.VeneerExcelListener;
import com.cf.imes.module.executor.service.veneer.VeneerService;
import com.cf.imes.module.executor.util.file.FileHelperUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -77,8 +76,7 @@ public class VeneerController {
@Operation(summary = "下载贴皮导入模版")
@OperateLog(type = EXPORT)
public void getVeneerImportTemplate(HttpServletResponse response) {
String veneerImportTemplatePath = TemplateFilePathConstants.getVeneerImportTemplatePath(LocaleContextHolder.getLocale().getLanguage());
FileHelperUtil.getFirstFile(veneerImportTemplatePath, response);
veneerService.importTemplateDownload(response);
}
@PostMapping("/import")
@@ -128,4 +126,16 @@ public class VeneerController {
veneerService.saveDefect(reqVO);
return success(true);
}
@PostMapping("/classificationName")
@Operation(summary = "创建分类名称")
public CommonResult<Long> createClassificationName(@Valid @RequestBody VeneerClassificationNameSaveReqVO saveReqVO) {
return success(veneerService.addClassificationName(saveReqVO));
}
@GetMapping("/org/classificationName")
@Operation(summary = "获取分类名称列表")
public CommonResult<List<VeneerClassificationNameRespVO>> getOrgVeneerClassificationNameList() {
return success(veneerService.getOrgVeneerClassificationNameList());
}
}
@@ -0,0 +1,20 @@
package com.cf.imes.module.executor.controller.admin.veneer.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
*
*
* @author Gqr
* @since 2026/1/22 17:21
*/
@Schema(description = "生产管理 - 贴皮分类名称列表 Response VO")
@Data
public class VeneerClassificationNameRespVO {
@Schema(description = "分类名称id")
private Long id;
@Schema(description = "分类名称")
private String name;
}
@@ -0,0 +1,20 @@
package com.cf.imes.module.executor.controller.admin.veneer.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
/**
*
*
* @author Gqr
* @since 2026/1/23 10:56
*/
@Data
public class VeneerClassificationNameSaveReqVO {
@Schema(description = "分类名称")
@NotNull(message = "{veneer.classification.name.save.name.not.null}")
@Length(max = 64, message = "{veneer.classification.name.save.name.length.max}")
private String name;
}
@@ -0,0 +1,42 @@
package com.cf.imes.module.executor.dal.dataobject.veneer;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cf.imes.framework.mybatis.core.dataobject.BaseDO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 贴皮 veneers_classification_name_{N} DO
*
* @author Gqr
* @since 2026/1/22 17:07
*/
@TableName("veneers_classification_name")
@KeySequence("veneers_classification_name_n_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class VeneerClassificationNameDO extends BaseDO {
/**
* 主键
*/
private Long id;
/**
* 分类名称
*/
private String name;
/**
* 组织ID
*/
private Long organId;
}
@@ -0,0 +1,15 @@
package com.cf.imes.module.executor.dal.mysql.veneer;
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerClassificationNameDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 贴皮分类管理 Mapper
*
* @author Gqr
* @since 2026/1/22 17:12
*/
@Mapper
public interface VeneerClassificationNameMapper extends BaseMapperX<VeneerClassificationNameDO> {
}
@@ -10,6 +10,7 @@ import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.alibaba.excel.util.ListUtils;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerImportExcelVO;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDO;
import com.cf.imes.module.executor.enums.veneer.VeneerStatusEnum;
@@ -19,10 +20,13 @@ import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_DIGITAL_TYPE_ERROR;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_LENGTH_ERROR;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_NUMBER_LENGTH_ERROR;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_ORG_CLASSIFICATION_NAME_NOT_EXIST;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_POSITIVE_NUMBER_ERROR;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_REQUIRED_LACK;
@@ -33,7 +37,7 @@ import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT
* @since 2026/1/5 18:18
*/
@Slf4j
public class VeneerExcelListener<T> extends AnalysisEventListener<VeneerImportExcelVO> {
public class VeneerExcelListener extends AnalysisEventListener<VeneerImportExcelVO> {
/**
* 每500条操作入库一次
*/
@@ -48,6 +52,8 @@ public class VeneerExcelListener<T> extends AnalysisEventListener<VeneerImportEx
private Map<Integer, String> headMap;
private Set<String> orgClassificationNameSet;
private Long organId;
public VeneerExcelListener(VeneerService veneerService) {
@@ -55,6 +61,10 @@ public class VeneerExcelListener<T> extends AnalysisEventListener<VeneerImportEx
// 查询机构下所有的贴皮编号
orgVeneerNoMap = veneerService.getOrgVeneerNo();
organId = SecurityFrameworkUtils.getUserOrganId();
List<VeneerClassificationNameRespVO> orgVeneerClassificationNameList = veneerService.getOrgVeneerClassificationNameList();
if (CollUtil.isNotEmpty(orgVeneerClassificationNameList)) {
orgClassificationNameSet = orgVeneerClassificationNameList.stream().map(VeneerClassificationNameRespVO::getName).collect(Collectors.toSet());
}
}
/**
@@ -74,22 +84,10 @@ public class VeneerExcelListener<T> extends AnalysisEventListener<VeneerImportEx
throw new ServiceException(VENEER_IMPORT_REQUIRED_LACK, rowIndex);
}
// 校验贴皮是新增还是更新
String veneerNo = data.getVeneerNo();
Long existId = orgVeneerNoMap.get(veneerNo);
if (ObjectUtil.isNotNull(existId)) {
data.setId(existId);
// 累计更新列表
cachedUpdateDataList.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedUpdateDataList.size() >= BATCH_COUNT) {
updateData();
// 存储完成清理 list
cachedUpdateDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
return;
}
// 校验分类名称组织内是否存在
validClassificationName(data.getClassificationName(), rowIndex);
// 校验属性长度
validFieldLength(headMap.get(0), veneerNo, 32, rowIndex);
@@ -108,6 +106,22 @@ public class VeneerExcelListener<T> extends AnalysisEventListener<VeneerImportEx
validPositiveFloatNumberSize(headMap.get(9), data.getThickness(), Double.class, 5, 3, rowIndex);
validPositiveNumber(headMap.get(13), data.getNum(), rowIndex);
// 校验贴皮是新增还是更新
Long existId = orgVeneerNoMap.get(veneerNo);
if (ObjectUtil.isNotNull(existId)) {
data.setId(existId);
// 累计更新列表
cachedUpdateDataList.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedUpdateDataList.size() >= BATCH_COUNT) {
updateData();
// 存储完成清理 list
cachedUpdateDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
return;
}
// 累计新增列表
cachedInsertDataList.add(data);
@@ -119,11 +133,6 @@ public class VeneerExcelListener<T> extends AnalysisEventListener<VeneerImportEx
}
}
@Override
public void extra(CellExtra extra, AnalysisContext context) {
super.extra(extra, context);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
@@ -132,11 +141,6 @@ public class VeneerExcelListener<T> extends AnalysisEventListener<VeneerImportEx
log.debug("所有数据解析完成!");
}
@Override
public boolean hasNext(AnalysisContext context) {
return super.hasNext(context);
}
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
this.headMap = headMap;
@@ -172,6 +176,16 @@ public class VeneerExcelListener<T> extends AnalysisEventListener<VeneerImportEx
log.debug("excel导入贴皮数据保存成功!");
}
/**
* 校验组织内分类名称是否存在
* @param classificationName
*/
private void validClassificationName(String classificationName, int rowIndex) {
if (!orgClassificationNameSet.contains(classificationName)) {
throw new ServiceException(VENEER_IMPORT_ORG_CLASSIFICATION_NAME_NOT_EXIST, rowIndex, classificationName);
}
}
/**
* 校验长度
*
@@ -240,11 +254,7 @@ public class VeneerExcelListener<T> extends AnalysisEventListener<VeneerImportEx
try {
double number = Double.parseDouble(numberStr);
// 进行正负判断
if (number > 0) {
return true;
} else {
return false;
}
return number > 0;
} catch (NumberFormatException e) {
return false;
}
@@ -0,0 +1,55 @@
package com.cf.imes.module.executor.service.veneer;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddressList;
/**
* 贴皮excel模版分类名称填充处理器
*
* @author Gqr
* @since 2026/1/22 18:50
*/
public class VeneerExcelTemplateClassificationNameWriteHandler implements SheetWriteHandler {
private final int columnIndex; // 0-basedC 列 = 2
private final String[] options;
private final int firstRow;
private final int lastRow;
public VeneerExcelTemplateClassificationNameWriteHandler(int columnIndex,
String[] options,
int firstRow,
int lastRow) {
this.columnIndex = columnIndex;
this.options = options;
this.firstRow = firstRow;
this.lastRow = lastRow;
}
@Override
public void afterSheetCreate(WriteWorkbookHolder workbookHolder,
WriteSheetHolder sheetHolder) {
Sheet sheet = sheetHolder.getSheet();
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint constraint =
helper.createExplicitListConstraint(options);
CellRangeAddressList addressList =
new CellRangeAddressList(firstRow, lastRow, columnIndex, columnIndex);
DataValidation validation =
helper.createValidation(constraint, addressList);
validation.setShowErrorBox(true);
validation.setSuppressDropDownArrow(true);
sheet.addValidationData(validation);
}
}
@@ -1,11 +1,14 @@
package com.cf.imes.module.executor.service.veneer;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameSaveReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPageReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDO;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@@ -52,6 +55,13 @@ public interface VeneerService {
*/
Long uploadPic(MultipartFile file) throws IOException;
/**
* 贴皮导入模版下载
*
* @throws IOException
*/
void importTemplateDownload(HttpServletResponse response);
/**
* 分页查询贴皮
*
@@ -96,4 +106,19 @@ public interface VeneerService {
* @return
*/
void saveDefect(VeneerDefectSaveReqVO defectSaveReqVO);
/**
* 新增分类名称
*
* @param saveReqVO 名称
* @return
*/
Long addClassificationName(VeneerClassificationNameSaveReqVO saveReqVO);
/**
* 获取组织分类名称列表
*
* @return
*/
List<VeneerClassificationNameRespVO> getOrgVeneerClassificationNameList();
}
@@ -1,8 +1,11 @@
package com.cf.imes.module.executor.service.veneer;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cf.imes.framework.common.exception.ServiceException;
@@ -11,23 +14,33 @@ import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.json.JsonUtils;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.executor.constants.file.TemplateFilePathConstants;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameSaveReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPageReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerClassificationNameDO;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDO;
import com.cf.imes.module.executor.dal.mysql.veneer.VeneerBatchMapper;
import com.cf.imes.module.executor.dal.mysql.veneer.VeneerClassificationNameMapper;
import com.cf.imes.module.executor.dal.mysql.veneer.VeneerMapper;
import com.cf.imes.module.executor.util.file.FileHelperUtil;
import com.cf.imes.module.infra.api.file.FileApi;
import com.cf.imes.module.system.api.organ.OrganApi;
import com.cf.imes.module.system.api.organ.dto.OrganizationDTO;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -35,8 +48,11 @@ import java.util.Optional;
import java.util.stream.Collectors;
import static cn.hutool.core.date.DatePattern.PURE_DATETIME_PATTERN;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_TEMPLATE_DOWNLOAD_ERROR;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_NOT_EXIST;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_NO_EXIST;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_ORG_CLASSIFICATION_NAME_EMPTY;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_ORG_CLASSIFICATION_NAME_EXIST;
/**
* 贴皮管理ServiceImpl 实现类
@@ -58,6 +74,9 @@ public class VeneerServiceImpl implements VeneerService {
@Resource
private VeneerBatchMapper veneerBatchMapper;
@Resource
private VeneerClassificationNameMapper veneerClassificationNameMapper;
@Override
public Long create(VeneerSaveReqVO reqVO) {
// 1、校验贴皮编号
@@ -120,6 +139,39 @@ public class VeneerServiceImpl implements VeneerService {
return fileApi.createFileAndReturnId(file.getBytes(), String.format("%s-veneer-pic-file-%s.%s", organizationDTO.getName(), LocalDateTimeUtil.format(LocalDateTime.now(), PURE_DATETIME_PATTERN), getFileExtension(file.getOriginalFilename())));
}
@Override
public void importTemplateDownload(HttpServletResponse response) {
try {
// 根据语种获取模版的路径
String veneerImportTemplatePath = TemplateFilePathConstants.getVeneerImportTemplatePath(LocaleContextHolder.getLocale().getLanguage());
// 获取文件
File templateFile = FileHelperUtil.getFirstFile(veneerImportTemplatePath);
// 获取组织分类名称列表
List<VeneerClassificationNameRespVO> orgVeneerClassificationNameList = getOrgVeneerClassificationNameList();
if (CollUtil.isEmpty(orgVeneerClassificationNameList)) {
throw new ServiceException(VENEER_ORG_CLASSIFICATION_NAME_EMPTY);
}
String[] options = orgVeneerClassificationNameList.stream().map(VeneerClassificationNameRespVO::getName).toArray(String[]::new);
// 从第2行开始, 到200行写入数据源
EasyExcelFactory.write(response.getOutputStream())
.withTemplate(FileUtil.getInputStream(templateFile))
.registerWriteHandler(new VeneerExcelTemplateClassificationNameWriteHandler(
2,
options,
1,
200
))
.sheet(0)
.doWrite(Collections.emptyList());
} catch (IOException ie) {
throw new ServiceException(VENEER_IMPORT_TEMPLATE_DOWNLOAD_ERROR);
}
}
@Override
public PageResult<VeneerRespVO> getPage(VeneerPageReqVO pageReqVO) {
PageResult<VeneerDO> veneerDOPageResult = veneerMapper.selectPage(pageReqVO);
@@ -217,4 +269,33 @@ public class VeneerServiceImpl implements VeneerService {
.map(ext -> ext.toLowerCase(Locale.ROOT))
.orElse(".jpg");
}
@Override
public Long addClassificationName(VeneerClassificationNameSaveReqVO saveReqVO) {
String classificationName = saveReqVO.getName();
// 校验组织下分类名称唯一
validOrgClassificationName(classificationName);
VeneerClassificationNameDO veneerClassificationNameDO = new VeneerClassificationNameDO();
veneerClassificationNameDO.setName(classificationName);
veneerClassificationNameMapper.insert(veneerClassificationNameDO);
return veneerClassificationNameDO.getId();
}
/**
* 校验组织下分类名称唯一
*
* @param classificationName
*/
private void validOrgClassificationName(String classificationName) {
if (veneerClassificationNameMapper.selectCount(new LambdaQueryWrapper<VeneerClassificationNameDO>()
.eq(VeneerClassificationNameDO::getName, classificationName)) > 0) {
throw new ServiceException(VENEER_ORG_CLASSIFICATION_NAME_EXIST);
}
}
@Override
public List<VeneerClassificationNameRespVO> getOrgVeneerClassificationNameList() {
return BeanUtil.copyToList(veneerClassificationNameMapper.selectList(), VeneerClassificationNameRespVO.class);
}
}
@@ -32,7 +32,6 @@ public class FileHelperUtil {
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) {
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
fis.close();
response.reset();
response.setCharacterEncoding(CharsetUtil.UTF_8);
@@ -67,12 +66,11 @@ public class FileHelperUtil {
}
/**
* 下载目录下第一个文件
* 获取目录下第一个文件
*
* @param dirPath
* @param response
*/
public static void getFirstFile(String dirPath, HttpServletResponse response) {
public static File getFirstFile(String dirPath) {
File dir = new File(dirPath);
if (!dir.exists() || !dir.isDirectory()) {
throw new ServiceException(FILE_NOT_EXISTS);
@@ -83,26 +81,6 @@ public class FileHelperUtil {
throw new ServiceException(FILE_NOT_EXISTS);
}
File file = files[0];
// 获取文件名
String filename = file.getName();
// 将文件写入输入流
try (FileInputStream fis = new FileInputStream(file);
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) {
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
fis.close();
response.reset();
response.setCharacterEncoding(CharsetUtil.UTF_8);
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, CharsetUtil.UTF_8));
response.addHeader("Content-Length", "" + file.length());
response.setContentType("application/octet-stream");
outputStream.write(buffer);
outputStream.flush();
} catch (IOException ex) {
throw new ServiceException(FILE_NOT_EXISTS);
}
return files[0];
}
}
@@ -16,3 +16,5 @@ veneer.save.width.not.null=宽度不能为空
veneer.save.veneerName.not.null=贴皮名称不能为空
veneer.save.veneerName.length.max=贴皮名称不能超过64个字符
veneer.defect.save.id.not.null=贴皮ID不能为空
veneer.classification.name.save.name.not.null=分类名称不能为空
veneer.classification.name.save.name.length.max=分类名称长度不能超过64
@@ -14,5 +14,7 @@ veneer.save.veneerNo.length.max=貼皮編號不能超過32個字元
veneer.save.veneerNo.not.null=貼皮編號不能為空
veneer.save.width.not.null=寬度不能為空
veneer.save.veneerName.not.null=貼皮名稱不能為空
veneer.save.veneerName.length.max=貼皮名稱不能超过64个字
veneer.save.veneerName.length.max=貼皮名稱不能超过64个字
veneer.defect.save.id.not.null=貼皮ID不能為空
veneer.classification.name.save.name.not.null=分類名稱不能為空
veneer.classification.name.save.name.length.max=分類名稱不能超過64個字元
@@ -16,3 +16,5 @@ veneer.save.width.not.null=width cannot be empty
veneer.save.veneerName.not.null=veneer name cannot be empty
veneer.save.veneerName.length.max=veneer name length cannot exceed 64 characters
veneer.defect.save.id.not.null=veneer id cannot be empty
veneer.classification.name.save.name.not.null=classification name cannot be empty
veneer.classification.name.save.name.length.max=classification name length cannot exceed 64 characters
@@ -21,3 +21,7 @@ veneer.import.length.error=第{0}行{1}长度不能超过{2}个字符,请检
veneer.import.digital.type.error=第{0}行{1}只能输入数字,请检查文件
veneer.import.positive.number.error=第{0}行{1}必须大于0,请检查文件
veneer.import.number.length.error=第{0}行{1}长度不能超过整数{2}位、小数{3}位,请检查文件
veneer.org.classification.name.exist=组织下分类名称已存在,请检查
veneer.org.classification.name.empty=组织下分类名称不存在,请前往 [新增] 窗口维护分类名称
veneer.import.template.download.error=导入模版下载失败
veneer.import.org.classification.name.not.exist=第{0}行分类名称{1}不存在,请前往 [新增] 窗口维护分类名称
@@ -21,3 +21,7 @@ veneer.import.length.error=第{0}行{1}長度不能超過{2}個字元,請檢
veneer.import.digital.type.error=第{0}行{1}只能輸入數位,請檢查檔案
veneer.import.positive.number.error=第{0}行{1}必須大於0,請檢查檔案
veneer.import.number.length.error=第{0}行{1}長度不能超過整數{2}位、小數{3}位,請檢查檔案
veneer.org.classification.name.exist=組織下分類名稱已存在,請檢查
veneer.org.classification.name.empty=組織下分類名稱不存在,請前往 [新增] 視窗維護分類名稱
veneer.import.template.download.error=導入模版下載失敗
veneer.import.org.classification.name.not.exist=第{0}行分類名稱{1}不存在,請前往[新增]視窗維護分類名稱
@@ -21,3 +21,7 @@ veneer.import.length.error=The length of the field cannot exceed {0} characters
veneer.import.digital.type.error=The field can only be filled in with numbers in line {0}, please check the file
veneer.import.positive.number.error=The field must be greater than 0 in line {0}, please check the file
veneer.import.number.length.error=The length of the field cannot exceed integer {0} digits and decimal {1} digits in line {2}, please check the file
veneer.org.classification.name.exist=The classification name already exists under the organization, please check
veneer.org.classification.name.empty=The classification name does not exist under the organization, Please go to the [Add] window to maintain the classification name
veneer.import.template.download.error=Import template download failed
veneer.import.org.classification.name.not.exist=The classification name {1} does not exist in line {0}, please go to the [Add] window to maintain the classification name
@@ -1,17 +1,23 @@
package com.cf.imes.module.executor.controller.admin.veneer;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.builder.ExcelReaderSheetBuilder;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.security.test.WithMockLoginUser;
import com.cf.imes.framework.test.core.ut.BaseWebUnitTest;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameSaveReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerImportExcelVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO;
import com.cf.imes.module.executor.enums.veneer.VeneerProductLevelEnum;
import com.cf.imes.module.executor.service.veneer.VeneerExcelListener;
import com.cf.imes.module.executor.service.veneer.VeneerService;
import com.cf.imes.module.executor.util.file.FileHelperUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,11 +28,14 @@ import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
@@ -161,32 +170,55 @@ class VeneerControllerTest extends BaseWebUnitTest {
}
@Test
@WithMockLoginUser
void importExcel_success() throws Exception {
MockMultipartFile file = new MockMultipartFile(
"file", "test.xlsx",
"application/vnd.ms-excel",
new byte[]{1, 2, 3}
"file",
"veneer.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"dummy excel content".getBytes()
);
mockMvc.perform(multipart("/executor/veneer/import").file(file))
.andExpect(status().isOk());
try (
MockedStatic<EasyExcelFactory> excelMock =
Mockito.mockStatic(EasyExcelFactory.class);
MockedConstruction<VeneerExcelListener> listenerMock =
Mockito.mockConstruction(VeneerExcelListener.class)
) {
ExcelReaderBuilder readerBuilder = Mockito.mock(ExcelReaderBuilder.class);
ExcelReaderSheetBuilder sheetBuilder = Mockito.mock(ExcelReaderSheetBuilder.class);
excelMock.when(() ->
EasyExcelFactory.read(
any(InputStream.class),
eq(VeneerImportExcelVO.class),
any()
)
).thenReturn(readerBuilder);
Mockito.when(readerBuilder.headRowNumber(anyInt()))
.thenReturn(readerBuilder);
Mockito.when(readerBuilder.sheet())
.thenReturn(sheetBuilder);
Mockito.doNothing().when(sheetBuilder).doRead();
mockMvc.perform(
multipart("/executor/veneer/import").file(file)
).andExpect(status().isOk());
}
}
@Test
void getVeneerImportTemplate_success() throws Exception {
try (MockedStatic<FileHelperUtil> mocked =
Mockito.mockStatic(FileHelperUtil.class)) {
mocked.when(() ->
FileHelperUtil.getFirstFile(
anyString(),
any(HttpServletResponse.class))
).thenAnswer(invocation -> null);
Mockito.doNothing()
.when(veneerService)
.importTemplateDownload(any(HttpServletResponse.class));
mockMvc.perform(get("/executor/veneer/import/template"))
.andExpect(status().isOk());
}
Mockito.verify(veneerService)
.importTemplateDownload(any(HttpServletResponse.class));
}
@Test
@@ -203,4 +235,48 @@ class VeneerControllerTest extends BaseWebUnitTest {
.andExpect(jsonPath("$.data").value(true));
}
@Test
void createClassificationName_success() throws Exception {
VeneerClassificationNameSaveReqVO reqVO = new VeneerClassificationNameSaveReqVO();
reqVO.setName("测试分类");
Mockito.when(veneerService.addClassificationName(any()))
.thenReturn(1L);
mockMvc.perform(
post("/executor/veneer/classificationName")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(reqVO))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.data").value(1L));
Mockito.verify(veneerService)
.addClassificationName(any(VeneerClassificationNameSaveReqVO.class));
}
@Test
void getOrgVeneerClassificationNameList_success() throws Exception {
VeneerClassificationNameRespVO respVO = new VeneerClassificationNameRespVO();
respVO.setId(1L);
respVO.setName("测试分类");
Mockito.when(veneerService.getOrgVeneerClassificationNameList())
.thenReturn(List.of(respVO));
mockMvc.perform(
get("/executor/veneer/org/classificationName")
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.data[0].id").value(1L))
.andExpect(jsonPath("$.data[0].name").value("测试分类"));
Mockito.verify(veneerService)
.getOrgVeneerClassificationNameList();
}
}
@@ -1,6 +1,10 @@
package com.cf.imes.module.executor.service.veneer;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
@@ -11,25 +15,41 @@ import com.cf.imes.framework.common.util.json.JsonUtils;
import com.cf.imes.framework.security.test.WithMockLoginUser;
import com.cf.imes.framework.test.core.ut.BaseMockitoUnitTest;
import com.cf.imes.module.executor.common.WithMockLoginUserExtension;
import com.cf.imes.module.executor.constants.file.TemplateFilePathConstants;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerClassificationNameSaveReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDefectSaveReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPageReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerSaveReqVO;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerClassificationNameDO;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDO;
import com.cf.imes.module.executor.dal.mysql.veneer.VeneerBatchMapper;
import com.cf.imes.module.executor.dal.mysql.veneer.VeneerClassificationNameMapper;
import com.cf.imes.module.executor.dal.mysql.veneer.VeneerMapper;
import com.cf.imes.module.executor.enums.veneer.VeneerProductLevelEnum;
import com.cf.imes.module.executor.util.file.FileHelperUtil;
import com.cf.imes.module.infra.api.file.FileApi;
import com.cf.imes.module.system.api.organ.OrganApi;
import com.cf.imes.module.system.api.organ.dto.OrganizationDTO;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.springframework.mock.web.MockMultipartFile;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@@ -39,6 +59,7 @@ import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceExce
import static com.cf.imes.framework.test.core.util.RandomUtils.randomPojo;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_NOT_EXIST;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_NO_EXIST;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_ORG_CLASSIFICATION_NAME_EXIST;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -46,10 +67,13 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -78,6 +102,12 @@ class VeneerServiceImplTest extends BaseMockitoUnitTest {
@Mock
private VeneerBatchMapper veneerBatchMapper;
@Mock
private VeneerClassificationNameMapper veneerClassificationNameMapper;
@Mock
private HttpServletResponse response;
@BeforeAll
static void init(){
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), VeneerDO.class);
@@ -481,6 +511,208 @@ class VeneerServiceImplTest extends BaseMockitoUnitTest {
verify(veneerMapper, never()).update(any());
}
@Test
void addClassificationName_success() {
// given
VeneerClassificationNameSaveReqVO reqVO =
new VeneerClassificationNameSaveReqVO();
reqVO.setName("测试分类");
// 唯一校验通过
Mockito.when(veneerClassificationNameMapper.selectCount(any()))
.thenReturn(0L);
// mock insert 时给 id
Mockito.doAnswer(invocation -> {
VeneerClassificationNameDO entity = invocation.getArgument(0);
entity.setId(1L);
return 1;
}).when(veneerClassificationNameMapper).insert(any(VeneerClassificationNameDO.class));
// when
Long id = veneerService.addClassificationName(reqVO);
// then
assertEquals(1L, id);
Mockito.verify(veneerClassificationNameMapper)
.selectCount(any());
Mockito.verify(veneerClassificationNameMapper)
.insert(any(VeneerClassificationNameDO.class));
}
@Test
void addClassificationName_exist_throwException() {
VeneerClassificationNameSaveReqVO reqVO =
new VeneerClassificationNameSaveReqVO();
reqVO.setName("重复分类");
// 已存在
Mockito.when(veneerClassificationNameMapper.selectCount(any()))
.thenReturn(1L);
assertServiceException(() -> veneerService.addClassificationName(reqVO),
VENEER_ORG_CLASSIFICATION_NAME_EXIST);
// insert 一定不会被调用
Mockito.verify(veneerClassificationNameMapper, Mockito.never())
.insert(any(VeneerClassificationNameDO.class));
}
@Test
void getOrgVeneerClassificationNameList_success() {
VeneerClassificationNameDO entity = new VeneerClassificationNameDO();
entity.setId(1L);
entity.setName("分类A");
Mockito.when(veneerClassificationNameMapper.selectList())
.thenReturn(List.of(entity));
List<VeneerClassificationNameRespVO> result =
veneerService.getOrgVeneerClassificationNameList();
assertEquals(1, result.size());
assertEquals("分类A", result.get(0).getName());
Mockito.verify(veneerClassificationNameMapper)
.selectList();
}
@Test
void importTemplateDownload_success() throws IOException {
File mockFile = Mockito.mock(File.class);
try (
MockedStatic<TemplateFilePathConstants> pathMock =
Mockito.mockStatic(TemplateFilePathConstants.class);
MockedStatic<FileUtil> fileUtilMock =
Mockito.mockStatic(FileUtil.class);
MockedStatic<FileHelperUtil> fileMock =
Mockito.mockStatic(FileHelperUtil.class);
MockedStatic<EasyExcelFactory> excelMock =
Mockito.mockStatic(EasyExcelFactory.class)
) {
VeneerClassificationNameDO veneerClassificationNameDO = new VeneerClassificationNameDO();
veneerClassificationNameDO.setName("分类名称");
when(veneerClassificationNameMapper.selectList()).thenReturn(List.of(veneerClassificationNameDO));
pathMock.when(() ->
TemplateFilePathConstants.getVeneerImportTemplatePath(any())
).thenReturn("/mock/path");
fileMock.when(() ->
FileHelperUtil.getFirstFile(anyString())
).thenReturn(mockFile);
// ⭐ 关键 1:mock 模板流(不碰文件)
fileUtilMock.when(() -> FileUtil.getInputStream(any(File.class)))
.thenReturn(new BufferedInputStream(new ByteArrayInputStream(new byte[]{1})));
// mock response OutputStream
ServletOutputStream outputStream = Mockito.mock(ServletOutputStream.class);
Mockito.when(response.getOutputStream())
.thenReturn(outputStream);
// mock EasyExcel 写链
ExcelWriterBuilder writerBuilder = Mockito.mock(ExcelWriterBuilder.class);
ExcelWriterSheetBuilder sheetBuilder = Mockito.mock(ExcelWriterSheetBuilder.class);
excelMock.when(() -> EasyExcelFactory.write(any(OutputStream.class)))
.thenReturn(writerBuilder);
when(writerBuilder.withTemplate(
any(InputStream.class)))
.thenReturn(writerBuilder);
when(writerBuilder.registerWriteHandler(any()))
.thenReturn(writerBuilder);
when(writerBuilder.sheet(anyInt()))
.thenReturn(sheetBuilder);
doNothing().when(sheetBuilder).doWrite(anyList());
// 执行
assertDoesNotThrow(() ->
veneerService.importTemplateDownload(response));
}
}
@Test
void importTemplateDownload_fail_when_org_classification_name_list_is_empty() {
// 1️⃣ mock 模板路径 & 文件
File mockFile = Mockito.mock(File.class);
try (
MockedStatic<TemplateFilePathConstants> pathMock =
Mockito.mockStatic(TemplateFilePathConstants.class);
MockedStatic<FileHelperUtil> fileMock =
Mockito.mockStatic(FileHelperUtil.class)
) {
pathMock.when(() ->
TemplateFilePathConstants.getVeneerImportTemplatePath(any())
).thenReturn("/mock/path");
fileMock.when(() ->
FileHelperUtil.getFirstFile(anyString())
).thenReturn(mockFile);
// 2️⃣ mock 分类名称列表为空
Mockito.when(veneerClassificationNameMapper.selectList())
.thenReturn(new ArrayList<>());
// 3️⃣ 验证抛出异常
assertServiceException(() -> veneerService.importTemplateDownload(response),
com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_ORG_CLASSIFICATION_NAME_EMPTY);
}
}
@Test
void importTemplateDownload_fail_when_io_exception_occurs() throws Exception {
// 1️⃣ mock 模板路径 & 文件
File mockFile = Mockito.mock(File.class);
try (
MockedStatic<TemplateFilePathConstants> pathMock =
Mockito.mockStatic(TemplateFilePathConstants.class);
MockedStatic<FileHelperUtil> fileMock =
Mockito.mockStatic(FileHelperUtil.class);
MockedStatic<EasyExcelFactory> excelMock =
Mockito.mockStatic(EasyExcelFactory.class)
) {
pathMock.when(() ->
TemplateFilePathConstants.getVeneerImportTemplatePath(any())
).thenReturn("/mock/path");
fileMock.when(() ->
FileHelperUtil.getFirstFile(anyString())
).thenReturn(mockFile);
// 2️⃣ mock 分类名称列表
VeneerClassificationNameDO entity = new VeneerClassificationNameDO();
entity.setName("分类A");
Mockito.when(veneerClassificationNameMapper.selectList())
.thenReturn(List.of(entity));
// 3️⃣ mock response OutputStream 并抛出IOException
Mockito.when(response.getOutputStream())
.thenThrow(new java.io.IOException("IO Error"));
// 4️⃣ 验证抛出异常
assertServiceException(() -> veneerService.importTemplateDownload(response),
com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_IMPORT_TEMPLATE_DOWNLOAD_ERROR);
}
}
@Test
void getFileExtension_whenEmpty() {
assertEquals(".jpg", veneerService.getFileExtension("file."));