EasyExcel多行表头带动态下拉框导入导出具体实现

一、准备环境包



maven:

<!-- guava本地缓存-->
<dependency>
	<groupId>com.google.guava</groupId>
	<artifactId>guava</artifactId>
	<version>32.1.2-jre</version>
</dependency>


<!--easyexcel依赖-->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>easyexcel</artifactId>
	<version>2.2.10</version>
</dependency>

二、多行表头的excel导出 带有设置下拉框


设计代码:

import java.lang.annotation.*;

/**
 * 标注导出的列为下拉框类型,并为下拉框设置内容
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelSelected {
    /**
     * 固定下拉内容
     */
    String[] source() default {};

    /**
     * 动态下拉内容
     */
    Class<? extends ExcelDynamicSelect>[] sourceClass() default {};

    /**
     * 设置下拉框的起始行,默认为第二行
     */
    int firstRow() default 1;

    /**
     * 设置下拉框的结束行,默认为最后一行
     */
    int lastRow() default 0x10000;
}
public interface ExcelDynamicSelect {
    /**
     * 获取动态生成的下拉框可选数据
     * @return 动态生成的下拉框可选数据
     */
    String[] getSource();
}
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;

@Configuration
public class CacheConfig {

    @Bean
    public Cache<String, Object> myCache() {
        return CacheBuilder.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .maximumSize(100)
                .build();
    }
}

import cn.hutool.extra.spring.SpringUtil;
import com.google.common.cache.Cache;
import com.youqian.common.rest.RestResponse;
import com.youqian.pms.api.feign.warehouse.client.WarehouseClient;
import com.youqian.pms.api.feign.warehouse.dto.DropdownQuery;
import com.youqian.pms.api.feign.warehouse.dto.SupplierBasicInfo;
import com.youqian.pms.boot.utils.easyexcelutil.ExcelDynamicSelect;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;

/**
 * 获取所有供应商名称列表
 */
@Slf4j
@Component
public class SupplierNameServiceImpl implements ExcelDynamicSelect {


    private static final WarehouseClient warehouseClient;
    private static final Cache<String, Object> myCache;

    static {
        warehouseClient = SpringUtil.getBean(WarehouseClient.class);
        myCache = SpringUtil.getBean(Cache.class);
    }

    @PostConstruct
    private void init() {
        myCache.put("all-supplier-name", this.getDataList());
    }

    private String getDataList() {
        DropdownQuery query = new DropdownQuery();
        RestResponse<List<SupplierBasicInfo>> listRestResponse = warehouseClient.queryAllSuppliers(query);
        StringBuilder stringBuilder = new StringBuilder();
        if (listRestResponse.isSuccess()) {
            List<SupplierBasicInfo> data = listRestResponse.getData();
            for (SupplierBasicInfo datum : data) {
                String supplierName = datum.getSupplierName();
                if (StringUtils.isNotBlank(supplierName)) {
                    stringBuilder.append(supplierName).append(",");
                }
            }
        }
        log.info("guava cache queryAllSuppliers");
        return stringBuilder.toString();
    }

    @Override
    public String[] getSource() {
        String value = "";
        try {
            value = String.valueOf(myCache.get("all-supplier-name", this::getDataList));
        } catch (Exception e) {
            log.warn("guava cache queryAllSuppliers warn :", e);
        }
        return value.split(",");
    }

}


import cn.hutool.extra.spring.SpringUtil;
import com.google.common.cache.Cache;
import com.youqian.common.rest.RestResponse;
import com.youqian.pms.api.feign.warehouse.client.WarehouseClient;
import com.youqian.pms.api.feign.warehouse.dto.WarehouseManagementListReqDto;
import com.youqian.pms.api.feign.warehouse.dto.WarehouseManagementListRespDto;
import com.youqian.pms.boot.utils.easyexcelutil.ExcelDynamicSelect;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;

@Slf4j
@Component
public class WarehouseNameServiceImpl implements ExcelDynamicSelect {


    private static final WarehouseClient warehouseClient;
    private static final Cache<String, Object> myCache;

    static {
        warehouseClient = SpringUtil.getBean(WarehouseClient.class);
        myCache = SpringUtil.getBean(Cache.class);
    }

    @PostConstruct
    private void init() {
        myCache.put("all-warehouse-name", this.getDataList());
    }

    private String getDataList() {
        WarehouseManagementListReqDto query = new WarehouseManagementListReqDto();
        RestResponse<List<WarehouseManagementListRespDto>> listRestResponse = warehouseClient.findList(query);
        StringBuilder stringBuilder = new StringBuilder();
        if (listRestResponse.isSuccess()) {
            List<WarehouseManagementListRespDto> data = listRestResponse.getData();
            for (WarehouseManagementListRespDto datum : data) {
                String warehouseName = datum.getWarehouseName();
                if (StringUtils.isNotBlank(warehouseName)) {
                    stringBuilder.append(warehouseName).append(",");
                }
            }
        }
        log.info("guava cache warehouseName");
        return stringBuilder.toString();
    }

    @Override
    public String[] getSource() {
        String value = "";
        try {
            value = String.valueOf(myCache.get("all-warehouse-name", this::getDataList));
        } catch (Exception e) {
            log.warn("guava cache warehouseName warn :", e);
        }
        return value.split(",");
    }

}
import com.youqian.pms.boot.utils.easyexcelutil.ExcelDynamicSelect;
import com.youqian.pms.common.enums.buyorder.BuyTypeEnum;
import lombok.extern.slf4j.Slf4j;

