Java实现Excel导出的全部流程(多Sheet、复杂格式)

 更新时间:2025年12月22日 09:26:06   作者:喝汽水的猫^  
这篇文章主要介绍了如何根据图片格式导出Excel文件,并根据项目号区分不同的sheet页,通过Maven依赖、实体类定义、导出工具和使用示例,详细描述了如何实现这一功能,包括Sheet分组逻辑、格式还原和动态数据处理,需要的朋友可以参考下

需求:

1、根据如下图片,导出同样格式的excel文件。
2、并且list实体集合有项目号,根据项目号区分导出不同sheet页。项目号格式:G10086B1,G10087B1,去掉最后两位B1或B2在区分,比如G10086,G10086为一个sheet页。G10087又为一个新的sheet页。

一、依赖引入(Maven)

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.5</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.5</version>
</dependency>

二、完整的实体类定义

import lombok.Data;
import java.io.Serializable;

/**
 - 入库单实体类(完全对应Excel表格字段)
 */
@Data
public class WarehouseIn implements Serializable {
    private static final long serialVersionUID = 1L;

    // 1. 序号(Excel第1列,导出时自动生成,无需手动设置)
    private Integer serialNo;

    // 2. 日期(Excel第2列)
    private String date;

    // 3. 所属车间(Excel第3列)
    private String workshop;

    // 4. 项目号(Excel第4列,格式如GPC25061095B2,用于分组Sheet)
    private String projectNo;

    // 5. 机台(Excel第5列)
    private String machine;

    // 6. 模号(Excel第6列)
    private String modelNo;

    // 7. 任务包编码(Excel第7列)
    private String taskPackageCode;

    // 8. 任务包名称(Excel第8列)
    private String taskPackageName;

    // 9. 入库数量(Excel第9列)
    private Integer inStockQty;

    // 10. 实收数(Excel第10列)
    private Integer actualReceivedQty;

    // 11. 班(Excel第11列,如:早班/中班/晚班)
    private String workShift;

    // 12. 组(Excel第12列,如:1组/2组)
    private String workGroup;

    // 13. 组员(Excel第13列,如:张三/李四)
    private String groupMember;

    // 14. 备注(Excel第14列)
    private String remarks;

    // 扩展字段(Excel中隐藏的辅助字段,用于填充表头信息)
    // 填单日期(Excel表头-填单日期)
    private String fillDate;

    // 交付批次(Excel表头-交付批次,如:第二批(2+2))
    private String deliveryBatch;

    // 单号(Excel表头-单号,如:GEN2025131)
    private String orderNo;
}

实体类说明:

  • 字段对应关系:每个字段与 Excel 表格列完全对齐,字段名采用「业务语义 + 字段类型」命名,便于理解和维护。
  • 扩展字段:fillDate(填单日期)、deliveryBatch(交付批次)、orderNo(单号)是 Excel 表头的动态信息,实体类中新增这些字段用于填充,避免硬编码。
  • 序列化:实现Serializable接口,支持分布式场景下的数据传输(如 Redis 缓存、RPC 调用)。
  • Lombok 注解:使用@Data自动生成getter/setter/toString等方法,简化代码。

三、代码实现

