《Spring Cloud Alibaba实战》系列-集成Easy Excel实现Excel的导入导出

简介

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。

快速开始

增加相关依赖包

<!--easyexcel-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.4</version>
</dependency>

监听器

在doSomething方法中实现新增逻辑,可实现异步导入

/**
 * 监听类,可以自定义
 *
 * @author liuyi
 * @Created 2019-7-18 18:01:53
 **/
public class ExcelListener extends AnalysisEventListener {

   /**
    * 自定义用于暂时存储data。
    * 可以通过实例获取该值
    */
   private List<Object> datas = new ArrayList<>();

   /**
    * 通过 AnalysisContext 对象还可以获取当前 sheet,当前行等数据
    */
   @Override
   public void invoke(Object object, AnalysisContext context) {
      //数据存储到list,供批量处理,或后续自己业务逻辑处理。
      datas.add(object);
      //根据业务自行 do something
      doSomething();
        /*
        如数据过大,可以进行定量分批处理
        if(datas.size()<=100){
            datas.add(object);
        }else {
            doSomething();
            datas = new ArrayList<Object>();
        }
         */
   }

   /**
    * 根据业务自行实现该方法
    */
   private void doSomething() {
   }

   @Override
   public void doAfterAllAnalysed(AnalysisContext context) {
        /*
            datas.clear();
            解析结束销毁不用的资源
         */
   }

   public List<Object> getDatas() {
      return datas;
   }

   public void setDatas(List<Object> datas) {
      this.datas = datas;
   }
}

工具类

/**
 * Excel工具类
 *
 * @author liuyi
 * @Created 2019-7-18 18:01:53
 **/
@Slf4j
public class EasyExcelUtil {
    /**
     * 读取单个sheet的excel文件
     * @param excel 文件
     * @param t 实体类型
     * @param headRowNumber 头行数
     * @return
     * @throws Exception
     */
    public static <T> List<T> readSingleExcel(MultipartFile excel, T t,int headRowNumber) throws IOException {
        return EasyExcel.read(excel.getInputStream(), t.getClass(), new ExcelListener())
                .sheet().headRowNumber(headRowNumber).doReadSync();
    }

   
    /**
     * 导出文件
     * 导出模板时,tList传一个空list即可
     * @param tList 数据集
     * @param tClass 数据类型
     * @param <T>
     * @throws IOException
     */
    public static <T> void writeSingleExcel(String fileName,String sheetName, List<T> tList, Class tClass) throws IOException{
        HttpServletResponse response = RequestHolder.getResponse();
        try (ServletOutputStream outputStream = response.getOutputStream()){
            setResponse(fileName, response);
            EasyExcel.write(outputStream, tClass).autoCloseStream(Boolean.FALSE)
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .registerWriteHandler(new CustomCellWriteHandler())
                    .sheet(sheetName)
                    .doWrite(tList);
        } catch (Exception e) {
            errorWrite(response, e);
        }
    }

    /**
     * 导出多sheet
     * @param fileName 文件名
     * @param sheetExcelDataList sheet对象
     * @throws IOException
     */
    public static void writeMultiExcel(String fileName, List<SheetExcelData> sheetExcelDataList) throws IOException{
        HttpServletResponse response = RequestHolder.getResponse();
        ServletOutputStream outputStream = response.getOutputStream();
        setResponse(fileName, response);
        ExcelWriter excelWriter = EasyExcel.write(outputStream).autoCloseStream(false).build();
        try {
            for (int i = 0,length = sheetExcelDataList.size(); i < length; i++) {
                WriteSheet writeSheet = EasyExcel.writerSheet(i+1, sheetExcelDataList.get(i).getSheetName())
                        .head(sheetExcelDataList.get(i).getTClass())
                        .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).build();
                excelWriter.write(sheetExcelDataList.get(i).getDataList(), writeSheet);
            }
        } catch (Exception e) {
            errorWrite(response, e);
        }finally {
            // 刷新流,不加这句话,下载文件损坏打不开
            outputStream.flush();
            // outputStream.close();
            if(excelWriter != null){
                // 千万别忘记finish关闭流
                excelWriter.finish();
            }
        }
    }

    /**
     * 无对象导出
     * @param fileName
     * @param headList
     * @param dataList
     * @throws IOException
     */
    public static void writeWithoutModel(String fileName, List<List<String>> headList,List<List<Object>> dataList) throws IOException{
        HttpServletResponse response = RequestHolder.getResponse();
        try (ServletOutputStream outputStream = response.getOutputStream()){
            setResponse(fileName, response);
            // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
            EasyExcel.write(outputStream).head(headList).sheet("模板").registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).doWrite(dataList);
        } catch (Exception e) {
            errorWrite(response, e);
        }
    }

    /**
     * 导出错误
     * @param response
     * @param e
     * @throws IOException
     */
    private static void errorWrite(HttpServletResponse response, Exception e) throws IOException {
        // 重置response
        response.reset();
        log.error(e.getMessage(), e);
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        response.getWriter().println(JSON.toJSONString(BaseResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR.value(), "导出失败")));
    }

    /**
     * 设置导出信息
     * @param fileName
     * @param response
     * @throws UnsupportedEncodingException
     */
    private static void setResponse(String fileName, HttpServletResponse response) throws UnsupportedEncodingException {
        // 重置response
        response.reset();
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码
        fileName = URLEncoder.encode(fileName + DateUtil.date2Str(new Date(),"yyyy-MM-dd_HH_mm_ss") + ExcelTypeEnum.XLSX.getValue(), "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName);
    }
}

