最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用EasyExcel實現(xiàn)模板導(dǎo)出Excel數(shù)據(jù)并合并單元格

 更新時間:2026年03月23日 09:43:57   作者:Java小王子呀  
這篇文章主要為大家詳細(xì)介紹了如何使用EasyExcel實現(xiàn)模板導(dǎo)出Excel數(shù)據(jù)并合并單元格,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下

需求

數(shù)據(jù)庫里的主表+明細(xì)表,聯(lián)查出數(shù)據(jù)并導(dǎo)出Excel,合并主表數(shù)據(jù)的單元格。

代碼

controller

    @PostMapping("export")
    @ApiOperation(value = "導(dǎo)出數(shù)據(jù)")
    protected void export(@ApiParam @Valid @RequestBody NewWmsExceptionCaseSearchCondition request, HttpServletResponse response) throws IOException {
        getService().export(request, response);
    }

service

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.ctsfreight.oseb.common.strategy.CustomRowMergeStrategy;
import com.ctsfreight.oseb.common.utils.TokenUtil;
import com.ctsfreight.oseb.common.vo.*;
import com.ctsfreight.oseb.common.vo.excel.ExceptionExcelVo;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.ResourceLoader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;


    @Resource
    private ResourceLoader resourceLoader;
    private final String TEMPLATE_EXCEPTION_EXCEL_XLSX = "classpath:template/exception_excel.xlsx";


 @Override
    public void export(NewWmsExceptionCaseSearchCondition request, HttpServletResponse response) throws IOException {
        String fileName = "明細(xì)_" + LocalDateTime.now();
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(fileName + ".xlsx", "utf-8"));

        String template = TEMPLATE_EXCEPTION_EXCEL_XLSX;
        InputStream inputStream = resourceLoader.getResource(template).getInputStream();
        File xlsx = null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();

            List<ExceptionExcelVo> crossdockSeaFinanceVoList = baseMapper.listExceptionExcelVo(request);
            if (CollectionUtils.isNotEmpty(crossdockSeaFinanceVoList)) {
                AtomicInteger index = new AtomicInteger(0);
                AtomicReference<String> lastId = new AtomicReference<>("");
                crossdockSeaFinanceVoList.forEach(item -> {
                    String currentId = item.getId();
                    if (!lastId.get().equals(currentId)) {
                        index.set(index.get() + 1);
                        lastId.set(currentId);
                    }
                    item.setId(String.valueOf(index.get()));
                });

                ExcelWriter excelWriter = EasyExcel.write(bos).registerWriteHandler(new CustomRowMergeStrategy(ExceptionExcelVo.class))
                        .withTemplate(inputStream).build();

                WriteSheet writeSheet = EasyExcel.writerSheet(0).build();
                excelWriter.write(crossdockSeaFinanceVoList, writeSheet);
                excelWriter.finish();
            }

            InputStreamSource inputStreamSource = new ByteArrayResource(bos.toByteArray());
            xlsx = File.createTempFile("明細(xì)_" + UUID.randomUUID(), ".xlsx");
            FileUtils.copyInputStreamToFile(inputStreamSource.getInputStream(), xlsx);
            IOUtils.copy(inputStreamSource.getInputStream(), response.getOutputStream());
        } catch (Exception e) {
            log.error("export error", e);
            throw new ApiException(ResultCode.FAULT);
        } finally {
            if (xlsx != null) {
                xlsx.delete();
            }
            inputStream.close();
        }
    }

這里的template 是放在了src/main/resources/template/delivery_export_en.xlsx

xml

    <select id="listExceptionExcelVo" resultType="com.ctsfreight.oseb.common.vo.excel.ExceptionExcelVo">
        SELECT ecs.id AS id,
               ecs.order_no       AS orderNo,
               ecs.container_no   AS containerNo,
               ecs.total_amount   AS totalAmount,
               ecsit.sort_note    AS sortNote,
               ecsit.consignee_name AS consigneeName,
               ecsit.fba_id       AS fbaId,
               ecsit.fba_number   AS fbaNumber,
               ecsit.package_num  AS packageNum
        FROM (
                 SELECT id, order_no, container_no, total_amount, create_time
                 FROM exception_case_summary
                 WHERE delete_flag = 0
                    <if test="request.summaryIdList != null and !request.summaryIdList.isEmpty()">
                        AND id IN
                            <foreach item="id" collection="request.summaryIdList" open="(" separator="," close=")">
                                #{id}
                            </foreach>
                    </if>
                 ORDER BY create_time DESC
                     LIMIT 100
             ) ecs
                 LEFT JOIN exception_case_sorting_item ecsit
                           ON ecs.id = ecsit.exception_case_summary_id
                 WHERE ecsit.delete_flag = 0
                 ORDER BY ecs.create_time DESC;
    </select>

