【POI】Excel数据导入

 

Postman请求方式:

 

Controller接口代码:

    /**
     * /partImport/part/importUpload
     * @param importFile
     * @return
     */
    @PostMapping("/part/importUpload")
    public Map<String, Object> importUpload(@RequestParam(value = "file") MultipartFile importFile) {
        return partMainDataImportService.importUpload(importFile);
    }

对象类型:

import org.springframework.web.multipart.MultipartFile;

 

逻辑思路:

1、Excel转变可获取数据

2、数据结果集插入DB

    @Override
    public Map<String, Object> importUpload(MultipartFile importFile) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Map<String, Object> resultMap = new HashMap<>();

        List<Map<Integer, String>> partMainDatas = null; // Sheet1 配件主数据信息
        List<Map<Integer, String>> partExclusivePrice = null; // Sheet2 配件专属价格
        List<Map<Integer, String>> partReplacements = null; // Sheet3 配件替换件

        try {
            // 获取数据集合对象 getDataListFromImportExcel导出方法
            partMainDatas = getDataListFromImportExcel(importFile, resultMap, 0);
            partExclusivePrice = getDataListFromImportExcel(importFile, resultMap, 1);
            partReplacements = getDataListFromImportExcel(importFile, resultMap, 2);

            Integer integer = insertPartMainData(partMainDatas);
            resultMap.put("partMainDatas", integer);

            if (!CollectionUtils.isEmpty(partExclusivePrice)) {
                Integer integer2 = insertExclusivePrice(partExclusivePrice);
                resultMap.put("partOwnPrices", integer2);
            } else {
                resultMap.put("partOwnPrices", 0);
            }
            if (!CollectionUtils.isEmpty(partReplacements)) {
                Integer integer3 = insertReplacements(partReplacements);
                resultMap.put("partShifts", integer3);
            } else {
                resultMap.put("partShifts", 0);
            }

        } catch (Exception exception) {
            exception.printStackTrace();
            return resultMap;
        } finally {
            stopWatch.stop();
            resultMap.put("execTime", stopWatch.getTotalTimeSeconds() + '秒');
        }
        resultMap.put("200", "导入成功");
        return resultMap;
    }

 

