Excel导出

1. 编写导出类

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
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
@ColumnWidth  //默认行宽
@HeadRowHeight //首行高度
@ContentRowHeight //其他行高度
public class CameraExcel {
    /**
     * 编码
     */
    @ColumnWidth
    @ExcelProperty("编码")
    private String relationCode;

    /**
     * 设备名称
     */
    @ColumnWidth
    @ExcelProperty("设备名称")
    private String name;

    /**
     * 设备位置描述
     */
    @ColumnWidth
    @ExcelProperty("设备位置描述")
    private String location;

    /**
     * 设备厂家
     */
    @ColumnWidth
    @ExcelProperty("设备厂家")
    private String factory;

    /**
     * 设备型号
     */
    @ColumnWidth
    @ExcelProperty("设备型号")
    private String model;

    /**
     * 生产日期
     */
    @ColumnWidth
    @ExcelProperty(value = "生产日期 2017-01-01", converter = LocalDateTimeConverter.class) //LocalDateTime使用自定义转换器,不然无法转换
    @DateTimeFormat("yyyy-MM-dd")
    private LocalDateTime productionDate;

    /**
     * 启用状态
     */
    @ColumnWidth
    @ExcelProperty("启用状态(0否1是)")
    private Integer status;

}

2. 导出方法

public void export(HttpServletResponse response)throw Exception{
    //设置返回response的内容类型和编码格式
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding(Charsets.UTF_8.name());
    // 文件名称一定要编码,不然会返回乱码名称
    fileName = URLEncoder.encode(fileName, Charsets.UTF_8.name());
    response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");

    List<CameraExcel> list = new ArrayList<>();
    //填充完数据之后
    EasyExcel.write(response.getOutputStream(), CameraExcel.class).sheet().doWrite(list);
}

3. 自定义转换器

/**
 * @ClassName LocalDateTimeConverter 参考@Link,但Excel日期无法直接转换成LocalDateTime,就先转换为LocalDate再转换为LocalDateTime
 * @Description TODO:
 * @Date 2020/9/29 22:03
 * @Author HeXiaoyuan
 * @Link https://my.oschina.net/u/4256916/blog/3311780
 **/
public class LocalDateTimeConverter implements Converter<LocalDateTime> {

    @Override
    public Class<LocalDateTime> supportJavaTypeKey() {
        return LocalDateTime.class;
    }

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

    @Override
    public LocalDateTime convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration){
        // 将excel 中的 数据 转换为 LocalDateTime
        if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
            return LocalDateTime.parse(cellData.getStringValue(), DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        } else {
            // 获取注解的 format  注意,注解需要导入这个 excel.annotation.format.DateTimeFormat;
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(contentProperty.getDateTimeFormatProperty().getFormat());
            LocalDate localDate = LocalDate.parse(cellData.getStringValue(), formatter);
            long timestamp = localDate.atStartOfDay(ZoneOffset.ofHours(8)).toInstant().toEpochMilli();
            LocalDateTime localDateTime = Instant.ofEpochMilli(timestamp).atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
            return localDateTime;
//            return LocalDateTime.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        }
    }

    @Override
    public CellData convertToExcelData(LocalDateTime value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        // 将 LocalDateTime 转换为 String
        if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
            return new CellData(value.toString());
        } else {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(contentProperty.getDateTimeFormatProperty().getFormat());
            return new CellData(value.format(formatter));
        }
    }
}

4. 数据存储Listener

//***重点是实现集成AnalysisEventListener抽象类并实现invoke方法(每条数据均会执行invoke方法)。所以我们可以在invoke方法中实现存储逻辑
// Listener 不能被spring管理,要每次new,然后里面用到spring可以构造方法传进去
public class ImportListener extends AnalysisEventListener<DemoData> {
    private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<DemoData> list = new ArrayList<DemoData>();
    /**
     * 这个业务对象可以不是持久层对象,可以通过构造方法传入
     */
    private Service service;
    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param service
     */
    public DemoDataListener(Service service) {
        this.service = service;
    }
    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data
     *            one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }
    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        LOGGER.info("所有数据解析完成!");
    }
    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOGGER.info("{}条数据,开始存储数据库!", list.size());
        demoDAO.save(list);
        LOGGER.info("存储数据库成功!");
    }
}

5. 数据导入

    @PostMapping("/excel/upload")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "Excel模板导入", notes = "Excel模板导入")
    public R upload(@ApiParam(value = "模板文件", required = true) @RequestParam("file") MultipartFile file) {
        ImportListener<T> importListener = new ImportListener(importer);
        InputStream inputStream = new BufferedInputStream(excel.getInputStream());
        ExcelReaderBuilder builder = EasyExcel.read(inputStream, ExcelClazz.class, importListener);
        if (builder != null) {
            builder.doReadAll();
        }
        return R.success("导入成功");
    }

6. 官方文档

https://www.yuque.com/easyexcel/doc/easyexcel