LIMIT 100,是為了查詢最新的100條數(shù)據(jù),不然后面數(shù)據(jù)太多了

vo:

package com.ctsfreight.oseb.common.vo.excel;

import com.alibaba.excel.annotation.ExcelProperty;
import com.ctsfreight.oseb.common.strategy.annotations.CustomRowMerge;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * <p>
 *  信息VO
 * </p>
 *
 *
 */
@Data
@Accessors(chain = true)
@ApiModel(value = "信息VO")
public class ExceptionExcelVo {

    @ApiModelProperty("主表id")
    @ExcelProperty(index = 0)
    @CustomRowMerge(needMerge = true, isPk = true)
    private String id;

    @ApiModelProperty("號")
    @ExcelProperty(index = 1)
    @CustomRowMerge(needMerge = true)
    private String containerNo;

    @ApiModelProperty("單號")
    @ExcelProperty(index = 2)
    @CustomRowMerge(needMerge = true)
    private String orderNo;

    @ApiModelProperty("總箱數(shù)")
    @ExcelProperty(index = 3)
    @CustomRowMerge(needMerge = true)
    private Integer totalAmount;

    @ApiModelProperty("標(biāo)")
    @ExcelProperty(index = 4)
    private String sortNote;

    @ApiModelProperty("")
    @ExcelProperty(index = 5)
    private String consigneeName;

    @ApiModelProperty("")
    @ExcelProperty(index = 6)
    private String fbaId;

    @ApiModelProperty("")
    @ExcelProperty(index = 7)
    private String fbaNumber;

    @ApiModelProperty("箱數(shù)")
    @ExcelProperty(index = 8)
    private Integer packageNum;

}

自定義單元格合并策略

package com.ctsfreight.oseb.common.strategy;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.ctsfreight.oseb.common.strategy.annotations.CustomRowMerge;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * 自定義單元格合并策略
 */
public class CustomRowMergeStrategy implements RowWriteHandler {
    /**
     * 主鍵下標(biāo)集合
     */
    private List<Integer> pkColumnIndex = new ArrayList<>();

    /**
     * 需要合并的列的下標(biāo)集合
     */
    private List<Integer> needMergeColumnIndex = new ArrayList<>();

    /**
     * DTO數(shù)據(jù)類型
     */
    private Class<?> elementType;

    public CustomRowMergeStrategy(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 sheet = writeSheetHolder.getSheet();

        // 獲取標(biāo)題行
        Row titleRow = sheet.getRow(0);

        if (pkColumnIndex.isEmpty()) {
            this.lazyInit(writeSheetHolder);
        }

        // 判斷是否需要和上一行進行合并
        // 不能和標(biāo)題合并,只能數(shù)據(jù)行之間合并
        if (row.getRowNum() <= 1) {
            return;
        }
        // 獲取上一行數(shù)據(jù)
        Row lastRow = sheet.getRow(row.getRowNum() - 1);
        // 將本行和上一行是同一類型的數(shù)據(jù)(通過主鍵字段進行判斷),則需要合并
        boolean margeBol = true;
        for (Integer pkIndex : pkColumnIndex) {
            String lastKey = lastRow.getCell(pkIndex).getCellType() == CellType.STRING ? lastRow.getCell(pkIndex).getStringCellValue() : String.valueOf(lastRow.getCell(pkIndex).getNumericCellValue());
            String currentKey = row.getCell(pkIndex).getCellType() == CellType.STRING ? row.getCell(pkIndex).getStringCellValue() : String.valueOf(row.getCell(pkIndex).getNumericCellValue());
            if (!StringUtils.equalsIgnoreCase(lastKey, currentKey)) {
                margeBol = false;
                break;
            }
        }
        if (margeBol) {
            for (Integer needMerIndex : needMergeColumnIndex) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(row.getRowNum() - 1, row.getRowNum(),
                        needMerIndex, needMerIndex);
                sheet.addMergedRegionUnsafe(cellRangeAddress);
            }
        }
    }

    /**
     * 初始化主鍵下標(biāo)和需要合并字段的下標(biāo)
     */
    private void lazyInit(WriteSheetHolder writeSheetHolder) {

        // 獲取當(dāng)前sheet
        Sheet sheet = writeSheetHolder.getSheet();

        // 獲取標(biāo)題行
        Row titleRow = sheet.getRow(0);
        // 獲取DTO的類型
        Class<?> eleType = this.elementType;

        // 獲取DTO所有的屬性
        Field[] fields = eleType.getDeclaredFields();

        int i = 0;
        // 遍歷所有的字段,因為是基于DTO的字段來構(gòu)建excel,所以字段數(shù) >= excel的列數(shù)
        for (Field theField : fields) {
            // 獲取@ExcelProperty注解,用于獲取該字段對應(yīng)在excel中的列的下標(biāo)
            ExcelProperty easyExcelAnno = theField.getAnnotation(ExcelProperty.class);
            // 為空,則表示該字段不需要導(dǎo)入到excel,直接處理下一個字段
            if (null == easyExcelAnno) {
                continue;
            }
            // 獲取自定義的注解,用于合并單元格
            CustomRowMerge customMerge = theField.getAnnotation(CustomRowMerge.class);

            // 沒有@CustomMerge注解的默認(rèn)不合并
            if (null == customMerge) {
                continue;
            }

            // 判斷是否有主鍵標(biāo)識
            if (customMerge.isPk()) {
                pkColumnIndex.add(i);
            }

            // 判斷是否需要合并
            if (customMerge.needMerge()) {
                needMergeColumnIndex.add(i);
            }
            i++;
        }

        // 沒有指定主鍵,則異常
        if (pkColumnIndex.isEmpty()) {
            throw new IllegalStateException("使用@CustomMerge注解必須指定主鍵");
        }

    }
}