处理器

可选项

/**
 * 自定义拦截器。对第一行第一列的头超链接到:https://blog.csdn.net/HXNLYW
 *
 * @author gourd.hu
 */
@Slf4j
public class CustomCellWriteHandler implements CellWriteHandler {

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
                                 Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,
        Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                 List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        if (isHead && cell.getColumnIndex() == 0) {
            CreationHelper createHelper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper();
            Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL);
            hyperlink.setAddress("https://blog.csdn.net/HXNLYW");
            cell.setHyperlink(hyperlink);
        }
    }

}


/**
 * @Description 自定义样式
 * @Author gourd
 * @Date 2020/1/3 13:58
 * @Version 1.0
 */
public class CustomerCellStyleHandler {

    public static HorizontalCellStyleStrategy getCustomerCellStyle()
    {
        // 头的策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 背景设置为红色
        headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short)20);
        headWriteCellStyle.setWriteFont(headWriteFont);
        // 内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
        contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        // 背景绿色
        contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
        contentWriteFont.setFontHeightInPoints((short)20);
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }

}


/**
 * 自定义拦截器.对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2
 *
 * @author Jiaju Zhuang
 */
@Slf4j
public class CustomSheetWriteHandler implements SheetWriteHandler {


    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        log.info("第{}个Sheet写入成功。", writeSheetHolder.getSheetNo());
        // 区间设置 第一列第一行和第二行的数据。由于第一行是头,所以第一、二行的数据实际上是第二三行
        CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 2, 0, 0);
        DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper();
        DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"测试1", "测试2"});
        DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
        writeSheetHolder.getSheet().addValidationData(dataValidation);
    }
}

案例

导入导出用户,部门

导入实体

/**
 * 部门导出导入实体
 * @author gourd
 * @date 2019-04-02 17:26:16
 */
@Data
public class DepartPO {

    /**
     * 姓名
     */
    @ExcelProperty(index = 0,value = {"名称"})
    private String name;

    /**
     * 编号
     */
    @ExcelProperty(index = 1,value = {"编号"})
    private String code;

    /**
     * 描述
     */
    @ExcelProperty(index = 2,value =  {"描述"})
    private String description;

}

/**
 * 用户导出导入实体
 * @author gourd
 * @date 2019-04-02 17:26:16
 */
@Data
public class UserPO {

    /**
     * 年龄
     */
    @ExcelProperty(index = 1,value = {"基础信息","年龄"})
    private Integer age;

    /**
     * 姓名
     */
    @ExcelProperty(index = 0,value = {"基础信息","姓名"})
    private String name;

    /**
     * 性别(M-男,F-女,X-未知)
     */
    @ExcelProperty(index = 2,value = {"基础信息","性别"},converter = SexConverter.class)
    @EnumValue
    private SexEnum sex;

    /**
     * 生日
     */
    @ExcelProperty(index = 3,value = "生日")
    @DateTimeFormat("yyyy年MM月dd日")
    private Date birth;

    /**
     * 邮箱
     */
    @ExcelProperty(index = 4,value = {"通讯信息","邮箱"})
    private String email;

    /**
     * 手机
     */
    @ExcelProperty(index = 5,value =  {"通讯信息","手机"})
    private String mobilePhone;

}

/**
 * @Description sheet对象
 * @Author gourd
 * @Date 2020/1/3 12:48
 * @Version 1.0
 */
@Data
public class SheetExcelData<T> {

    /**
     * 数据
     */
    private List<T> dataList;

    /**
     * sheet名
     */
    private String sheetName;

    /**
     * 对象类型
     */
    private Class tClass;
}

性别转换器和枚举

/**
 * @author 性别类型转化器
 */
public class SexConverter implements Converter<SexEnum> {
    @Override
    public Class supportJavaTypeKey() {
        return String.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    /**
     * 这里读的时候会调用
     *
     * @param cellData
     *            NotNull
     * @param contentProperty
     *            Nullable
     * @param globalConfiguration
     *            NotNull
     * @return
     */
    @Override
    public SexEnum convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
                                    GlobalConfiguration globalConfiguration) {
        return SexEnum.getEnumByLabel(cellData.getStringValue());
    }

    @Override
    public CellData convertToExcelData(SexEnum sexEnum, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return null;
    }

}

/**
 * 性别枚举工具类
 *
 * @author gourd
 * @create 2018-07-04 15:41
 **/