/**
 * 获取采购类型列表
 */
@Slf4j
public class BuyTypeStrServiceImpl implements ExcelDynamicSelect {


    @Override
    public String[] getSource() {
        StringBuilder value = new StringBuilder();
        for (BuyTypeEnum typeEnum : BuyTypeEnum.values()) {
            value.append(typeEnum.getDesc()).append(",");
        }
        return value.toString().split(",");
    }

}

/**
 * 采购订单模板导出
 */
@ApiOperation("采购订单导入模板导出")
@PostMapping("/buyOrder/exportPurchasingOrderTemplate")
public void exportPurchasingOrderTemplate(HttpServletResponse response) {
	log.info("exportPurchasingOrderTemplate start");
	this.buyOrderOperateService.exportPurchasingOrderTemplate(response);
}
导出模板对象:多个表头对应多个对象

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.youqian.pms.boot.utils.easyexcelutil.ExcelSelected;
import lombok.Data;

import java.io.Serializable;

/**
 * 采购订单基本信息
 */
@Data
@HeadRowHeight(20)
@ContentRowHeight(18)
public class PurchaseOrderBaseInfoResp implements Serializable {

    private static final long serialVersionUID = -2121379439910317195L;

    /**
     * 供应商名称
     */
    @ExcelSelected(sourceClass = SupplierNameServiceImpl.class)
    @ExcelProperty(index = 0,value = "供应商名称")
    @ColumnWidth(30)
    private String supplierName;
    /**
     * 仓库名称
     */
    @ExcelSelected(sourceClass = WarehouseNameServiceImpl.class)
    @ExcelProperty(index = 1,value = "仓库名称")
    @ColumnWidth(30)
    private String warehouseName;
    /**
     * 采购类型
     */
    @ExcelSelected(sourceClass = BuyTypeStrServiceImpl.class)
    @ExcelProperty(index = 2,value = "采购类型")
    @ColumnWidth(30)
    private String buyTypeStr;
    /**
     * 货主
     */
    @ExcelSelected(sourceClass = BuyerNameServiceImpl.class)
    @ExcelProperty(index = 3,value = "货主")
    @ColumnWidth(30)
    private String buyerName;
}


import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;

/**
 * 采购订单商品信息
 */
@Data
@HeadRowHeight(20)
@ContentRowHeight(18)
public class PurchaseOrderGoodsResp {

    /**
     * sku编码
     */
    @ExcelProperty(index = 0,value = "SKU 编码")
    @ColumnWidth(30)
    private String skuCode;
    /**
     * 数量
     */
    @ExcelProperty(index = 1,value = "数量")
    @ColumnWidth(30)
    private String count;
    /**
     * 单价
     */
    @ExcelProperty(index = 2,value = "单价")
    @ColumnWidth(30)
    private String price;
}

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.metadata.WriteSheet;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Slf4j
public class EasyExcelUtil {

    /**
     * 创建即将导出的sheet页(sheet页中含有带下拉框的列)
     * @param head 导出的表头信息和配置
     * @param sheetNo sheet索引
     * @param sheetName sheet名称
     * @param <T> 泛型
     * @return sheet页
     */
    public static <T> WriteSheet writeSelectedSheet(Class<T> head, Integer sheetNo, String sheetName) {
        Map<Integer, ExcelSelectedResolve> selectedMap = resolveSelectedAnnotation(head);

        return EasyExcel.writerSheet(sheetNo, sheetName)
                .head(head)
                .registerWriteHandler(new SelectedSheetWriteHandler(selectedMap))
                .build();
    }

    /**
     * 解析表头类中的下拉注解
     * @param head 表头类
     * @param <T> 泛型
     * @return Map<下拉框列索引, 下拉框内容> map
     */
    private static <T> Map<Integer, ExcelSelectedResolve> resolveSelectedAnnotation(Class<T> head) {
        Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>();

        // getDeclaredFields(): 返回全部声明的属性;getFields(): 返回public类型的属性
        Field[] fields = head.getDeclaredFields();
        for (int i = 0; i < fields.length; i++){
            Field field = fields[i];
            // 解析注解信息
            ExcelSelected selected = field.getAnnotation(ExcelSelected.class);
            ExcelProperty property = field.getAnnotation(ExcelProperty.class);
            if (selected != null) {
                ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();
                String[] source = excelSelectedResolve.resolveSelectedSource(selected);
                if (source != null && source.length > 0){
                    excelSelectedResolve.setSource(source);
                    excelSelectedResolve.setFirstRow(selected.firstRow());
                    excelSelectedResolve.setLastRow(selected.lastRow());
                    if (property != null && property.index() >= 0){
                        selectedMap.put(property.index(), excelSelectedResolve);
                    } else {
                        selectedMap.put(i, excelSelectedResolve);
                    }
                }
            }
        }
        return selectedMap;
    }