效果圖

拓展

可以增加居中策略

可以通過 EasyExcel 的 WriteHandlerAbstractCellStyleStrategy 來設(shè)置 Excel 單元格內(nèi)容的 水平居中垂直居中

使用 WriteHandler 自定義單元格樣式

你可以創(chuàng)建一個繼承自 AbstractCellStyleStrategyAbstractCellWriteHandler 的類,設(shè)置單元格樣式。

import com.alibaba.excel.write.handler.AbstractCellStyleStrategy;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class CenterCellStyleStrategy extends AbstractCellStyleStrategy {

    @Override
    protected void setHeadCellStyle(Cell cell, Head head, Integer relativeRowIndex) {
        // 如果你也希望表頭居中,可以在這里設(shè)置
        setCellStyle(cell);
    }

    @Override
    protected void setContentCellStyle(Cell cell, Head head, Integer relativeRowIndex) {
        setCellStyle(cell);
    }

    private void setCellStyle(Cell cell) {
        Workbook workbook = cell.getSheet().getWorkbook();
        CellStyle cellStyle = workbook.createCellStyle();

        // 設(shè)置水平居中
        cellStyle.setAlignment(HorizontalAlignment.CENTER);

        // 設(shè)置垂直居中
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        // 可選:自動換行
        cellStyle.setWrapText(true);

        cell.setCellStyle(cellStyle);
    }
}

注冊樣式策略到導(dǎo)出邏輯中

ExcelWriter excelWriter = EasyExcel.write(bos)
    .registerWriteHandler(new CenterCellStyleStrategy()) // 設(shè)置居中樣式
    .registerWriteHandler(new CustomRowMergeStrategy(Arrays.asList(
        "containerNo", "orderNo", "totalAmount", "sortNote", "consigneeName"
    )))
    .withTemplate(inputStream)
    .build();

如果你只想對某些列設(shè)置居中(可選)

你可以修改 setCellStyle 方法,根據(jù) cell.getColumnIndex() 判斷是否對某些列應(yīng)用居中

private void setCellStyle(Cell cell) {
    Workbook workbook = cell.getSheet().getWorkbook();
    CellStyle cellStyle = workbook.createCellStyle();

    // 只對第 0 列(柜號)和第 2 列(登記總箱數(shù))設(shè)置居中
    if (cell.getColumnIndex() == 0 || cell.getColumnIndex() == 2) {
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setWrapText(true);
    } else {
        // 其他列左對齊
        cellStyle.setAlignment(HorizontalAlignment.LEFT);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
    }

    cell.setCellStyle(cellStyle);
}

如果你使用的是 .xlsx 模板,并希望保留模板樣式

你可以這樣設(shè)置:

// 從模板中讀取樣式,避免覆蓋原有樣式
CellStyle originalStyle = cell.getCellStyle();