1、导出工具

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ExcelExportUtil {

			/**
	     * 生产入库单导出
	     */
	    @Override
	    public void exportWarehouseInExcel(OutputStream outputStream, List<StockInEntity> list) throws IOException {
	        // 按项目号分组(去掉最后两位,如GPC25061095B2 → GPC25061095)
	        Map<String, List<StockInEntity>> groupByProject = list.stream()
	                .filter(item -> isValidProjectNumber(item.getProjectNumber()))
	                .collect(Collectors.groupingBy(item -> truncateProjectNumber(item.getProjectNumber())));
	
	        // 创建Workbook并自动关闭
	        try (Workbook workbook = new XSSFWorkbook()) {
	
	            // 为每个分组创建Sheet
	            for (Map.Entry<String, List<StockInEntity>> entry : groupByProject.entrySet()) {
	                String sheetName = entry.getKey() + "项目"; // Sheet名(如GPC25061095项目)
	                Sheet sheet = workbook.createSheet(sheetName);
	                List<StockInEntity> sheetData = entry.getValue();
	
	                // 构建当前Sheet的入库单内容
	                buildStockInEntitySheet(sheet, sheetData);
	            }
	
	            // 写出Excel
	            workbook.write(outputStream);
	        }
	    }
	
	    // 判断项目编号是否合法
	    private boolean isValidProjectNumber(String projectNo) {
	        return projectNo != null && projectNo.length() >= 2;
	    }
	
	
	    // 截取项目编号(去掉最后两位)
	    private String truncateProjectNumber(String projectNo) {
	        if (!isValidProjectNumber(projectNo)) {
	            throw new IllegalArgumentException("Invalid project number: " + projectNo);
	        }
	        return projectNo.substring(0, projectNo.length() - 2);
	    }
	
	    /**
	     * 构建单个Sheet的入库单格式
	     */
	    private static void buildStockInEntitySheet(Sheet sheet, List<StockInEntity> data) {
	        // 指定时区
	        ZoneId zoneId = ZoneId.of("Asia/Shanghai");
	        // 获取指定时区的当前日期
	        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
	        String formattedDate = LocalDate.now(zoneId).format(formatter);
	
	        // ========== 设置列宽 ==========
	        int[] columnWidths = {2500, 3500, 5000, 6000, 4000, 4500, 5500, 6500, 3000, 3000, 3000, 3000, 7000, 3000};
	        for (int i = 0; i < columnWidths.length; i++) {
	            sheet.setColumnWidth(i, columnWidths[i]);
	        }
	
	        // ==========  创建样式 ==========
	        Workbook workbook = sheet.getWorkbook();
	        // 标题样式(居中、加粗)
	        CellStyle titleStyle = workbook.createCellStyle();
	        Font titleFont = workbook.createFont();
	        titleFont.setBold(true);
	        titleFont.setFontHeightInPoints((short) 16);
	        titleStyle.setFont(titleFont);
	        titleStyle.setAlignment(HorizontalAlignment.CENTER);
	        titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
	
	        // 表头样式(灰色背景、居中)
	        CellStyle headerStyle = workbook.createCellStyle();
	        Font headerFont = workbook.createFont();
	        headerFont.setBold(true);
	        headerFont.setColor(IndexedColors.WHITE.getIndex());
	        headerStyle.setFont(headerFont);
	        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
	        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
	        headerStyle.setAlignment(HorizontalAlignment.CENTER);
	        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
	        headerStyle.setBorderTop(BorderStyle.THIN);
	        headerStyle.setBorderBottom(BorderStyle.THIN);
	        headerStyle.setBorderLeft(BorderStyle.THIN);
	        headerStyle.setBorderRight(BorderStyle.THIN);
	
	        // 内容样式(居中、边框)
	        CellStyle contentStyle = workbook.createCellStyle();
	        Font row4Font = workbook.createFont();
	        row4Font.setBold(true);
	        contentStyle.setFont(row4Font);
	        contentStyle.setAlignment(HorizontalAlignment.CENTER);
	        contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);
	        contentStyle.setBorderTop(BorderStyle.THIN);
	        contentStyle.setBorderBottom(BorderStyle.THIN);
	        contentStyle.setBorderLeft(BorderStyle.THIN);
	        contentStyle.setBorderRight(BorderStyle.THIN);
	
	        // ==========  构建Sheet内容 ==========
	        // 第1行:入库单标题(合并单元格)
	        Row row1 = sheet.createRow(0);
	        Cell cell1 = row1.createCell(0);
	        cell1.setCellValue("入库单");
	        cell1.setCellStyle(titleStyle);
	        sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 13)); // 合并A1-N1
	
	        // 第2行:填单日期、项目号等信息(合并单元格)
	        CellStyle row2Style = workbook.createCellStyle();
	        Font row2Font = workbook.createFont();
	        row2Font.setUnderline(Font.U_SINGLE); // 单下划线(也可以用U_DOUBLE表示双下划线)
	        row2Font.setBold(true);
	        row2Style.setFont(row2Font);
	        row2Style.setFillForegroundColor(IndexedColors.WHITE.getIndex());
	        row2Style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
	        row2Style.setAlignment(HorizontalAlignment.CENTER);
	        row2Style.setVerticalAlignment(VerticalAlignment.CENTER);
	        // 创建行并设置高度
	        Row row2 = sheet.createRow(1);
	        row2.setHeightInPoints((short) 35);
	        // 填单日期(A2-B2)
	        Cell cell2A = row2.createCell(0);
	        cell2A.setCellValue("填单日期:" + formattedDate);
	        cell2A.setCellStyle(row2Style);
	        sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 1));
	        // 项目号(C2-F2)
	        Cell cell2C = row2.createCell(2);
	        cell2C.setCellValue("项目号:" + data.get(0).getProjectNumber().substring(0, data.get(0).getProjectNumber().length() - 2));
	        cell2C.setCellStyle(row2Style);
	        sheet.addMergedRegion(new CellRangeAddress(1, 1, 2, 5));
	        // 交付批次(G2-J2)
	        Cell cell2G = row2.createCell(6);
	        cell2G.setCellValue("交付批次:" + data.get(0).getDeliveryBatch());
	        cell2G.setCellStyle(row2Style);
	        sheet.addMergedRegion(new CellRangeAddress(1, 1, 6, 9));
	        // 单号(K2-N2)
	        Cell cell2K = row2.createCell(10);
	        cell2K.setCellValue("单号:" + data.get(0).getInputNo());
	        cell2K.setCellStyle(row2Style);
	        sheet.addMergedRegion(new CellRangeAddress(1, 1, 10, 13));
	
	        // 第3行:表头(序号、日期、所属车间...)
	        Row row3 = sheet.createRow(2);
	        row3.setHeightInPoints((short) 50);
	        String[] headers = {"序号", "日期", "所属车间", "项目号", "机台", "模号", "任务包编码", "任务包名称", "入库数量", "实收数", "班", "组", "组员", "备注"};
	        for (int i = 0; i < headers.length; i++) {
	            Cell cell = row3.createCell(i);
	            cell.setCellValue(headers[i]);
	            cell.setCellStyle(headerStyle);
	        }
	
	        // 第4行及以后:数据行
	        for (int i = 0; i < data.size(); i++) {
	            StockInEntity item = data.get(i);
	            Row row = sheet.createRow(3 + i);
	            row.setHeightInPoints((short) 45);
	
	            // 序号
	            Cell cell0 = row.createCell(0);
	            cell0.setCellValue(i + 1);
	            cell0.setCellStyle(contentStyle);
	
	            // 日期
	            Cell cell11 = row.createCell(1);
	            cell11.setCellValue(item.getCreateTime().format(formatter));
	            cell11.setCellStyle(contentStyle);
	
	            // 所属车间
	            Cell cell2 = row.createCell(2);
	            cell2.setCellValue(item.getReceiveDeptName());
	            cell2.setCellStyle(contentStyle);
	
	            // 项目号
	            Cell cell3 = row.createCell(3);
	            cell3.setCellValue(item.getProjectNumber());
	            cell3.setCellStyle(contentStyle);
	
	            // 机台
	            Cell cell4 = row.createCell(4);
	            cell4.setCellValue(item.getMachine());
	            cell4.setCellStyle(contentStyle);
	
	            // 模号
	            Cell cell5 = row.createCell(5);
	            cell5.setCellValue(item.getModelNo());
	            cell5.setCellStyle(contentStyle);
	
	            // 任务包编码
	            Cell cell6 = row.createCell(6);
	            cell6.setCellValue(item.getMaterialCode());
	            cell6.setCellStyle(contentStyle);
	
	            // 任务包名称
	            Cell cell7 = row.createCell(7);
	            cell7.setCellValue(item.getMaterialName());
	            cell7.setCellStyle(contentStyle);
	
	            // 入库数量
	            Cell cell8 = row.createCell(8);
	            cell8.setCellValue(String.valueOf(item.getQty()));
	            cell8.setCellStyle(contentStyle);
	
	            // 实收数
	            Cell cell9 = row.createCell(9);
	            cell9.setCellValue(String.valueOf(item.getQty()));
	            cell9.setCellStyle(contentStyle);
	
	            // 班、组、组员、备注(示例中为空,可根据实际数据填充)
	            for (int j = 10; j < 14; j++) {
	                Cell cell = row.createCell(j);
	                cell.setCellStyle(contentStyle);
	            }
	        }
	    }
    }