    public static boolean isIDNumber(String IDNumber) {
        if (IDNumber == null || "".equals(IDNumber)) {
            return false;
        }
        // 定义判别用户身份证号的正则表达式(15位或者18位,最后一位可以为字母)
        String regularExpression = "(^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|" +
                "(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)";
        //假设18位身份证号码:41000119910101123X  410001 19910101 123X
        //^开头
        //[1-9] 第一位1-9中的一个      4
        //\\d{5} 五位数字           10001(前六位省市县地区)
        //(18|19|20)                19(现阶段可能取值范围18xx-20xx年)
        //\\d{2}                    91(年份)
        //((0[1-9])|(10|11|12))     01(月份)
        //(([0-2][1-9])|10|20|30|31)01(日期)
        //\\d{3} 三位数字            123(第十七位奇数代表男,偶数代表女)
        //[0-9Xx] 0123456789Xx其中的一个 X(第十八位为校验值)
        //$结尾

        //假设15位身份证号码:410001910101123  410001 910101 123
        //^开头
        //[1-9] 第一位1-9中的一个      4
        //\\d{5} 五位数字           10001(前六位省市县地区)
        //\\d{2}                    91(年份)
        //((0[1-9])|(10|11|12))     01(月份)
        //(([0-2][1-9])|10|20|30|31)01(日期)
        //\\d{3} 三位数字            123(第十五位奇数代表男,偶数代表女),15位身份证不含X
        //$结尾
        boolean matches = IDNumber.matches(regularExpression);

        //判断第18位校验值
        if (matches) {

            if (IDNumber.length() == 18) {
                try {
                    char[] charArray = IDNumber.toCharArray();
                    //前十七位加权因子
                    int[] idCardWi = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
                    //这是除以11后,可能产生的11位余数对应的验证码
                    String[] idCardY = {"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"};
                    int sum = 0;
                    for (int i = 0; i < idCardWi.length; i++) {
                        int current = Integer.parseInt(String.valueOf(charArray[i]));
                        int count = current * idCardWi[i];
                        sum += count;
                    }
                    char idCardLast = charArray[17];
                    int idCardMod = sum % 11;
                    if (idCardY[idCardMod].toUpperCase().equals(String.valueOf(idCardLast).toUpperCase())) {
                        return true;
                    } else {
                        return false;
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    return false;
                }
            }
            return false;
        }
        return matches;
    }


    public static  boolean isMobile(String phone){
        Pattern p = null;
        Matcher m = null;
        boolean b = false;
        // 验证手机号
        String s2="^[1](([3|5|6|7|8|9][\\d])|([4][4,5,6,7,8,9])|([6][2,5,6,7])|([7][^9])|([9][1,8,9]))[\\d]{8}$";
        if(StringUtils.isNotBlank(phone)){
            p = Pattern.compile(s2);
            m = p.matcher(phone);
            b = m.matches();
        }
        return b;
    }

}

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Data
@Slf4j
public class ExcelSelectedResolve {
    /**
     * 下拉内容
     */
    private String[] source;

    /**
     * 设置下拉框的起始行,默认为第二行
     */
    private int firstRow;

    /**
     * 设置下拉框的结束行,默认为最后一行
     */
    private int lastRow;

    public String[] resolveSelectedSource(ExcelSelected excelSelected) {
        if (excelSelected == null) {
            return null;
        }

        // 获取固定下拉框的内容
        String[] source = excelSelected.source();
        if (source.length > 0) {
            return source;
        }

        // 获取动态下拉框的内容
        Class<? extends ExcelDynamicSelect>[] classes = excelSelected.sourceClass();
        if (classes.length > 0) {
            try {
                ExcelDynamicSelect excelDynamicSelect = classes[0].newInstance();
                String[] dynamicSelectSource = excelDynamicSelect.getSource();
                if (dynamicSelectSource != null && dynamicSelectSource.length > 0) {
                    return dynamicSelectSource;
                }
            } catch (InstantiationException | IllegalAccessException e) {
                log.error("解析动态下拉框数据异常", e);
            }
        }
        return null;
    }

}

import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;

import java.util.Map;

@Data
@AllArgsConstructor
public class SelectedSheetWriteHandler implements SheetWriteHandler {

    private final Map<Integer, ExcelSelectedResolve> selectedMap;

    /**
     * 设置阈值,避免生成的导入模板下拉值获取不到,可自行设置数量大小
     */
    private static final Integer LIMIT_NUMBER = 25;