Excel转换到数据逻辑部分:

    /**
     * 提供给配件主数据导入处理
     * @param importFile
     * @param map 响应结果,如果报错异常,则在这里设置
     * @return
     */
    private List<Map<Integer, String>> getDataListFromImportExcel(MultipartFile importFile, Map<String, Object> map, Integer sheetIndex) throws Exception {

        Workbook workbook = null; // Excel工作薄
        Sheet sheet = null; // Excel表单 Sheet页
        Row row = null; //
        Map<Integer, String> dataMap = null; // 表单中的一行数据
        List<Map<Integer, String>> dataList = null; // 表单中的所有数据
        boolean isValidRow = false; // Excel行中是否是有效数据行

        InputStream inputStream = importFile.getInputStream(); // 文件流
        String filename = importFile.getOriginalFilename(); // 文件名


        // 获取Excel工作簿对象
        if ("xls".equalsIgnoreCase(filename.substring(filename.lastIndexOf(".") + 1, filename.length()))) {
            workbook = new HSSFWorkbook(inputStream);
        } else {
            workbook = new XSSFWorkbook(inputStream);
        }

        sheet = workbook.getSheetAt(sheetIndex); // 获取指定Sheet页来处理
        Boolean flag = false; // 标记,如果检测到空行为true
        Integer num = 0;
        dataList = new ArrayList<>();

        int physicalNumberOfRows = sheet.getPhysicalNumberOfRows();
        for (int i = 1 /* 从表单第二行开始读取数据,读取整个表单中的数据 */; i < physicalNumberOfRows; i++) {
            num = 0;
            if(flag){
                break;
            }
            row = sheet.getRow(i);
            if (null != row) {
                dataMap = new HashMap<>();

                int physicalNumberOfCells = sheet.getRow(0).getPhysicalNumberOfCells();

                for (int j = 0; j < physicalNumberOfCells; j++) {

                    if(null != row.getCell(j) && !StringUtils.isNullOrEmpty(row.getCell(j).getStringCellValue())){
                        num ++;
                        break;
                    }else if(sheet.getRow(0).getPhysicalNumberOfCells() - 1  == j && num == 0){
                        flag = true; // true结束
                        break;
                    }
                }
                    // 根据模板表头长度,读取一行数据
                for (int j = 0; j < sheet.getRow(0).getPhysicalNumberOfCells(); j++) {
                    if(flag){
                        break;
                    }
                    // 属于零件主数据导入的判断逻辑, sheet主数据 且下标是这个集合内的,判断空则抛出空异常
                    if (
                            sheetIndex.equals(0) &&
                                    this.validateColumnIndexListForMainData.contains(j) &&
                                    null == row.getCell(j)
                    ) {
                        map.put("5002", "导入的Excel数据(Sheet1配件主数据)必填字段存在空值!!!");
                        throw new ServiceBizException(ERR_MESSAGE);
                    }
                    else if (sheetIndex.equals(1) &&  null == row.getCell(j)) {
                        map.put("5003", "导入的Excel数据(Sheet2专属价格)存在空值!!!");
                        throw new ServiceBizException(ERR_MESSAGE);
                    }
                    else if (sheetIndex.equals(2) &&  null == row.getCell(j)) {
                        map.put("5004", "导入的Excel数据(Sheet3替换件)存在空值!!!");
                        throw new ServiceBizException(ERR_MESSAGE);
                    }

                    if (row.getCell(j) == null) {
                        dataMap.put(j, "");
                    } else {
                        String parseExcel = parseExcel(row.getCell(j)).trim();
                        if (!StringUtils.isBlank(parseExcel)) {
                            dataMap.put(j, parseExcel);
                            isValidRow = true;
                        } else {
                            dataMap.put(j, "");
                        }
                    }
                }

                /**
                 * 读取完一条记录,如果是有效数据行,则加入返回结果中
                 */
                if (isValidRow) {
                    dataList.add(dataMap);
                }
            }
        }
        if (CollectionUtils.isEmpty(dataList) && sheetIndex.equals(0)) {
            map.put("5001", "导入的Excel数据不能为空");
            throw new ServiceBizException(ERR_MESSAGE);
        }
        return dataList;
    }

这里有一段解析处理,有点麻烦