CellStyle newStyle = workbook.createCellStyle();
newStyle.cloneStyleFrom(originalStyle); // 復(fù)制原樣式
newStyle.setAlignment(HorizontalAlignment.CENTER);
newStyle.setVerticalAlignment(VerticalAlignment.CENTER);
newStyle.setWrapText(true);

cell.setCellStyle(newStyle);

到此這篇關(guān)于使用EasyExcel實現(xiàn)模板導(dǎo)出Excel數(shù)據(jù)并合并單元格的文章就介紹到這了,更多相關(guān)EasyExcel模板導(dǎo)出Excel數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot?讀取yml文件的多種方式匯總

    SpringBoot?讀取yml文件的多種方式匯總

    這篇文章主要介紹了SpringBoot讀取yml文件的幾種方式,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-05-05
  • java線程池詳解及代碼介紹

    java線程池詳解及代碼介紹

    這篇文章主要介紹了java中線程池的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-08-08
  • SpringMvc/SpringBoot HTTP通信加解密的實現(xiàn)

    SpringMvc/SpringBoot HTTP通信加解密的實現(xiàn)

    這篇文章主要介紹了SpringMvc/SpringBoot HTTP通信加解密的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • 深入解析Java編程中final關(guān)鍵字的作用

    深入解析Java編程中final關(guān)鍵字的作用

    final關(guān)鍵字正如其字面意思一樣,意味著最后,比如被final修飾后類不能集成、變量不能被再賦值等,以下我們就來深入解析Java編程中final關(guān)鍵字的作用:
    2016-06-06
  • JVM調(diào)優(yōu)參數(shù)的設(shè)置

    JVM調(diào)優(yōu)參數(shù)的設(shè)置

    Java虛擬機的調(diào)優(yōu)是一個復(fù)雜而關(guān)鍵的任務(wù),可以通過多種參數(shù)來實現(xiàn),本文就來介紹一下JVM調(diào)優(yōu)參數(shù)的設(shè)置,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • 一文搞懂Runnable、Callable、Future、FutureTask及應(yīng)用

    一文搞懂Runnable、Callable、Future、FutureTask及應(yīng)用

    一般創(chuàng)建線程只有兩種方式,一種是繼承Thread,一種是實現(xiàn)Runnable接口,在Java1.5之后就有了Callable、Future,這二種可以提供線程執(zhí)行完的結(jié)果,本文主要介紹了Runnable、Callable、Future、FutureTask及應(yīng)用,感興趣的可以了解一下
    2023-08-08
  • java中常用的json,jsonarray,map數(shù)據(jù)結(jié)構(gòu)與對象互轉(zhuǎn)詳解

    java中常用的json,jsonarray,map數(shù)據(jù)結(jié)構(gòu)與對象互轉(zhuǎn)詳解

    這篇文章主要為大家詳細(xì)介紹了java中常用的json,jsonarray,map數(shù)據(jù)結(jié)構(gòu)與對象互轉(zhuǎn)的相關(guān)方法,主要是FastJSON和Jackson兩種常用庫,有需要的小伙伴可以了解下
    2025-12-12
  • 淺談Spring學(xué)習(xí)之request,session與globalSession作用域

    淺談Spring學(xué)習(xí)之request,session與globalSession作用域

    這篇文章主要介紹了Spring學(xué)習(xí)之request,session與globalSession作用域的相關(guān)內(nèi)容,需要的朋友可以參考下。
    2017-09-09
  • Bean的循環(huán)依賴問題全面解析

    Bean的循環(huán)依賴問題全面解析

    文章介紹了Spring框架如何解決setter方法注入的單例bean之間的循環(huán)依賴問題,本文結(jié)合實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2025-12-12
  • Java十分鐘精通進階適配器模式

    Java十分鐘精通進階適配器模式

    適配器模式(Adapter?Pattern)是作為兩個不兼容的接口之間的橋梁。這種類型的設(shè)計模式屬于結(jié)構(gòu)型模式,它結(jié)合了兩個獨立接口的功能
    2022-04-04

最新評論

辰溪县| 沧源| 句容市| 贡山| 晋中市| 灵宝市| 阿城市| 云霄县| 临清市| 丹巴县| 阿尔山市| 龙游县| 台东市| 金溪县| 汉川市| 碌曲县| 湖北省| 砚山县| 高雄县| 台北市| 蕲春县| 自治县| 通道| 吴旗县| 乐都县| 岫岩| 厦门市| 巴中市| 盐城市| 苍梧县| 阜新市| 五大连池市| 尉犁县| 镇平县| 互助| 铜陵市| 枣阳市| 万宁市| 怀远县| 两当县| 神农架林区|