    /**
     * Called before create the sheet
     */
    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    /**
     * Called after the sheet is created
     */
    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        // 这里可以对cell进行任何操作
        Sheet sheet = writeSheetHolder.getSheet();
        DataValidationHelper helper = sheet.getDataValidationHelper();
        selectedMap.forEach((k, v) -> {
            // 设置下拉列表的行: 首行,末行,首列,末列
//            CellRangeAddressList rangeList = new CellRangeAddressList(v.getFirstRow(), v.getLastRow(), k, k);
            CellRangeAddressList rangeList = new CellRangeAddressList(v.getFirstRow(), 5, k, k);
            // 如果下拉值总数大于25,则使用一个新sheet存储,避免生成的导入模板下拉值获取不到
            if (v.getSource().length > LIMIT_NUMBER) {
                //定义sheet的名称
                //1.创建一个隐藏的sheet 名称为 hidden + k
                String sheetName = "hidden" + k;
                Workbook workbook = writeWorkbookHolder.getWorkbook();
                Sheet hiddenSheet = workbook.createSheet(sheetName);
                for (int i = 0, length = v.getSource().length; i < length; i++) {
                    // 开始的行数i,列数k
                    hiddenSheet.createRow(i).createCell(k).setCellValue(v.getSource()[i]);
                }
                Name category1Name = workbook.createName();
                category1Name.setNameName(sheetName);
                String excelLine = getExcelLine(k);
                // =hidden!$H:$1:$H$50  sheet为hidden的 H1列开始H50行数据获取下拉数组
                String refers = "=" + sheetName + "!$" + excelLine + "$1:$" + excelLine + "$" + (v.getSource().length + 1);
                // 将刚才设置的sheet引用到你的下拉列表中
                DataValidationConstraint constraint = helper.createFormulaListConstraint(refers);
                DataValidation dataValidation = helper.createValidation(constraint, rangeList);
                writeSheetHolder.getSheet().addValidationData(dataValidation);
                // 设置存储下拉列值得sheet为隐藏
                int hiddenIndex = workbook.getSheetIndex(sheetName);
                if (!workbook.isSheetHidden(hiddenIndex)) {
                    workbook.setSheetHidden(hiddenIndex, true);
                }
            }
            // 设置下拉列表的值
            DataValidationConstraint constraint = helper.createExplicitListConstraint(v.getSource());
            // 设置约束
            DataValidation validation = helper.createValidation(constraint, rangeList);
            // 阻止输入非下拉选项的值
            validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
            validation.setShowErrorBox(true);
//			validation.setSuppressDropDownArrow(true);
            validation.createErrorBox("提示", "请输入下拉选项中的内容");
            sheet.addValidationData(validation);
        });
    }


    /**
     * 返回excel列标A-Z-AA-ZZ
     *
     * @param num 列数
     * @return java.lang.String
     */
    private String getExcelLine(int num) {
        String line = "";
        int first = num / 26;
        int second = num % 26;
        if (first > 0) {
            line = (char) ('A' + first - 1) + "";
        }
        line += (char) ('A' + second) + "";
        return line;
    }
}
@Override
public void exportPurchasingOrderTemplate(HttpServletResponse response) {
	String filename = "采购订单导入模板";
	try {
		// 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要
		filename = URLEncoder.encode(filename, "UTF-8");
		response.setContentType("application/octet-stream;charset=ISO8859-1");
		response.setHeader("Content-Disposition", "attachment;filename=" + filename);
		response.addHeader("Pragma", "no-cache");
		response.addHeader("Cache-Control", "no-cache");


		// 设置隔行表头
		List<List<String>> headList = Lists.newArrayList();
		headList.add(Lists.newArrayList());
		headList.add(Lists.newArrayList());
		headList.add(Lists.newArrayList());
		headList.add(Lists.newArrayList());
		headList.add(Lists.newArrayList());
		ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
		WriteSheet writeSheet = EasyExcelUtil.writeSelectedSheet(PurchaseOrderBaseInfoResp.class, 0, filename);
		excelWriter.write(headList, writeSheet);

		WriteTable writeTable2 = EasyExcel.writerTable(2).needHead(Boolean.TRUE).head(PurchaseOrderGoodsResp.class).build();
		excelWriter.write(new ArrayList<String>(), writeSheet, writeTable2);
		excelWriter.finish();
	} catch (Exception e) {
		log.error("导出【采购订单导入模板】 error :",e);
	}
}

三、多行表头的excel导入

 /**
 * 采购商品批量导入
 * @return 采购商品列表
 */
@ApiOperation("采购订单导入")
@PostMapping("/buyOrder/importPurchasingOrder")
public ResponseResult<AddOrModifyBuyOrderDto> importPurchasingOrder(MultipartFile file) {
	log.info("importPurchasingOrder start");
	try {
		return ResponseResult.buildSuccessResponse(this.buyOrderOperateService.importPurchasingOrder(file));
	} catch (Exception e) {
		log.info("采购订单导入失败", e);
		return ResponseResult.build(-1, e.getMessage(),null);
	}
}
@Override
public AddOrModifyBuyOrderDto importPurchasingOrder(MultipartFile file) {
	try {
		// 读取excel内容
		List<PurchaseOrderBaseInfoResp> baseInfoRespList = new ArrayList<>();
		List<PurchaseOrderGoodsResp> goodsRespList = new ArrayList<>();
		PurchaseOrderBaseInfoRespListener purchaseOrderBaseInfoRespListener = new PurchaseOrderBaseInfoRespListener(baseInfoRespList);
		PurchaseOrderGoodsRespListener purchaseOrderGoodsRespListener = new PurchaseOrderGoodsRespListener(goodsRespList);
		EasyExcel.read(file.getInputStream(), PurchaseOrderBaseInfoResp.class, purchaseOrderBaseInfoRespListener).head(PurchaseOrderBaseInfoResp.class).sheet().doRead();
		EasyExcel.read(file.getInputStream(), PurchaseOrderGoodsResp.class, purchaseOrderGoodsRespListener).headRowNumber(7).sheet().doRead();
		// 组装数据
		return this.assemblyObject(baseInfoRespList, goodsRespList);
	} catch (Exception e){
		log.error("导出【采购订单导入模板】 error :",e);
		throw new Exception(e.getMessage());
	}
}

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.fastjson.JSON;
import com.youqian.pms.boot.controller.internal.buyorder.dto.PurchaseOrderBaseInfoResp;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;

