<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot 集成 EasyExcel 3.x 優(yōu)雅實(shí)現(xiàn) Excel 導(dǎo)入導(dǎo)出

          共 15212字,需瀏覽 31分鐘

           ·

          2024-04-12 03:44

          來源:blog.csdn.net/qq991658923/article/details/128153012


          介紹

          EasyExcel 是一個(gè)基于 Java 的、快速、簡潔、解決大文件內(nèi)存溢出的 Excel 處理工具。它能讓你在不用考慮性能、內(nèi)存的等因素的情況下,快速完成 Excel 的讀、寫等功能。


          EasyExcel文檔地址:https://easyexcel.opensource.alibaba.com/


          快速開始


          引入依賴

          <dependency>    <groupId>com.alibaba</groupId>    <artifactId>easyexcel</artifactId>    <version>3.1.3</version></dependency>

          簡單導(dǎo)出


          以導(dǎo)出用戶信息為例,接下來手把手教大家如何使用EasyExcel實(shí)現(xiàn)導(dǎo)出功能!


          定義實(shí)體類


          在EasyExcel中,以面向?qū)ο笏枷雭韺?shí)現(xiàn)導(dǎo)入導(dǎo)出,無論是導(dǎo)入數(shù)據(jù)還是導(dǎo)出數(shù)據(jù)都可以想象成具體某個(gè)對象的集合,所以為了實(shí)現(xiàn)導(dǎo)出用戶信息功能,首先創(chuàng)建一個(gè)用戶對象UserDO實(shí)體類,用于封裝用戶信息:

          /** * 用戶信息 * * @author william@StarImmortal */@Datapublic class UserDO {    @ExcelProperty("用戶編號(hào)")    @ColumnWidth(20)    private Long id;    @ExcelProperty("用戶名")    @ColumnWidth(20)    private String username;    @ExcelIgnore    private String password;    @ExcelProperty("昵稱")    @ColumnWidth(20)    private String nickname;    @ExcelProperty("生日")    @ColumnWidth(20)    @DateTimeFormat("yyyy-MM-dd")    private Date birthday;    @ExcelProperty("手機(jī)號(hào)")    @ColumnWidth(20)    private String phone;    @ExcelProperty("身高(米)")    @NumberFormat("#.##")    @ColumnWidth(20)    private Double height;    @ExcelProperty(value = "性別", converter = GenderConverter.class)    @ColumnWidth(10)    private Integer gender;}


          上面代碼中類屬性上使用了EasyExcel核心注解:


          • @ExcelProperty:核心注解,value屬性可用來設(shè)置表頭名稱,converter屬性可以用來設(shè)置類型轉(zhuǎn)換器;

          • @ColumnWidth:用于設(shè)置表格列的寬度;

          • @DateTimeFormat:用于設(shè)置日期轉(zhuǎn)換格式;

          • @NumberFormat:用于設(shè)置數(shù)字轉(zhuǎn)換格式。


          自定義轉(zhuǎn)換器


          在EasyExcel中,如果想實(shí)現(xiàn)枚舉類型到字符串類型轉(zhuǎn)換(例如gender屬性:1 -> 男,2 -> 女),需實(shí)現(xiàn)Converter接口來自定義轉(zhuǎn)換器,下面為自定義GenderConverter性別轉(zhuǎn)換器代碼實(shí)現(xiàn):

          /** * Excel 性別轉(zhuǎn)換器 * * @author william@StarImmortal */public class GenderConverter implements Converter<Integer> {    @Override    public Class<?> supportJavaTypeKey() {        return Integer.class;    }    @Override    public CellDataTypeEnum supportExcelTypeKey() {        return CellDataTypeEnum.STRING;    }    @Override    public Integer convertToJavaData(ReadConverterContext<?> context) {        return GenderEnum.convert(context.getReadCellData().getStringValue()).getValue();    }    @Override    public WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) {        return new WriteCellData<>(GenderEnum.convert(context.getValue()).getDescription());    }}
          /** * 性別枚舉 * * @author william@StarImmortal */@Getter@AllArgsConstructorpublic enum GenderEnum {    /**     * 未知     */    UNKNOWN(0, "未知"),    /**     * 男性     */    MALE(1, "男性"),    /**     * 女性     */    FEMALE(2, "女性");    private final Integer value;    @JsonFormat    private final String description;    public static GenderEnum convert(Integer value) {        return Stream.of(values())                .filter(bean -> bean.value.equals(value))                .findAny()                .orElse(UNKNOWN);    }    public static GenderEnum convert(String description) {        return Stream.of(values())                .filter(bean -> bean.description.equals(description))                .findAny()                .orElse(UNKNOWN);    }}


          定義接口

          /** * EasyExcel導(dǎo)入導(dǎo)出 * * @author william@StarImmortal */@RestController@RequestMapping("/excel")public class ExcelController {    @GetMapping("/export/user")    public void exportUserExcel(HttpServletResponse response) {        try {            this.setExcelResponseProp(response, "用戶列表");            List<UserDO> userList = this.getUserList();            EasyExcel.write(response.getOutputStream())                    .head(UserDO.class)                    .excelType(ExcelTypeEnum.XLSX)                    .sheet("用戶列表")                    .doWrite(userList);        } catch (IOException e) {            throw new RuntimeException(e);        }    }    /**     * 設(shè)置響應(yīng)結(jié)果     *     * @param response    響應(yīng)結(jié)果對象     * @param rawFileName 文件名     * @throws UnsupportedEncodingException 不支持編碼異常     */    private void setExcelResponseProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException {        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");        response.setCharacterEncoding("utf-8");        String fileName = URLEncoder.encode(rawFileName, "UTF-8").replaceAll("\+", "%20");        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");    }        /**     * 讀取用戶列表數(shù)據(jù)     *     * @return 用戶列表數(shù)據(jù)     * @throws IOException IO異常     */    private List<UserDO> getUserList() throws IOException {        ObjectMapper objectMapper = new ObjectMapper();        ClassPathResource classPathResource = new ClassPathResource("mock/users.json");        InputStream inputStream = classPathResource.getInputStream();        return objectMapper.readValue(inputStream, new TypeReference<List<UserDO>>() {        });    }}


          測試接口


          運(yùn)行項(xiàng)目,通過 Postman 或者 Apifox 工具來進(jìn)行接口測試


          注意:在 Apifox 中訪問接口后無法直接下載,需要點(diǎn)擊返回結(jié)果中的下載圖標(biāo)才行,點(diǎn)擊之后方可對Excel文件進(jìn)行保存。


          接口地址:http://localhost:8080/excel/export/user




          復(fù)雜導(dǎo)出


          由于 EasyPoi 支持嵌套對象導(dǎo)出,直接使用內(nèi)置 @ExcelCollection 注解即可實(shí)現(xiàn),遺憾的是 EasyExcel 不支持一對多導(dǎo)出,只能自行實(shí)現(xiàn),通過此issues了解到,項(xiàng)目維護(hù)者建議通過自定義合并策略方式來實(shí)現(xiàn)一對多導(dǎo)出。



          解決思路:只需把訂單主鍵相同的列中需要合并的列給合并了,就可以實(shí)現(xiàn)這種一對多嵌套信息的導(dǎo)出


          自定義注解


          創(chuàng)建一個(gè)自定義注解,用于標(biāo)記哪些屬性需要合并單元格,哪個(gè)屬性是主鍵:

          /** * 用于判斷是否需要合并以及合并的主鍵 * * @author william@StarImmortal */@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface ExcelMerge {    /**     * 是否合并單元格     *     * @return true || false     */    boolean merge() default true;    /**     * 是否為主鍵(即該字段相同的行合并)     *     * @return true || false     */    boolean isPrimaryKey() default false;}


          定義實(shí)體類


          在需要合并單元格的屬性上設(shè)置 @ExcelMerge 注解,二級(jí)表頭通過設(shè)置 @ExcelProperty 注解中 value 值為數(shù)組形式來實(shí)現(xiàn)該效果:

          /** * @author william@StarImmortal */@Datapublic class OrderBO {    @ExcelProperty(value = "訂單主鍵")    @ColumnWidth(16)    @ExcelMerge(merge = true, isPrimaryKey = true)    private String id;    @ExcelProperty(value = "訂單編號(hào)")    @ColumnWidth(20)    @ExcelMerge(merge = true)    private String orderId;    @ExcelProperty(value = "收貨地址")    @ExcelMerge(merge = true)    @ColumnWidth(20)    private String address;    @ExcelProperty(value = "創(chuàng)建時(shí)間")    @ColumnWidth(20)    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")    @ExcelMerge(merge = true)    private Date createTime;    @ExcelProperty(value = {"商品信息", "商品編號(hào)"})    @ColumnWidth(20)    private String productId;    @ExcelProperty(value = {"商品信息", "商品名稱"})    @ColumnWidth(20)    private String name;    @ExcelProperty(value = {"商品信息", "商品標(biāo)題"})    @ColumnWidth(30)    private String subtitle;    @ExcelProperty(value = {"商品信息", "品牌名稱"})    @ColumnWidth(20)    private String brandName;    @ExcelProperty(value = {"商品信息", "商品價(jià)格"})    @ColumnWidth(20)    private BigDecimal price;    @ExcelProperty(value = {"商品信息", "商品數(shù)量"})    @ColumnWidth(20)    private Integer count;}


          數(shù)據(jù)映射與平鋪


          導(dǎo)出之前,需要對數(shù)據(jù)進(jìn)行處理,將訂單數(shù)據(jù)進(jìn)行平鋪,orderList為平鋪前格式,exportData為平鋪后格式:



          自定義單元格合并策略


          當(dāng) Excel 中兩列主鍵相同時(shí),合并被標(biāo)記需要合并的列:

          /** * 自定義單元格合并策略 * * @author william@StarImmortal */public class ExcelMergeStrategy implements RowWriteHandler {    /**     * 主鍵下標(biāo)     */    private Integer primaryKeyIndex;    /**     * 需要合并的列的下標(biāo)集合     */    private final List<Integer> mergeColumnIndexList = new ArrayList<>();    /**     * 數(shù)據(jù)類型     */    private final Class<?> elementType;    public ExcelMergeStrategy(Class<?> elementType) {        this.elementType = elementType;    }    @Override    public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {        // 判斷是否為標(biāo)題        if (isHead) {            return;        }        // 獲取當(dāng)前工作表        Sheet sheet = writeSheetHolder.getSheet();        // 初始化主鍵下標(biāo)和需要合并字段的下標(biāo)        if (primaryKeyIndex == null) {            this.initPrimaryIndexAndMergeIndex(writeSheetHolder);        }        // 判斷是否需要和上一行進(jìn)行合并        // 不能和標(biāo)題合并,只能數(shù)據(jù)行之間合并        if (row.getRowNum() <= 1) {            return;        }        // 獲取上一行數(shù)據(jù)        Row lastRow = sheet.getRow(row.getRowNum() - 1);        // 將本行和上一行是同一類型的數(shù)據(jù)(通過主鍵字段進(jìn)行判斷),則需要合并        if (lastRow.getCell(primaryKeyIndex).getStringCellValue().equalsIgnoreCase(row.getCell(primaryKeyIndex).getStringCellValue())) {            for (Integer mergeIndex : mergeColumnIndexList) {                CellRangeAddress cellRangeAddress = new CellRangeAddress(row.getRowNum() - 1, row.getRowNum(), mergeIndex, mergeIndex);                sheet.addMergedRegionUnsafe(cellRangeAddress);            }        }    }    /**     * 初始化主鍵下標(biāo)和需要合并字段的下標(biāo)     *     * @param writeSheetHolder WriteSheetHolder     */    private void initPrimaryIndexAndMergeIndex(WriteSheetHolder writeSheetHolder) {        // 獲取當(dāng)前工作表        Sheet sheet = writeSheetHolder.getSheet();        // 獲取標(biāo)題行        Row titleRow = sheet.getRow(0);        // 獲取所有屬性字段        Field[] fields = this.elementType.getDeclaredFields();        // 遍歷所有字段        for (Field field : fields) {            // 獲取@ExcelProperty注解,用于獲取該字段對應(yīng)列的下標(biāo)            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);            // 判斷是否為空            if (null == excelProperty) {                continue;            }            // 獲取自定義注解,用于合并單元格            ExcelMerge excelMerge = field.getAnnotation(ExcelMerge.class);            // 判斷是否需要合并            if (null == excelMerge) {                continue;            }            for (int i = 0; i < fields.length; i++) {                Cell cell = titleRow.getCell(i);                if (null == cell) {                    continue;                }                // 將字段和表頭匹配上                if (excelProperty.value()[0].equalsIgnoreCase(cell.getStringCellValue())) {                    if (excelMerge.isPrimaryKey()) {                        primaryKeyIndex = i;                    }                    if (excelMerge.merge()) {                        mergeColumnIndexList.add(i);                    }                }            }        }        // 沒有指定主鍵,則異常        if (null == this.primaryKeyIndex) {            throw new IllegalStateException("使用@ExcelMerge注解必須指定主鍵");        }    }}


          定義接口


          將自定義合并策略 ExcelMergeStrategy 通過 registerWriteHandler 注冊上去:

          /** * EasyExcel導(dǎo)入導(dǎo)出 * * @author william@StarImmortal */@RestController@RequestMapping("/excel")public class ExcelController {    @GetMapping("/export/order")    public void exportOrderExcel(HttpServletResponse response) {        try {            this.setExcelResponseProp(response, "訂單列表");            List<OrderDO> orderList = this.getOrderList();            List<OrderBO> exportData = this.convert(orderList);            EasyExcel.write(response.getOutputStream())                    .head(OrderBO.class)                    .registerWriteHandler(new ExcelMergeStrategy(OrderBO.class))                    .excelType(ExcelTypeEnum.XLSX)                    .sheet("訂單列表")                    .doWrite(exportData);        } catch (IOException e) {            throw new RuntimeException(e);        }    }    /**     * 設(shè)置響應(yīng)結(jié)果     *     * @param response    響應(yīng)結(jié)果對象     * @param rawFileName 文件名     * @throws UnsupportedEncodingException 不支持編碼異常     */    private void setExcelResponseProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException {        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");        response.setCharacterEncoding("utf-8");        String fileName = URLEncoder.encode(rawFileName, "UTF-8").replaceAll("\+", "%20");        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");    }}


          測試接口


          運(yùn)行項(xiàng)目,通過 Postman 或者 Apifox 工具來進(jìn)行接口測試


          注意:在 Apifox 中訪問接口后無法直接下載,需要點(diǎn)擊返回結(jié)果中的下載圖標(biāo)才行,點(diǎn)擊之后方可對Excel文件進(jìn)行保存。


          接口地址:http://localhost:8080/excel/export/order




          簡單導(dǎo)入


          以導(dǎo)入用戶信息為例,接下來手把手教大家如何使用EasyExcel實(shí)現(xiàn)導(dǎo)入功能!

          /** * EasyExcel導(dǎo)入導(dǎo)出 * * @author william@StarImmortal */@RestController@RequestMapping("/excel")@Api(tags = "EasyExcel")public class ExcelController {        @PostMapping("/import/user")    public ResponseVO importUserExcel(@RequestPart(value = "file") MultipartFile file) {        try {            List<UserDO> userList = EasyExcel.read(file.getInputStream())                    .head(UserDO.class)                    .sheet()                    .doReadSync();            return ResponseVO.success(userList);        } catch (IOException e) {            return ResponseVO.error();        }    }}

          參考資料


          • 項(xiàng)目地址:https://github.com/alibaba/easyexcel

          • 官方文檔:https://www.yuque.com/easyexcel/doc/easyexcel

          • 一對多導(dǎo)出優(yōu)雅方案:https://github.com/alibaba/easyexcel/issues/1780



          瀏覽 62
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  爆乳女神麻酥酥 | 日韩女优激情电影在线观看 | av秋霞| 成人黄色网址免费 | 高清视频在线观看一区 |