2、使用示例(Web 场景)

在 Controller 中调用工具类,导出 Excel:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;

@RestController
public class ExportController {

    /**
     * 生产入库单导出
     */
    @PostMapping("/exportProInput")
    public void exportProInput(HttpServletResponse response, InputQueryDTO input) {
        List<StockInEntity> list = baseService.selectProInput(input);
        PaAssert.isError(CollUtil.isEmpty(list), "导出数据不能为空");
        try {
            // 设置响应头
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            String fileName = URLEncoder.encode("入库单.xlsx", "UTF-8");
            response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
            // 导出Excel
            OutputStream outputStream = response.getOutputStream();
            baseService.exportWarehouseInExcel(outputStream, list);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
            throw new BaseException("导出失败");
        }
    }

    // 模拟数据(实际从数据库查询)
    private List<StockInEntity> getWarehouseInData() {
        // 示例数据,实际替换为业务数据
        StockInEntity= new StockInEntity();
        item1.setProjectNo("GPC25061095B2");
        item1.setDate("2025/12/16");
        item1.setWorkshop("精密装配");
        item1.setMachine("J20-04");
        item1.setModel("5400002400");
        item1.setTaskCode("导向压销部件2");
        item1.setTaskName("导向压销部件2");
        item1.setInQty(2);
        item1.setActualQty(2);

        // 可添加更多数据...
        return List.of(item1, item1); // 示例中重复添加,实际替换为真实数据
    }
}