@Getter
@Slf4j
public class PurchaseOrderBaseInfoRespListener implements ReadListener<PurchaseOrderBaseInfoResp> {

    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 缓存的数据
     */
    private final List<PurchaseOrderBaseInfoResp> cachedDataList;

    public PurchaseOrderBaseInfoRespListener(List<PurchaseOrderBaseInfoResp> cachedDataList) {
        this.cachedDataList = cachedDataList;
    }

    @Override
    public void onException(Exception e, AnalysisContext analysisContext) {
    }

    @Override
    public void invokeHead(Map<Integer, CellData> map, AnalysisContext analysisContext) {
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(PurchaseOrderBaseInfoResp data, AnalysisContext context) {
        Integer rowIndex = context.readRowHolder().getRowIndex();
        if (rowIndex < 6) {
            log.info("解析到一条数据:{}", JSON.toJSONString(data));
            cachedDataList.add(data);
        }
    }

    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.info("所有数据解析完成!");
    }

    @Override
    public boolean hasNext(AnalysisContext analysisContext) {
        Integer rowIndex = analysisContext.readRowHolder().getRowIndex();
        return rowIndex < 6;
    }


}



import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.fastjson.JSON;
import com.youqian.pms.boot.controller.internal.buyorder.dto.PurchaseOrderGoodsResp;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;

@Getter
@Slf4j
public class PurchaseOrderGoodsRespListener implements ReadListener<PurchaseOrderGoodsResp> {

    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 缓存的数据
     */
    private final List<PurchaseOrderGoodsResp> cachedDataList;


    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     */
    public PurchaseOrderGoodsRespListener(List<PurchaseOrderGoodsResp> cachedDataList) {
        this.cachedDataList = cachedDataList;
    }

    @Override
    public void onException(Exception e, AnalysisContext analysisContext) throws Exception {

    }