@JSONType(serializeEnumAsJavaBean = true)
public enum SexEnum {
    M("M","男"),
    F("F","女"),
    X("X","未知");

    @Getter
    @Setter
    @EnumValue
    private String value;

    @Getter
    @Setter
    private String label;

    SexEnum(String value, String label) {
        this.value = value;
        this.label = label;
    }

    /**
     * 根据label获取枚举
     * @param label
     * @return
     */
    public static SexEnum getEnumByLabel (String label){
        if(StringUtils.isBlank(label)){
            return null;
        }
        for (SexEnum sexEnum : SexEnum.values()) {
            if(sexEnum.getLabel().equals(label)){
                return sexEnum;
            }
        }
        return null;

    }
}

controller测试

/**
 * @Description excel操作
 * @Author gourd
 * @Date 2019/12/30 15:41
 * @Version 1.0
 */
@Api(tags = "excel", description = "excel操作" )
@RestController
@RequestMapping("/excel")
@Slf4j
public class EasyExcelController {

    @Autowired
    private RbacUserService rbacUserService;

    @Autowired
    private RbacDepartService rbacDepartService;


    /**
     * 单sheet文件导入
     *
     */
    @PostMapping("/single-read")
    @ResponseBody
    @ApiOperation(value = "单sheet文件导入")
    public BaseResponse singleImport(MultipartFile file) throws IOException {
        List<UserPO> userPOList = EasyExcelUtil.readSingleExcel(file,new UserPO(),2);
        // 导入逻辑 .......
        return BaseResponse.ok("success");
    }


    /**
     * 单sheet文件导出
     *
     */
    @GetMapping("/single-export")
    @ResponseBody
    @ApiOperation(value = "单sheet文件导出")
    public void singleExport() throws IOException{
        List<RbacUser> rbacUsers = rbacUserService.findAll();
        List<UserPO> userPOList = CollectionCopyUtil.copyList(rbacUsers, UserPO.class);
        EasyExcelUtil.writeSingleExcel("单sheet导出","用户",userPOList,UserPO.class);
    }



    /**
     * 多sheet文件导出
     *
     */
    @GetMapping("/multi-export")
    @ResponseBody
    @ApiOperation(value = "多sheet文件导出")
    public void multiSheetExport() throws IOException{
        List<RbacUser> rbacUsers = rbacUserService.findAll();
        List<UserPO> userPOList = CollectionCopyUtil.copyList(rbacUsers, UserPO.class);
        List<RbacDepart> rbacDeparts = rbacDepartService.list();
        List<DepartPO> departPOList = CollectionCopyUtil.copyList(rbacDeparts, DepartPO.class);
        List<SheetExcelData> sheetExcelDataList = new ArrayList<>(3);
        SheetExcelData<UserPO> userPOSheetExcelData = new SheetExcelData<>();
        userPOSheetExcelData.setSheetName("用户");
        userPOSheetExcelData.setTClass(UserPO.class);
        userPOSheetExcelData.setDataList(userPOList);
        SheetExcelData<DepartPO> departPOSheetExcelData = new SheetExcelData<>();
        departPOSheetExcelData.setSheetName("部门");
        departPOSheetExcelData.setTClass(DepartPO.class);
        departPOSheetExcelData.setDataList(departPOList);
        sheetExcelDataList.add(userPOSheetExcelData);
        sheetExcelDataList.add(departPOSheetExcelData);
        EasyExcelUtil.writeMultiExcel("多sheet导出",sheetExcelDataList);
    }

    /**
     * 无对象文件导出
     *
     */
    @GetMapping("/no-model-export")
    @ResponseBody
    @ApiOperation(value = "无对象文件导出")
    public void noModelExport() throws IOException{
        EasyExcelUtil.writeWithoutModel("测试无对象导出",head(),dataList());
    }


    private List<List<String>> head() {
        List<List<String>> list = new ArrayList<>();
        List<String> head0 = new ArrayList<>();
        head0.add("统一头");
        head0.add("字符串");
        List<String> head1 = new ArrayList<>();
        head1.add("统一头");
        head1.add("数字" );
        List<String> head2 = new ArrayList<>();
        head2.add("统一头");
        head2.add("日期");
        list.add(head0);
        list.add(head1);
        list.add(head2);
        return list;
    }

    private List<List<Object>> dataList() {
        List<List<Object>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            List<Object> data = new ArrayList<>();
            data.add("字符串" + i);
            data.add(0.56);
            data.add(DateUtil.date2Str(new Date(),"yyyy-MM-dd HH:mm:ss"));
            list.add(data);
        }
        return list;
    }

}

测试效果

单,多sheet的导出

excel导入,通过断点的方式演示

===============================================

代码均已上传至本人的开源项目

葫芦胡:https://blog.csdn.net/HXNLYW/article/details/98037354

发布了92 篇原创文章 · 获赞 362 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/HXNLYW/article/details/103896356