四、导出效果

总结

  1. Sheet 分组逻辑:通过projectNo.substring(0, projectNo.length() - 2)截取项目号前缀,实现同一前缀的记录分到同一个 Sheet。
  2. 格式还原:通过单元格合并、样式设置(背景色、边框、对齐方式)还原示例中的Excel 格式。
  3. 动态数据:代码中 “填单日期、交付批次、单号” 为固定值,实际应从业务数据中动态获取(可在实体类中添加对应字段)。

到此这篇关于Java实现Excel导出的全部流程(多Sheet、复杂格式)的文章就介绍到这了,更多相关Java实现Excel导出内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring的Ioc模拟实现详细介绍

    Spring的Ioc模拟实现详细介绍

    这篇文章主要介绍了Spring的Ioc模拟实现详细介绍,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • Java 数据结构链表操作实现代码

    Java 数据结构链表操作实现代码

    这篇文章主要介绍了Java 数据结构链表操作的相关资料,并附实例代码,需要的朋友可以参考下
    2016-10-10
  • 基于Java并发容器ConcurrentHashMap#put方法解析

    基于Java并发容器ConcurrentHashMap#put方法解析

    下面小编就为大家带来一篇基于Java并发容器ConcurrentHashMap#put方法解析。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • 老生常谈Log4j和Log4j2的区别(推荐)

    老生常谈Log4j和Log4j2的区别(推荐)

    下面小编就为大家带来老生常谈Log4j和Log4j2的区别(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • 详解Java线性结构中的链表

    详解Java线性结构中的链表

    除了一些算法之外,我们还有掌握一些常见的数据结构,比如数组、链表、栈、队列、树等结构,所以接下来就给大家详细讲解一下线性结构中的链表,需要的朋友可以参考下
    2023-07-07
  • SpringBoot安全策略开发之集成数据传输加密

    SpringBoot安全策略开发之集成数据传输加密

    这篇文章主要介绍了SpringBoot集成数据传输加密,近期在对开发框架安全策略方面进行升级优化,提供一些通用场景的解决方案,本文针对前后端数据传输加密进行简单的分享
    2023-01-01
  • java工具类static静态方法读取yml配置过程

    java工具类static静态方法读取yml配置过程

    文章介绍了在工具类中获取YAML配置时遇到的问题,由于变量是静态的,而Spring加载静态方法比IOC容器早,导致无法直接使用@Value注解读取YAML配置,从而读取结果为null
    2024-11-11
  • springboot RESTful以及参数注解的使用方式

    springboot RESTful以及参数注解的使用方式

    这篇文章主要介绍了springboot RESTful以及参数注解的使用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • 使用@Autowired注解有错误提示的解决

    使用@Autowired注解有错误提示的解决

    这篇文章主要介绍了使用@Autowired注解有错误提示的解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java Swing监听器的原理及使用方法举例

    Java Swing监听器的原理及使用方法举例

    swing是一个比较老的技术了,我觉得学习它还是很有必要的,也比较容易激发学习的兴趣,这篇文章主要介绍了Java Swing监听器的原理及使用方法的相关资料,需要的朋友可以参考下
    2025-08-08

最新评论