    @Override
    public void invokeHead(Map<Integer, CellData> map, AnalysisContext analysisContext) {
        System.out.println();
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(PurchaseOrderGoodsResp data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
    }

    @Override
    public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {

    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        log.info("所有数据解析完成!");
    }

    @Override
    public boolean hasNext(AnalysisContext analysisContext) {
        return true;
    }


}

private AddOrModifyBuyOrderDto assemblyObject(List<PurchaseOrderBaseInfoResp> baseInfoRespList, List<PurchaseOrderGoodsResp> goodsRespList) {
        // 校验数据
        if (CollectionUtils.isEmpty(baseInfoRespList) || CollectionUtils.isEmpty(goodsRespList)) {
            throw new Exception("导入的数据为空,请检查后重新操作");
        }
        PurchaseOrderBaseInfoResp baseInfoResp = baseInfoRespList.get(0);
        String buyerName = baseInfoResp.getBuyerName();
        if (StringUtils.isEmpty(buyerName)) {
            throw new Exception("[货主]不可为空");
        }
        String buyTypeStr = baseInfoResp.getBuyTypeStr();
        if (StringUtils.isEmpty(buyTypeStr)) {
            throw new Exception("[采购类型]不可为空");
        }
        String supplierName = baseInfoResp.getSupplierName();
        if (StringUtils.isEmpty(supplierName)) {
            throw new Exception("[供应商]不可为空");
        }
        String warehouseName = baseInfoResp.getWarehouseName();
        if (StringUtils.isEmpty(warehouseName)) {
            throw new Exception("[仓库]不可为空");
        }
        List<String> skuCodeList = new ArrayList<>();
        for (PurchaseOrderGoodsResp orderGoodsResp : goodsRespList) {
            String count = orderGoodsResp.getCount();
            if (StringUtils.isEmpty(count)) {
                throw new Exception("[SKU 数量]不可为空");
            }
            String price = orderGoodsResp.getPrice();
            if (StringUtils.isEmpty(price)) {
                throw new Exception("[SKU 单价]不可为空");
            }
            String skuCode = orderGoodsResp.getSkuCode();
            if (StringUtils.isEmpty(skuCode)) {
                throw new Exception("[SKU 编码]不可为空");
            }
            skuCodeList.add(skuCode);
        }
        Map<String, PurchaseOrderGoodsResp> skuCode2IdentityMap = goodsRespList.stream().collect(
                Collectors.toMap(PurchaseOrderGoodsResp::getSkuCode, Function.identity(), (oldOne, newOne) -> newOne)
        );
        AddOrModifyBuyOrderDto addDto = new AddOrModifyBuyOrderDto();
        CompletableFuture<Void> f1 = CompletableFuture.runAsync(()->{
            RestResponse<BuySupplierMainResp> buySupplierMainRespRestResponse = supplierBuyerUserService.querySupplierMainListByBuyerName(buyerName);
            BuySupplierMainResp data = buySupplierMainRespRestResponse.getData();
            if (buySupplierMainRespRestResponse.isSuccess() && null != data) {
                Long buyerId = data.getId();
                addDto.setBuyerId(buyerId);
            } else {
                throw new Exception("[货主]: "+buyerName+" 不存在,请检查后重新填写");
            }
            addDto.setBuyerName(buyerName);
            int buyType = BuyTypeEnum.getCodeByDesc(buyTypeStr);
            if (buyType == -1) {
                throw new Exception("[采购类型]: "+buyTypeStr+" 不存在,请检查后重新填写");
            }
            addDto.setBuyType(buyType);
        });
        CompletableFuture<Void> f2 = CompletableFuture.runAsync(()->{
            DropdownQuery query = new DropdownQuery();
            query.setText(supplierName);
            RestResponse<List<SupplierBasicInfo>> listRestResponse = warehouseClient.queryAllSuppliers(query);
            log.info("AssemblyObject queryAllSuppliers req = {}, result = {}", JsonUtils.toJsonString(query), JsonUtils.toJsonString(listRestResponse));
            if (listRestResponse.isSuccess()) {
                List<SupplierBasicInfo> supplierBasicInfos = listRestResponse.getData();
                if (CollectionUtils.isNotEmpty(supplierBasicInfos)) {
                    Integer supplierId = supplierBasicInfos.get(0).getId();
                    if (null != supplierId) {
                        addDto.setSupplierId(Long.valueOf(supplierId));
                    } else {
                        throw new Exception("[供应商]: "+supplierName+" 不存在,请检查后重新填写");
                    }
                } else {
                    throw new Exception("[供应商]: "+supplierName+" 不存在,请检查后重新填写");
                }
            }
        });
        addDto.setSupplierName(supplierName);
        CompletableFuture<Void> f3 = CompletableFuture.runAsync(()->{
            WarehouseManagementListReqDto warehouseManagementListReqDto = new WarehouseManagementListReqDto();
            warehouseManagementListReqDto.setWarehouseName(warehouseName);
            RestResponse<List<WarehouseManagementListRespDto>> warehouseManagementListResp = warehouseClient.findList(warehouseManagementListReqDto);
            log.info("AssemblyObject findList req = {}, result = {}", JsonUtils.toJsonString(warehouseManagementListReqDto), JsonUtils.toJsonString(warehouseManagementListResp));
            if (warehouseManagementListResp.isSuccess()) {
                List<WarehouseManagementListRespDto> warehouseManagementListRespDtos = warehouseManagementListResp.getData();
                if (CollectionUtils.isNotEmpty(warehouseManagementListRespDtos)) {
                    Long warehouseId = warehouseManagementListRespDtos.get(0).getId();
                    if (null != warehouseId) {
                        addDto.setWarehouseId(warehouseId);
                    } else {
                        throw new Exception("[仓库]: "+warehouseName+" 不存在,请检查后重新填写");
                    }
                } else {
                    throw new Exception("[仓库]: "+warehouseName+" 不存在,请检查后重新填写");
                }
            }
        });
        CompletableFuture<Void> f4 = CompletableFuture.runAsync(() -> {
            List<BuyOrderItemDto> itemList = new ArrayList<>();
            BigDecimal postAmount = new BigDecimal("0");
            MaterielSpuInfoQueryReqDto materielSpuInfoQueryReqDto = new MaterielSpuInfoQueryReqDto();
            materielSpuInfoQueryReqDto.setSkuCodeList(skuCodeList);
            RestResponse<PageInfo<MaterielSpuInfoRespDto>> pageInfoRestResponse = warehouseClient.warehouseoutboundorderitemInfo(materielSpuInfoQueryReqDto);
            log.info("AssemblyObject warehouseoutboundorderitemInfo req = {}, result = {}", skuCodeList, JsonUtils.toJsonString(pageInfoRestResponse));
            if (pageInfoRestResponse.isSuccess()) {
                PageInfo<MaterielSpuInfoRespDto> materielSpuInfoResp = pageInfoRestResponse.getData();
                if (null != materielSpuInfoResp) {
                    List<MaterielSpuInfoRespDto> list = materielSpuInfoResp.getList();
                    if (CollectionUtils.isEmpty(list))  {
                        throw new Exception("[SKU]: "+skuCodeList+" 不存在,请检查后重新填写");
                    }
                    for (MaterielSpuInfoRespDto dto : list) {
                        String spuCode = dto.getSpuCode();
                        String spuName = dto.getSpuName();
                        List<MaterielSkuBySpuRespDto> skuListInfo = dto.getSkuListInfo();
                        for (MaterielSkuBySpuRespDto materielSkuBySpuRespDto : skuListInfo) {
                            String skuCode = materielSkuBySpuRespDto.getSkuCode();
                            PurchaseOrderGoodsResp purchaseOrderGoodsResp = skuCode2IdentityMap.get(skuCode);
                            String count = purchaseOrderGoodsResp.getCount();
                            String price = purchaseOrderGoodsResp.getPrice();
                            String skuName = materielSkuBySpuRespDto.getSkuName();
                            Long materielModelId = materielSkuBySpuRespDto.getMaterielModelId();
                            Long materielBrandId = materielSkuBySpuRespDto.getMaterielBrandId();
                            Long materielClassId = materielSkuBySpuRespDto.getMaterielClassId();
                            String materielBrandName = materielSkuBySpuRespDto.getMaterielBrandName();
                            String materielClassName = materielSkuBySpuRespDto.getMaterielClassName();
                            String materielModelName = materielSkuBySpuRespDto.getMaterielModelName();
                            String thumbnailUrl = materielSkuBySpuRespDto.getThumbnailUrl();
                            Long materielId = materielSkuBySpuRespDto.getId();
                            String specValues = materielSkuBySpuRespDto.getSpecValues();
                            Integer enableUniqueCodeFlag = materielSkuBySpuRespDto.getEnableUniqueCodeFlag();
                            String materielNewConfigName = materielSkuBySpuRespDto.getMaterielNewConfigName();
                            String materielBrandCode = materielSkuBySpuRespDto.getMaterielBrandCode();
                            String materielClassCode = materielSkuBySpuRespDto.getMaterielClassCode();

                            BuyOrderItemDto buyOrderItemDto = new BuyOrderItemDto();
                            buyOrderItemDto.setSkuCode(skuCode);
                            buyOrderItemDto.setSkuName(skuName);
                            buyOrderItemDto.setMaterielModelId(materielModelId);
                            buyOrderItemDto.setMaterielBrandId(materielBrandId);
                            buyOrderItemDto.setMaterielClassId(materielClassId);
                            buyOrderItemDto.setMaterielModelName(materielModelName);
                            buyOrderItemDto.setMaterielBrandName(materielBrandName);
                            buyOrderItemDto.setMaterielClassName(materielClassName);
                            buyOrderItemDto.setSkuImg(thumbnailUrl);
                            buyOrderItemDto.setMaterielId(materielId);
                            buyOrderItemDto.setMaterielName(skuName);
                            buyOrderItemDto.setSpec(specValues);
                            buyOrderItemDto.setEnableUniqueCodeFlag(enableUniqueCodeFlag);
                            buyOrderItemDto.setMaterielNewConfigName(materielNewConfigName);
                            buyOrderItemDto.setSpuCode(spuCode);
                            buyOrderItemDto.setSpuName(spuName);
                            buyOrderItemDto.setCount(Integer.valueOf(count));
                            buyOrderItemDto.setSkuAmount(new BigDecimal(price));
                            buyOrderItemDto.setRebateAmount(new BigDecimal("0"));
                            itemList.add(buyOrderItemDto);
                            postAmount = new BigDecimal(price).multiply(new BigDecimal(count)).add(postAmount);
                        }
                    }
                }
                addDto.setItemList(itemList);
                addDto.setRebate(0);
                BuyOrderPaymentDto buyOrderPayment = new BuyOrderPaymentDto();
                buyOrderPayment.setAdvanceAmount(new BigDecimal("0"));
                buyOrderPayment.setBalancePaymentDay(1);
                buyOrderPayment.setPostAmount(postAmount);
                buyOrderPayment.setSettlementCycle(1);
                buyOrderPayment.setTotalAmount(postAmount);
                addDto.setBuyOrderPayment(buyOrderPayment);
            }
        });
        try {
            CompletableFuture<Void> future = CompletableFuture.allOf(f1, f2, f3, f4);
            future.get();
        } catch (Exception e){
            throw new Exception(e.getMessage().split("=",4)[3].replace(")",""));
        }
        return addDto;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/599253.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第29章-SR技术概述

1. SR技术的产生背景 2. SR技术的基本概念 3. SR技术的基本原理 1. SR技术的产生背景 1.1 传统的路由器设备因其转发性能较低 ① 最长匹配算法的缺点&#xff0c;需要遍历整个路由表&#xff1b; ② 早期路由器多采用通用CPU进行转发处理&#xff0c;性能有限&#xff1b; ③…

word:三线表的绘制【攻略】

word&#xff1a;三线表的绘制【攻略】 前言版权推荐word&#xff1a;三线表的绘制效果简单方法另外的方法 最后 前言 2024-5-7 18:25:08 以下内容源自《【攻略】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客…

Linux--基础IO(文件描述符fd)

目录 1.回顾一下文件 2.理解文件 下面就是系统调用的文件操作 文件描述符fd&#xff0c;fd的本质是什么&#xff1f; 读写文件与内核级缓存区的关系 据上理论我们就可以知道&#xff1a;open在干什么 3.理解Linux一切皆文件 4.C语言中的FILE* 1.回顾一下文件 先来段代码…

Upload-labs 靶场通关解析(上)

前言 文件上传漏洞是一种常见的网络安全漏洞&#xff0c;存在于许多Web应用程序中。攻击者利用这个漏洞可以上传恶意文件到目标服务器&#xff0c;从而执行各种恶意操作&#xff0c;如执行恶意代码、获取敏感信息、控制服务器等。 文件上传漏洞的原理是&#xff0c;Web应用程…

(✌)粤嵌—2024/5/7—除自身以外数组的乘积

代码实现&#xff1a; /*** Note: The returned array must be malloced, assume caller calls free().*/ int* productExceptSelf(int *nums, int numsSize, int *returnSize) {// 左乘积int l[numsSize];l[0] 1;for (int i 1; i < numsSize; i) {l[i] l[i - 1] * nums[…

PyQt6--Python桌面开发(1.安装配置环境)

一.PyQt6简介 PyQt&#xff1a;PyQt是一个功能强大且成熟的GUI框架&#xff0c;基于Qt库。它提供了丰富的组件、布局和主题选项&#xff0c;以及强大的功能和灵活性。PyQt的优点是它具有现代化的外观和丰富的功能&#xff0c;适用于复杂的GUI应用程序。然而&#xff0c;由于Py…

VMware导入ova/ovf虚拟机文件

1.文件-打开-ova文件 2.为新虚拟机起名称 3.等待导入 4.导入完成&#xff0c;可以开始使用 参考链接&#xff1a;VMware导入ova/ovf虚拟机文件

数仓分层——ODS、DW、ADS

一、什么是数仓分层 数据仓库分层是一种组织和管理数据仓库的结构化方法&#xff0c;它将数据仓库划分为不同的层次或级别&#xff0c;每个层次具有特定的功能和目的。这种分层方法有助于管理数据仓库中的数据流程、数据处理和数据访问&#xff0c;并提供一种清晰的结构来支持…

ArrayList线程安全问题解决方案

jdk8 Stream API的出现大大简化了我们对于集合元素的处理代码&#xff0c;对于串行流来说&#xff0c;无需考虑线程安全问题&#xff1b;但是&#xff0c;对于并行流来说&#xff0c;由于它是以多线程的方式并行处理同一个集合中的数据元素的&#xff0c;因此&#xff0c;存在着…

[Android]国内流行的应用市场

国内应用市场的特点 由于Google Play商店服务国内不可用&#xff0c;有许多其它的应用商店充当Android应用的分发渠道。这些商店通常由中国的主要科技公司运营&#xff0c;每个商店都有自己的运营策略和用户基础。 全球移动供应商市场份额&#xff1a;https://gs.statcounter…

我独自升级崛起PC下载安装教程 我独自升级崛起PC下载教程

《我独自升级&#xff1a;崛起》这款游戏灵感源自热门网络漫画《我独自升级》&#xff0c;是一款深度浸入式RPG游戏。它不仅呈献给玩家一个情节错综复杂、引人入胜的故事线&#xff0c;让玩家能紧随主角步伐&#xff0c;亲历其成长的点点滴滴&#xff0c;还自豪地展示了琳琅满目…

geojson文件规格

geojson文件示例&#xff0c; {"type": "FeatureCollection","features": [{"type": "Feature","geometry": {"type": "Point","coordinates": [102.0, 0.5]},"properties&q…

RT-DETR-20240507周更说明|更新Inner-IoU、Focal-IoU、Focaler-IoU等数十种IoU计算方式

RT-DETR改进专栏|包含主干、模块、注意力、损失函数等改进 专栏介绍 本专栏包含模块、卷积、检测头、损失等深度学习前沿改进,目前已有改进点70&#xff01;每周更新。 20240507更新说明&#xff1a; ⭐⭐ 更新CIoU、DIoU、MDPIoU、GIoU、EIoU、SIoU、ShapeIou、PowerfulIoU、…

聚乙烯醇(PVA)纳米纤维膜的性能介绍

聚乙烯醇&#xff08;PVA&#xff09;纳米纤维膜是一种由聚乙烯醇&#xff08;PVA&#xff09;分子组成的纳米级薄膜。这种材料具有许多优良的性能和应用前景&#xff0c;具体如下&#xff1a; 制备工艺&#xff1a;聚乙烯醇纳米纤维膜可以通过静电纺丝等方法制备。具体步骤包括…

网络知识点之—QoS

QoS&#xff08;Quality of Service&#xff0c;服务质量&#xff09;指一个网络能够利用各种基础技术&#xff0c;为指定的网络通信提供更好的服务能力&#xff0c;是网络的一种安全机制&#xff0c; 是用来解决网络延迟和阻塞等问题的一种技术。QoS的保证对于容量有限的网络来…

5000A信号发生器使用方法

背景 gnss工作需要使用的5000A&#xff0c;所以做成文档&#xff0c;用于其他员工学习。 下载星历数据 https://cddis.nasa.gov/archive/gnss/data/daily/2024/brdc/ 修改daily中的年份&#xff0c;就可以获取相关截至时间的星历数据 brcd数据格式 第一行记录了卫星的PRN号&a…

科创板门槛升级!解析中国量子企业的上市之路与国际比拼

4月30日晚&#xff0c;中国证监会于发布了修订后的《科创属性评价指引&#xff08;试行&#xff09;》&#xff08;以下简称“新指引”&#xff09;&#xff0c;该指引自发布日起正式生效。本次修订对原有指引中的部分标准进行了调整&#xff0c;具体如下&#xff1a; 1&#x…

远程桌面连接不上,远程桌面连接不上的专业解决策略

在信息技术领域&#xff0c;远程桌面连接是一种非常重要的工具&#xff0c;它允许用户从任何地点、任何时间访问和操作远程计算机。然而&#xff0c;当远程桌面连接出现问题时&#xff0c;可能会严重影响工作效率。以下是一些可能导致远程桌面连接不上的原因以及相应的解决方案…

干货分享-策划人都在用的活动策划网站

职场上&#xff0c;学会借力&#xff0c;学会‘抄’&#xff0c;比辛辛苦苦做老黄牛&#xff0c;更能事倍功半&#xff0c;不仅自己省事省力&#xff0c;还能获得更多升职加薪的机会。 那么&#xff0c;职场新人如何快速的写出一份领导满意的方案&#xff1f; 今天分享的‘抄…

深度学习之基于Vgg19预训练卷积神经网络图像风格迁移系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 在数字艺术和图像处理领域&#xff0c;图像风格迁移技术一直备受关注。该技术可以将一幅图像的内容和…
最新文章