private static String parseExcel(Cell cell) {
        String result = "";
        switch (cell.getCellType()) {
            case HSSFCell.CELL_TYPE_NUMERIC:// 数字类型
                if (HSSFDateUtil.isCellDateFormatted(cell)) {// 处理日期格式、时间格式
                    SimpleDateFormat sdf = null;
                    if (cell.getCellStyle().getDataFormat() == HSSFDataFormat.getBuiltinFormat("h:mm")) {
                        sdf = new SimpleDateFormat("HH:mm");
                    } else {// 日期
                        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    }
                    Date date = cell.getDateCellValue();
                    result = sdf.format(date);
                } else if (cell.getCellStyle().getDataFormat() == 58) {
                    // 处理自定义日期格式:m月d日(通过判断单元格的格式id解决,id的值是58)
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                    double value = cell.getNumericCellValue();
                    Date date = org.apache.poi.ss.usermodel.DateUtil.getJavaDate(value);
                    result = sdf.format(date);
                } else {
                    cell.setCellType(XSSFCell.CELL_TYPE_STRING);
                    result = cell.getStringCellValue().toString();
                }
                break;
            case HSSFCell.CELL_TYPE_STRING:// String类型
                result = cell.getRichStringCellValue().toString();
                break;
            case HSSFCell.CELL_TYPE_BLANK:
                result = "";
                break;
            default:
                result = "";
                break;
        }
        return result;

 

然后第二部分数据封装对象来插入DB:

/**
     *
     * @param datas
     * @return
     */
    private Integer insertPartMainData(List<Map<Integer, String>> datas) throws Exception {
        Integer affectRows = 0;

        Class<PartInfoPO> partInfoPOClass = PartInfoPO.class;
        PartInfoPO partInfoPO ;
        // 先遍历集合
        for (Map<Integer, String> row : datas) {
            partInfoPO = new PartInfoPO();
            partInfoPO.setOwnerCode("-1");
            // pmd是每一行的结果
            Set<Integer> columns = row.keySet();
            // 遍历每一行获取单元格的值
            for (Integer column : columns) {
                String fieldKey = fields.get(column);
                String fieldValue = row.get(column);
                if(StringUtils.isNullOrEmpty(fieldValue)){continue;}

                Field field = partInfoPOClass.getDeclaredField(fieldKey);
                field.setAccessible(true);
                Class<?> type = field.getType();

                fieldValue = "是".equals(fieldValue) ? YES :  "否".equals(fieldValue) ? NO : fieldValue;

                if (Integer.class.equals(type)) {
                    // fieldValue = "是".equals(fieldValue) ? "10041001" :  "否".equals(fieldValue) ? "10041002" : fieldValue;
                    field.set(partInfoPO, Integer.valueOf(fieldValue));
                }
                else if(BigDecimal.class.equals(type)) {
                    // fieldValue = "是".equals(fieldValue) ? "10041001" :  "否".equals(fieldValue) ? "10041002" : fieldValue;
                    field.set(partInfoPO, new BigDecimal(fieldValue));
                }
                else if(Date.class.equals(type) ) {
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
                    field.set(partInfoPO,  simpleDateFormat.parse(fieldValue));
                }
                else {
                    field.set(partInfoPO, fieldValue);
                }
            }
            affectRows += partInfoMapper.insert(partInfoPO);
        }
        return affectRows;
    }

 

2021年11月18日 21点51分 更新:

导入需求有更为明确的逻辑:

 

这里重新更改了逻辑

以主数据为例,使用了双向取Key的工具Map

private static final BidiMap<String, String> MAIN_DATA_ALIAS = new DualHashBidiMap<>();

配置Excel固定的字段映射匹配:

MAIN_DATA_ALIAS.put("*配件代码", PART_NO);
MAIN_DATA_ALIAS.put("*配件名称", "partName");
MAIN_DATA_ALIAS.put("配件英文名", "partNameEn");
MAIN_DATA_ALIAS.put("计量单位代码", "unitCode");
MAIN_DATA_ALIAS.put("最小包装数", "minPackage");
MAIN_DATA_ALIAS.put("*适用车型", "applicableVehicleType");
MAIN_DATA_ALIAS.put("*型号", "model");
MAIN_DATA_ALIAS.put("替换件代码", "optionPartNo");
MAIN_DATA_ALIAS.put("替换方式", "optionDirect");
MAIN_DATA_ALIAS.put("单车用量", "quantityPerCar");
MAIN_DATA_ALIAS.put("*采购价(含税)", "planPriceTaxed");
MAIN_DATA_ALIAS.put("*采购价(不含税)", "planPriceNoTaxed");
MAIN_DATA_ALIAS.put("*厂端建议销售价(含税)", "limitPrice");
MAIN_DATA_ALIAS.put("*建议零售价(含税)", "suggestRetailPriceTaxed");
MAIN_DATA_ALIAS.put("*建议零售价(不含税)", "suggestRetailPriceNoTaxed");
MAIN_DATA_ALIAS.put("*三包标志", "isTripleGuarantee");
MAIN_DATA_ALIAS.put("*不可索赔标志", "isClaim");
MAIN_DATA_ALIAS.put("*是否有效", "partStatus");
MAIN_DATA_ALIAS.put("*是否危险品", "isUnsafe");
MAIN_DATA_ALIAS.put("*是否追溯件", "isBack");
MAIN_DATA_ALIAS.put("*是否DD件", "isDd");
MAIN_DATA_ALIAS.put("*是否监控件", "isMonitor");
MAIN_DATA_ALIAS.put("*是否可销售", "isCSale");
MAIN_DATA_ALIAS.put("*是否可采购", "isCPurchase");
MAIN_DATA_ALIAS.put("*是否为电池零件", "isBattery");
MAIN_DATA_ALIAS.put("备注", "remark");
MAIN_DATA_ALIAS.put("*停用", "isDisable");
MAIN_DATA_ALIAS.put("停用日期", "disableDate");

其中带星号的标记为必填字段

对于Excel的数据约束控制,后台不做处理了,Excel来设置数据有效性控制即可

 

数值类型在DB中使用了Decimal存储,在PO封装的时候可以使用String处理,就是要考虑空值情况

为了方便,只剩下日期类型必须Date类型存储

DATE_FIELDS.add("disableDate");
DATE_FIELDS.add("beginAt");
DATE_FIELDS.add("endAt");

读取的工作由Hutool工具完成,代码简单也好用

/**
 * 自定义设置Reader获取
 * @param inputStream Excel输入流
 * @param sheetIndex Sheet页 下标
 * @param alias 字段 头列 别名 映射集
 * @return
 */
private ExcelReader getReaderAfterCustomSetting(InputStream inputStream, Integer sheetIndex, Map<String, String> alias) {
    ExcelReader reader = ExcelUtil.getReader(inputStream, sheetIndex);
    reader.setHeaderAlias(alias);
    reader.setIgnoreEmptyRow(true);
    return reader;
}

主数据读取Map转PO的逻辑就是这样

因为不能直接转PO,有字段要翻译,所以PO封装必须依靠反射实现

注意我的空值逻辑Flag,还有Jdbc把值存储为其他类型,则无法直接赋值,所以最后还val.toString()这样再获取一下

private List<PartInfoPO> getMainDataListNew(List<Map<String, Object>> mainDataList) {
    List<PartInfoPO> partInfoPOList = new ArrayList<>();
    Class<PartInfoPO> partInfoPOClass = PartInfoPO.class;
    mainDataList.forEach(rowMap -> {
        PartInfoPO partInfoPO = new PartInfoPO();
        rowMap.keySet().forEach(colKey -> {
            try {
                if (StringUtils.isNullOrEmpty(colKey)) return;
                Object val = rowMap.get(colKey);
                Field declaredField = partInfoPOClass.getDeclaredField(colKey);
                ReflectionUtils.makeAccessible(declaredField);
                boolean isEmpty = null == val || "".equals(val);
                if (DATE_FIELDS.contains(colKey) && !isEmpty) {
                    val = DateUtil.parse(val.toString());
                    declaredField.set(partInfoPO, val);
                } else if ("optionDirect".equals(colKey) && !isEmpty) {
                    val =
                            "单向".equals(val) ? PART_DIRECT_1 :
                                    "双向".equals(val) ? PART_DIRECT_2 : val;
                    declaredField.set(partInfoPO, String.valueOf(val));
                } else if (DICT_1004_FIELDS.contains(colKey)) {
                    if ("是".equals(val)) val = YES;
                    else if ("否".equals(val)) val = NO;
                    declaredField.set(partInfoPO, val);
                } else if (!isEmpty) {
                    declaredField.set(partInfoPO, val.toString());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        partInfoPOList.add(partInfoPO);
    });
    return partInfoPOList;
}

专属价格加入了其他业务逻辑的需要:

private Set<PartOwnPricePO> getPartOwnPriceList(List<Map<String, Object>> partOwnPriceList) {
    Date currentDate = new Date();
    Set<PartOwnPricePO> list = new HashSet<>();
    Class<PartOwnPricePO> partOwnPricePOClass = PartOwnPricePO.class;
    partOwnPriceList.forEach(rowMap -> {
        PartOwnPricePO partOwnPricePO = new PartOwnPricePO();
        rowMap.keySet().forEach(colKey -> {
            try {
                Object val = rowMap.get(colKey);
                // 暂时不管Flag和空格
                if (StringUtils.isNullOrEmpty(colKey)) return;
                Field declaredField = partOwnPricePOClass.getDeclaredField(colKey);
                ReflectionUtils.makeAccessible(declaredField);
                if (DATE_FIELDS.contains(colKey)) {
                    val = DateUtil.parse(val.toString());
                    declaredField.set(partOwnPricePO, val);
                }
                else if (IS_DELETE.equals(colKey) && !StringUtils.isNullOrEmpty(val) && "Y".equals(val)) {
                    /**
                     * 设置是否删除标记为是
                     * d.当存在时,将所有对应数据的删除标记更新为“是”,并且如果对应的结束时间在导入时间之前则对于时间不进行更新。如果在导入时间之后,则更新对应结束时间。
                     */
                    declaredField.set(partOwnPricePO, YES);
                    int i = currentDate.compareTo(partOwnPricePO.getEndAt());// -1 currentDate < getEndAt
                    if (i > 0) {
                        partOwnPricePO.setEndAt(null); // 不更新结束时间
                    }
                }
                else {
                    declaredField.set(partOwnPricePO, val.toString());
                }
            } catch (Exception exception) {
                exception.printStackTrace();
            }
        });
        /**
         * 不设置默认经销商
         * partOwnPricePO.setOwnerCode("-1");
         */
        list.add(partOwnPricePO);
    });
    return list;
}

 

然后再是导入逻辑:

导入是允许重复插入的,类似下发操作

List<PartInfoPO> mainDataListNew = getMainDataListNew(mainDataList);
/**
 * a.在配件主档导入时,判断配件代码是否存在。不存在整行新增;存在时,对应配件代码进行更新
 */
// 获取所有配件代码
List<String> allPartNo = partInfoMapper.selectList(new QueryWrapper<PartInfoPO>().select(PART_NO1)).stream().map(PartInfoPO::getPartNo).collect(Collectors.toList());
// 有则更新,无则插入
mainDataListNew.forEach(partInfoPO -> {
    String partNo = partInfoPO.getPartNo();
    if (allPartNo.contains(partNo) ) {
        partInfoMapper.update(partInfoPO, new QueryWrapper<PartInfoPO>().eq(PART_NO1, partNo));
    } else {
        // 新增数据 没有经销商代码,则设置默认-1
        partInfoPO.setOwnerCode("-1");
        partInfoMapper.insert(partInfoPO);
    }
});

 

配件专属价格还有一个时间范围的要求:

    <select id="bizValidationSql" resultType="java.lang.Long">
        SELECT
            COUNT(*)
        FROM
            tm_part_own_price
        WHERE
            ( #{params.beginAt} BETWEEN BEGIN_AT AND END_AT OR #{params.endAt} BETWEEN BEGIN_AT AND END_AT)
            AND OWNER_CODE = #{params.ownerCode}
            AND PART_NO = #{params.partNo}
    </select>

就是插入DB的时候需要判断他的时间范围是不是交叉了

例如记录1 是 1 - 3月这个时间段,后面的记录不能和记录1重合,开始时间要么小于1月要么大于3月

结束时间也是如此,不能踩到1 - 3月这个范围

这个SQL就用来查询是否存在这个情况,如果存在则这个数据是交叉的,不允许插入

/**
 * b.导入专属价格信息,判断【配件代码+经销商代码+开始时间+结束时间】是否存在,存在时进行更新。不存在时新增数据
 * c.导入专属价格信息时,同一批次不能存在【配件代码+经销商代码+开始时间+结束时间】相同数据且【配件代码+经销商代码】相同是开始时间及结束时间不能与存在交叉情况。如以上两种情况出现则导入失败,并提示对应错误信息
 * d.在导入专属价格时,当删除标记被选中时,配件代码及经销商代码为必填。其余字段不校验必填。当导入时需要校验配件代码及经销商代码是否存在:当存在时,将所有对应数据的删除标记更新为“是”,并且如果对应的结束时间在导入时间之前则对于时间不进行
 */
Set<PartOwnPricePO> priceList = getPartOwnPriceList(ownPriceList);
List<PartOwnPricePO> errorPartNoList = new ArrayList<>();
/**
 * 逻辑校验与操作
 */
priceList.forEach(partOwnPricePO -> {
    QueryWrapper<PartOwnPricePO> eq = new QueryWrapper<PartOwnPricePO>()
            .eq("OWNER_CODE", partOwnPricePO.getOwnerCode())
            .eq(PART_NO1, partOwnPricePO.getPartNo())
            .eq("BEGIN_AT", partOwnPricePO.getBeginAt())
            .or().eq("END_AT", partOwnPricePO.getEndAt());
    List<PartOwnPricePO> sameList = partOwnPriceMapper.selectList(eq);
    if (!sameList.isEmpty()) {
        PartOwnPricePO poFromDB = sameList.get(0);
        // 目的是更新价格 与结束时间调整
        poFromDB.setOwnPrice(partOwnPricePO.getOwnPrice());
        poFromDB.setEndAt(partOwnPricePO.getEndAt());
        partOwnPriceMapper.update(poFromDB, eq);
        return;
    }
    // 是否交叉
    else if (partOwnPriceMapper.bizValidationSql(partOwnPricePO) > 0) {
        errorPartNoList.add(partOwnPricePO);
        return;
    }
    partOwnPriceMapper.insert(partOwnPricePO);
});
result.put("errorList", errorPartNoList);

 

2021年12月6日 15点34分 更新

SonarLint代码检查之后不允许使用反射API操作,代码进行了重构

 

主业务代码压缩的很少了

/**
     * LoginInfoDto loginInfo = ApplicationContextHelper.getBeanByType(LoginInfoDto.class);
     * @param importFile
     * @return
     */
    @Override
    public Map<String, Object> importUpload(MultipartFile importFile) {
        Map<String, Object> result = new HashMap<>();
        try {
            List<Map<String, Object>> mainDataList = readAllByCustomSetting(importFile.getInputStream(), 0, MAIN_DATA_ALIAS);
            List<Map<String, Object>> ownPriceList = readAllByCustomSetting(importFile.getInputStream(), 1, OWN_PRICE_ALIAS);

            // 校验
            importDataValidation1(mainDataList, result);
            if (!StringUtils.isNullOrEmpty(result.get(ERROR))) {
                return result;
            }
            importDataValidation2(ownPriceList, result);
            if (!StringUtils.isNullOrEmpty(result.get(ERROR))) {
                return result;
            }

            // 翻译,过滤
            mainDataList = getMainDataList2(mainDataList);
            ownPriceList = getOwnPriceList(ownPriceList);

            // 导入
            mainDataImport2(mainDataList);
            ownPriceImport2(ownPriceList);
            replaceImport(mainDataList);
        } catch (Exception exception) {
            logger.error(exception.getMessage());
        }
        result.put(SUCCESS, "导入成功");
        return result;
    }

就分为总体三步走:

校验 - 过滤翻译处理 - 导入

 

1、校验部分:

写在一起被SonarLint警告方法复杂程度超过了15,要求拆解

然后单元格的判断场景过多且复杂,这里就使用组合条件进行判断

    /**
     * 校验配件主数据
     * @param mainDataList
     * @param result
     */
    private void importDataValidation1(List<Map<String, Object>> mainDataList, Map<String, Object> result) {
        if (mainDataList.isEmpty()) {
            result.put(ERROR, "配件主数据Sheet页为空!!!");
            return;
        }
        for (Map<String, Object> rowMap : mainDataList) {
            for (Map.Entry<String, Object> entry : rowMap.entrySet()) {
                String key = entry.getKey();
                if ("".equals(key)) continue;
                String excelColTitle = MAIN_DATA_ALIAS.getKey(key);
                Object val = entry.getValue();

                boolean nullVal = StringUtils.isNullOrEmpty(val); // 空值判断
                boolean requiredCol = excelColTitle.contains("*"); // 必填列判断

                boolean flag = extractedMainDataValid(result, rowMap, key, excelColTitle, val, nullVal, requiredCol);
                if (flag) return;
            }
        }
    }

    private boolean extractedMainDataValid(Map<String, Object> result, Map<String, Object> rowMap, String key, String excelColTitle, Object val, boolean nullVal, boolean requiredCol) {
        // 一般空值校验
        if (nullVal && requiredCol) {
            result.put(ERROR, COMMON_TIP_PREFIX + excelColTitle + "】列存在必填项空值,请检查后重新上传!");
            return true;
        } // 替换件和替换方式为非必填项,但是填写任一项,皆为必填
        else if (
                (OPTION_PART_NO.equals(key) && !nullVal) && StringUtils.isNullOrEmpty(rowMap.get(OPTION_DIRECT)) // 当替换件不为空 且 替换方向是空值时
                        ||   (OPTION_DIRECT.equals(key) && !nullVal) && StringUtils.isNullOrEmpty(rowMap.get(OPTION_PART_NO)) // 或者替换方式不为空 且 替换件是空值时
        ) {
            result.put(ERROR, "配件主数据Sheet页,替换件和替换方式需要一起填写,请重新检查!");
            return true;
        }
        else if (OPTION_DIRECT.equals(key) && !nullVal && !DIRECT_FIELDS.contains(val.toString())) {
            result.put(ERROR, "配件主数据Sheet页,【替换方式】存在错误的状态值[单向,双向],请检查后重新上传!");
            return true;
        } // 数值校验
        else if (NUMBER_FIELDS.contains(key) && !nullVal && !val.toString().matches(NUMBER_REGEXP)) {
            result.put(ERROR, COMMON_TIP_PREFIX + excelColTitle + "】列存在错误的数值,请检查后重新上传!");
            return true;
        } // 是否字段校验处理
        else if (DICT_1004_FIELDS.contains(key) && !nullVal && !YES_NO_FIELDS.contains(val.toString()) ) {
            result.put(ERROR, COMMON_TIP_PREFIX + excelColTitle + "】列存在错误的状态值[是,否],请检查后重新上传!");
            return true;
        } // 日期校验
        else if (DISABLE_DATE.equals(key) && !nullVal && !(val instanceof DateTime)) {
            result.put(ERROR, "配件主数据Sheet页【停用日期】列存在错误的日期值,请检查后重新上传!");
            return true;
        }
        return false;
    }

 

2、翻译处理部分:

使用Stream的API处理,需要更改元素内容,

删除空Key是因为这个Hutool版本的Excel解析导入有点BUG,会读空串头

    /**
     * 封装可导入的数据
     * @param mainDataList
     * @return
     */
    private List<Map<String, Object>> getMainDataList2(List<Map<String, Object>> mainDataList) {
        return mainDataList.stream().map(row -> { // map() peek()
            row.remove("");
            // 翻译替换件方向
            row.computeIfPresent(OPTION_DIRECT, (key, value) -> DIRECT_FIELDS.get(0).equals(value) ?  PART_DIRECT_1 : PART_DIRECT_2);
            // 翻译日期
            row.computeIfPresent(DISABLE_DATE, (key, value) -> DateUtil.parse(value.toString()));
            // 翻译是否
            row.keySet().forEach(s -> {
                if (DICT_1004_FIELDS.contains(s)) row.computeIfPresent(s, (key, value) -> YES_NO_FIELDS.get(0).equals(value) ? YES : NO);
            });
            // 停用缺省设置
            if (StringUtils.isNullOrEmpty(row.get(IS_DISABLE))) row.put(IS_DISABLE, NO);
            // 删除空Val的Key
            row.entrySet().removeIf(next -> StringUtils.isNullOrEmpty(next.getValue()));
            /**
             * // 缺省参数设置:供应商代码 + 供应商名称
             * if (StringUtils.isNullOrEmpty(row.get("providerCode")))  row.put("providerCode", "000000000000");
             * if (StringUtils.isNullOrEmpty(row.get("providerName")))  row.put("providerCode", "主机厂");
             */
            return row;
        }).collect(Collectors.toList());
    }

 

3、导入部分:

导入操作是 插入 + 更新一起处理,这个在程序层处理有点吃力

改用SQL去处理了:

    /**
     * 导入主数据
     * @param mainDataList
     */
    private void mainDataImport2(List<Map<String, Object>> mainDataList) {
        mainDataList.forEach(currentRecord -> partInfoMapper.insertAndUpdateByMap(currentRecord));
    }

XmlMapper写好的SQL:

因为一些字段不是导入的必填项,需要动态判断处理

    <insert id="insertAndUpdateByMap" parameterType="map">
        INSERT INTO `tm_part_info` (
        PART_NO,
        OWNER_CODE,
        PART_NAME,
        APPLICABLE_VEHICLE_TYPE,
        MODEL,
        PLAN_PRICE_TAXED,
        PLAN_PRICE_NO_TAXED,
        CLAIM_PRICE,
        LIMIT_PRICE,
        SUGGEST_RETAIL_PRICE_TAXED,
        SUGGEST_RETAIL_PRICE_NO_TAXED,
        IS_TRIPLE_GUARANTEE,
        IS_CLAIM,
        PART_STATUS,
        IS_UNSAFE,
        IS_BACK,
        IS_DD,
        IS_MONITOR,
        IS_C_SALE,
        IS_C_PURCHASE,
        IS_BATTERY,
        <if test="partNameEn != null"> PART_NAME_EN, </if>
        <if test="unitCode != null"> UNIT_CODE, </if>
        <if test="minPackage != null"> MIN_PACKAGE, </if>
        <if test="quantityPerCar != null"> QUANTITY_PER_CAR, </if>
        <if test="optionPartNo != null"> OPTION_PART_NO, </if>
        <if test="optionDirect != null"> OPTION_DIRECT, </if>
        <if test="remark != null"> REMARK, </if>
        <if test="disableDate != null"> DISABLE_DATE, </if>
        IS_DISABLE
        ) VALUES (
        #{partNo},
        '-1',
        #{partName},
        #{applicableVehicleType},
        #{model},
        #{planPriceTaxed},
        #{planPriceNoTaxed},
        #{claimPrice},
        #{limitPrice},
        #{suggestRetailPriceTaxed},
        #{suggestRetailPriceNoTaxed},
        #{isTripleGuarantee},
        #{isClaim},
        #{partStatus},
        #{isUnsafe},
        #{isBack},
        #{isDd},
        #{isMonitor},
        #{isCSale},
        #{isCPurchase},
        #{isBattery},
        <if test="partNameEn != null"> #{partNameEn}, </if>
        <if test="unitCode != null"> #{unitCode}, </if>
        <if test="minPackage != null"> #{minPackage}, </if>
        <if test="quantityPerCar != null"> #{quantityPerCar}, </if>
        <if test="optionPartNo != null"> #{optionPartNo}, </if>
        <if test="optionDirect != null"> #{optionDirect}, </if>
        <if test="remark != null"> #{remark}, </if>
        <if test="disableDate != null"> #{disableDate}, </if>
        #{isDisable}
        )
        ON DUPLICATE KEY UPDATE
        PART_NAME = #{partName},
        APPLICABLE_VEHICLE_TYPE = #{applicableVehicleType},
        MODEL = #{model},
        PLAN_PRICE_TAXED = #{planPriceTaxed},
        PLAN_PRICE_NO_TAXED = #{planPriceNoTaxed},
        CLAIM_PRICE = #{claimPrice},
        LIMIT_PRICE = #{limitPrice},
        SUGGEST_RETAIL_PRICE_TAXED = #{suggestRetailPriceTaxed},
        SUGGEST_RETAIL_PRICE_NO_TAXED = #{suggestRetailPriceNoTaxed},
        IS_TRIPLE_GUARANTEE = #{isTripleGuarantee},
        IS_CLAIM = #{isClaim},
        PART_STATUS = #{partStatus},
        IS_UNSAFE = #{isUnsafe},
        IS_BACK = #{isBack},
        IS_DD = #{isDd},
        IS_MONITOR = #{isMonitor},
        IS_C_SALE = #{isCSale},
        IS_C_PURCHASE = #{isCPurchase},
        IS_BATTERY = #{isBattery},
        <if test="partNameEn != null"> PART_NAME_EN = #{partNameEn}, </if>
        <if test="unitCode != null"> UNIT_CODE = #{unitCode}, </if>
        <if test="minPackage != null"> MIN_PACKAGE = #{minPackage}, </if>
        <if test="quantityPerCar != null"> QUANTITY_PER_CAR = #{quantityPerCar}, </if>
        <if test="optionPartNo != null"> OPTION_PART_NO = #{optionPartNo}, </if>
        <if test="optionDirect != null"> OPTION_DIRECT = #{optionDirect}, </if>
        <if test="remark != null"> REMARK = #{remark}, </if>
        <if test="disableDate != null"> DISABLE_DATE = #{disableDate}, </if>
        IS_DISABLE = #{isDisable},
        DISTRIBUTION_STATUS = 0
    </insert>

 

posted @ 2021-11-03 19:45  emdzz  阅读(348)  评论(0编辑  收藏  举报