java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot中Excel处理指南

SpringBoot中Excel处理完全指南分享

作者:全栈凯哥

这篇文章主要介绍了SpringBoot中Excel处理完全指南,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

SpringBoot中Excel处理指南

1. Excel处理基础知识

1.1 为什么需要在应用中处理Excel文件?

在企业应用开发中,Excel文件处理是一个非常常见的需求,主要用于以下场景:

1.2 Java中的Excel处理库介绍

Java中处理Excel文件的主要库有以下几种:

1.2.1 Apache POI

Apache POI是Java中使用最广泛的Excel处理库,提供了全面的API来创建、读取和修改Office文档。

优点

缺点

1.2.2 EasyExcel

EasyExcel是阿里巴巴开源的Excel处理库,基于POI,但做了大量优化。

优点

缺点

1.2.3 JExcel

JExcel是另一个处理Excel的Java库。

优点

缺点

1.2.4 Apache POI SXSSF

SXSSF是POI提供的一种流式处理模式,专为处理大型Excel文件设计。

优点

缺点

1.3 Spring Boot中集成Excel处理

Spring Boot本身不提供Excel处理功能,但可以轻松集成上述各种Excel处理库。本指南将主要介绍:

  1. 如何在Spring Boot项目中集成Apache POI和EasyExcel
  2. 如何实现Excel导入导出的常见功能
  3. 如何处理常见问题和优化性能

2. 在Spring Boot中集成Excel处理库

2.1 集成Apache POI

2.1.1 添加依赖

pom.xml文件中添加以下依赖:

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

如果使用Gradle,在build.gradle中添加:

implementation 'org.apache.poi:poi:5.2.3'
implementation 'org.apache.poi:poi-ooxml:5.2.3'
2.1.2 创建基本配置类

创建一个配置类来处理Excel相关的配置:

package com.example.excel.config;

import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

@Configuration
public class ExcelConfig {
    
    @Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setMaxUploadSize(10485760); // 设置上传文件最大为10MB
        return resolver;
    }
}

2.2 集成EasyExcel

2.2.1 添加依赖

pom.xml文件中添加以下依赖:

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

如果使用Gradle,在build.gradle中添加:

implementation 'com.alibaba:easyexcel:3.2.1'
2.2.2 创建配置类
package com.example.excel.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

@Configuration
public class EasyExcelConfig {
    
    @Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setMaxUploadSize(10485760); // 设置上传文件最大为10MB
        return resolver;
    }
}

3. 使用Apache POI读取Excel文件

3.1 创建数据模型

首先,创建一个模型类来映射Excel中的数据:

package com.example.excel.model;

import lombok.Data;

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private String department;
}

3.2 创建Excel读取服务

创建一个服务类来处理Excel文件读取:

package com.example.excel.service;

import com.example.excel.model.User;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

@Service
public class ExcelService {

    public List<User> readUsersFromExcel(MultipartFile file) throws IOException {
        List<User> userList = new ArrayList<>();
        
        // 获取工作簿
        try (InputStream inputStream = file.getInputStream()) {
            Workbook workbook = WorkbookFactory.create(inputStream);
            
            // 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);
            
            // 跳过标题行
            Iterator<Row> rowIterator = sheet.rowIterator();
            if (rowIterator.hasNext()) {
                rowIterator.next(); // 跳过标题行
            }
            
            // 遍历数据行
            while (rowIterator.hasNext()) {
                Row row = rowIterator.next();
                User user = new User();
                
                // 读取单元格数据
                user.setId((long) row.getCell(0, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).getNumericCellValue());
                user.setName(getCellValueAsString(row.getCell(1)));
                user.setAge((int) row.getCell(2, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).getNumericCellValue());
                user.setEmail(getCellValueAsString(row.getCell(3)));
                user.setDepartment(getCellValueAsString(row.getCell(4)));
                
                userList.add(user);
            }
            
            workbook.close();
        }
        
        return userList;
    }
    
    // 获取单元格的字符串值
    private String getCellValueAsString(Cell cell) {
        if (cell == null) {
            return "";
        }
        
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    return cell.getDateCellValue().toString();
                } else {
                    return String.valueOf((int) cell.getNumericCellValue());
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                return cell.getCellFormula();
            default:
                return "";
        }
    }
}

3.3 创建Controller处理Excel上传

创建一个Controller来处理Excel文件上传:

package com.example.excel.controller;

import com.example.excel.model.User;
import com.example.excel.service.ExcelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;

@RestController
@RequestMapping("/api/excel")
public class ExcelController {

    @Autowired
    private ExcelService excelService;

    @PostMapping("/upload")
    public ResponseEntity<List<User>> uploadExcel(@RequestParam("file") MultipartFile file) {
        try {
            List<User> users = excelService.readUsersFromExcel(file);
            return ResponseEntity.ok(users);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseEntity.badRequest().build();
        }
    }
}

3.4 创建HTML上传页面

src/main/resources/templates目录下创建upload.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Excel上传</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="external nofollow"  rel="external nofollow" >
</head>
<body>
    <div class="container mt-5">
        <div class="card">
            <div class="card-header">
                <h3>上传Excel文件</h3>
            </div>
            <div class="card-body">
                <form id="uploadForm" enctype="multipart/form-data">
                    <div class="form-group">
                        <label for="file">选择Excel文件:</label>
                        <input type="file" class="form-control-file" id="file" name="file" accept=".xls,.xlsx">
                    </div>
                    <button type="button" class="btn btn-primary" onclick="uploadExcel()">上传</button>
                </form>
                
                <div class="mt-4">
                    <h4>上传结果:</h4>
                    <div id="resultContainer"></div>
                </div>
            </div>
        </div>
    </div>
    
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script>
        function uploadExcel() {
            var formData = new FormData(document.getElementById('uploadForm'));
            
            $.ajax({
                url: '/api/excel/upload',
                type: 'POST',
                data: formData,
                processData: false,
                contentType: false,
                success: function(response) {
                    var resultHtml = '<table class="table table-striped">' +
                                    '<thead><tr><th>ID</th><th>姓名</th><th>年龄</th><th>邮箱</th><th>部门</th></tr></thead>' +
                                    '<tbody>';
                    
                    for (var i = 0; i < response.length; i++) {
                        var user = response[i];
                        resultHtml += '<tr>' +
                                     '<td>' + user.id + '</td>' +
                                     '<td>' + user.name + '</td>' +
                                     '<td>' + user.age + '</td>' +
                                     '<td>' + user.email + '</td>' +
                                     '<td>' + user.department + '</td>' +
                                     '</tr>';
                    }
                    
                    resultHtml += '</tbody></table>';
                    
                    $('#resultContainer').html(resultHtml);
                },
                error: function(error) {
                    $('#resultContainer').html('<div class="alert alert-danger">上传失败: ' + error.responseText + '</div>');
                }
            });
        }
    </script>
</body>
</html>

3.5 处理更复杂的Excel结构

在实际应用中,Excel结构可能更复杂,如多个工作表、合并单元格、公式等。以下是处理这些情况的示例:

public List<Department> readComplexExcel(MultipartFile file) throws IOException {
    List<Department> departments = new ArrayList<>();
    
    try (InputStream inputStream = file.getInputStream()) {
        Workbook workbook = WorkbookFactory.create(inputStream);
        
        // 读取部门信息(第一个工作表)
        Sheet departmentSheet = workbook.getSheetAt(0);
        for (int i = 1; i <= departmentSheet.getLastRowNum(); i++) {
            Row row = departmentSheet.getRow(i);
            if (row == null) continue;
            
            Department department = new Department();
            department.setId((long) row.getCell(0).getNumericCellValue());
            department.setName(row.getCell(1).getStringCellValue());
            department.setManager(row.getCell(2).getStringCellValue());
            department.setEmployees(new ArrayList<>());
            
            departments.add(department);
        }
        
        // 读取员工信息(第二个工作表)
        Sheet employeeSheet = workbook.getSheetAt(1);
        for (int i = 1; i <= employeeSheet.getLastRowNum(); i++) {
            Row row = employeeSheet.getRow(i);
            if (row == null) continue;
            
            User employee = new User();
            employee.setId((long) row.getCell(0).getNumericCellValue());
            employee.setName(row.getCell(1).getStringCellValue());
            employee.setAge((int) row.getCell(2).getNumericCellValue());
            employee.setEmail(row.getCell(3).getStringCellValue());
            
            // 获取部门ID并关联到相应部门
            long departmentId = (long) row.getCell(4).getNumericCellValue();
            for (Department dept : departments) {
                if (dept.getId() == departmentId) {
                    dept.getEmployees().add(employee);
                    break;
                }
            }
        }
        
        workbook.close();
    }
    
    return departments;
}

4. 使用Apache POI创建和导出Excel文件

4.1 创建基本Excel文件

以下是一个创建简单Excel文件的示例:

package com.example.excel.service;

import com.example.excel.model.User;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

@Service
public class ExcelExportService {

    public ByteArrayInputStream exportUsersToExcel(List<User> users) throws IOException {
        try (Workbook workbook = new XSSFWorkbook()) {
            // 创建工作表
            Sheet sheet = workbook.createSheet("用户数据");
            
            // 创建表头样式
            Font headerFont = workbook.createFont();
            headerFont.setBold(true);
            headerFont.setColor(IndexedColors.BLUE.getIndex());
            
            CellStyle headerCellStyle = workbook.createCellStyle();
            headerCellStyle.setFont(headerFont);
            headerCellStyle.setFillForegroundColor(IndexedColors.LIGHT_YELLOW.getIndex());
            headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            headerCellStyle.setBorderBottom(BorderStyle.THIN);
            headerCellStyle.setBorderTop(BorderStyle.THIN);
            headerCellStyle.setBorderRight(BorderStyle.THIN);
            headerCellStyle.setBorderLeft(BorderStyle.THIN);
            
            // 创建表头行
            Row headerRow = sheet.createRow(0);
            
            // 创建表头单元格
            Cell cell0 = headerRow.createCell(0);
            cell0.setCellValue("ID");
            cell0.setCellStyle(headerCellStyle);
            
            Cell cell1 = headerRow.createCell(1);
            cell1.setCellValue("姓名");
            cell1.setCellStyle(headerCellStyle);
            
            Cell cell2 = headerRow.createCell(2);
            cell2.setCellValue("年龄");
            cell2.setCellStyle(headerCellStyle);
            
            Cell cell3 = headerRow.createCell(3);
            cell3.setCellValue("邮箱");
            cell3.setCellStyle(headerCellStyle);
            
            Cell cell4 = headerRow.createCell(4);
            cell4.setCellValue("部门");
            cell4.setCellStyle(headerCellStyle);
            
            // 设置数据单元格样式
            CellStyle dataCellStyle = workbook.createCellStyle();
            dataCellStyle.setBorderBottom(BorderStyle.THIN);
            dataCellStyle.setBorderTop(BorderStyle.THIN);
            dataCellStyle.setBorderRight(BorderStyle.THIN);
            dataCellStyle.setBorderLeft(BorderStyle.THIN);
            
            // 创建数据行
            int rowIdx = 1;
            for (User user : users) {
                Row row = sheet.createRow(rowIdx++);
                
                Cell idCell = row.createCell(0);
                idCell.setCellValue(user.getId());
                idCell.setCellStyle(dataCellStyle);
                
                Cell nameCell = row.createCell(1);
                nameCell.setCellValue(user.getName());
                nameCell.setCellStyle(dataCellStyle);
                
                Cell ageCell = row.createCell(2);
                ageCell.setCellValue(user.getAge());
                ageCell.setCellStyle(dataCellStyle);
                
                Cell emailCell = row.createCell(3);
                emailCell.setCellValue(user.getEmail());
                emailCell.setCellStyle(dataCellStyle);
                
                Cell deptCell = row.createCell(4);
                deptCell.setCellValue(user.getDepartment());
                deptCell.setCellStyle(dataCellStyle);
            }
            
            // 自动调整列宽
            for (int i = 0; i < 5; i++) {
                sheet.autoSizeColumn(i);
            }
            
            // 写入ByteArrayOutputStream
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            workbook.write(outputStream);
            
            return new ByteArrayInputStream(outputStream.toByteArray());
        }
    }
}

4.2 创建导出控制器

package com.example.excel.controller;

import com.example.excel.model.User;
import com.example.excel.service.ExcelExportService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/api/excel")
public class ExcelExportController {

    @Autowired
    private ExcelExportService excelExportService;

    @GetMapping("/export")
    public ResponseEntity<InputStreamResource> exportUsers() throws IOException {
        // 生成示例数据
        List<User> users = getTestUsers();
        
        // 生成Excel文件
        ByteArrayInputStream in = excelExportService.exportUsersToExcel(users);
        
        // 设置HTTP头
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attachment; filename=users.xlsx");
        
        // 返回Excel文件
        return ResponseEntity
                .ok()
                .headers(headers)
                .contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
                .body(new InputStreamResource(in));
    }
    
    // 生成测试用户数据
    private List<User> getTestUsers() {
        List<User> users = new ArrayList<>();
        
        User user1 = new User();
        user1.setId(1L);
        user1.setName("张三");
        user1.setAge(28);
        user1.setEmail("zhangsan@example.com");
        user1.setDepartment("研发部");
        users.add(user1);
        
        User user2 = new User();
        user2.setId(2L);
        user2.setName("李四");
        user2.setAge(32);
        user2.setEmail("lisi@example.com");
        user2.setDepartment("市场部");
        users.add(user2);
        
        User user3 = new User();
        user3.setId(3L);
        user3.setName("王五");
        user3.setAge(45);
        user3.setEmail("wangwu@example.com");
        user3.setDepartment("行政部");
        users.add(user3);
        
        User user4 = new User();
        user4.setId(4L);
        user4.setName("赵六");
        user4.setAge(36);
        user4.setEmail("zhaoliu@example.com");
        user4.setDepartment("财务部");
        users.add(user4);
        
        User user5 = new User();
        user5.setId(5L);
        user5.setName("钱七");
        user5.setAge(29);
        user5.setEmail("qianqi@example.com");
        user5.setDepartment("人力资源部");
        users.add(user5);
        
        return users;
    }
}

4.3 创建导出页面

src/main/resources/templates目录下创建export.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Excel导出</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="external nofollow"  rel="external nofollow" >
</head>
<body>
    <div class="container mt-5">
        <div class="card">
            <div class="card-header">
                <h3>导出Excel文件</h3>
            </div>
            <div class="card-body">
                <p>点击下面的按钮导出用户数据到Excel文件:</p>
                <a href="/api/excel/export" rel="external nofollow"  class="btn btn-primary">导出用户数据</a>
            </div>
        </div>
    </div>
</body>
</html>

4.4 创建复杂的Excel文件

以下是一个创建更复杂Excel文件的示例,包含多个工作表、合并单元格、公式等:

public ByteArrayInputStream exportComplexExcel(List<Department> departments) throws IOException {
    try (Workbook workbook = new XSSFWorkbook()) {
        // 创建字体和样式
        Font headerFont = workbook.createFont();
        headerFont.setBold(true);
        headerFont.setFontHeightInPoints((short) 14);
        
        CellStyle headerStyle = workbook.createCellStyle();
        headerStyle.setFont(headerFont);
        headerStyle.setFillForegroundColor(IndexedColors.LIGHT_BLUE.getIndex());
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        headerStyle.setAlignment(HorizontalAlignment.CENTER);
        
        CellStyle titleStyle = workbook.createCellStyle();
        Font titleFont = workbook.createFont();
        titleFont.setBold(true);
        titleFont.setFontHeightInPoints((short) 16);
        titleStyle.setFont(titleFont);
        titleStyle.setAlignment(HorizontalAlignment.CENTER);
        
        // 创建汇总表
        Sheet summarySheet = workbook.createSheet("部门汇总");
        
        // 创建标题行
        Row titleRow = summarySheet.createRow(0);
        Cell titleCell = titleRow.createCell(0);
        titleCell.setCellValue("公司部门人员统计");
        titleCell.setCellStyle(titleStyle);
        
        // 合并标题单元格
        summarySheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 3));
        
        // 创建表头
        Row headerRow = summarySheet.createRow(1);
        String[] headers = {"部门ID", "部门名称", "部门经理", "员工数量"};
        
        for (int i = 0; i < headers.length; i++) {
            Cell cell = headerRow.createCell(i);
            cell.setCellValue(headers[i]);
            cell.setCellStyle(headerStyle);
        }
        
        // 填充部门数据
        int rowIdx = 2;
        int totalEmployees = 0;
        
        for (Department dept : departments) {
            Row row = summarySheet.createRow(rowIdx++);
            
            row.createCell(0).setCellValue(dept.getId());
            row.createCell(1).setCellValue(dept.getName());
            row.createCell(2).setCellValue(dept.getManager());
            row.createCell(3).setCellValue(dept.getEmployees().size());
            
            totalEmployees += dept.getEmployees().size();
            
            // 为每个部门创建单独的工作表
            Sheet deptSheet = workbook.createSheet(dept.getName());
            
            // 创建部门表头
            Row deptHeaderRow = deptSheet.createRow(0);
            Cell deptTitleCell = deptHeaderRow.createCell(0);
            deptTitleCell.setCellValue(dept.getName() + " - 员工列表");
            deptTitleCell.setCellStyle(titleStyle);
            deptSheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 4));
            
            // 员工表头
            Row empHeaderRow = deptSheet.createRow(1);
            String[] empHeaders = {"员工ID", "姓名", "年龄", "邮箱", "入职年限"};
            
            for (int i = 0; i < empHeaders.length; i++) {
                Cell cell = empHeaderRow.createCell(i);
                cell.setCellValue(empHeaders[i]);
                cell.setCellStyle(headerStyle);
            }
            
            // 填充员工数据
            int empRowIdx = 2;
            for (User emp : dept.getEmployees()) {
                Row empRow = deptSheet.createRow(empRowIdx++);
                
                empRow.createCell(0).setCellValue(emp.getId());
                empRow.createCell(1).setCellValue(emp.getName());
                empRow.createCell(2).setCellValue(emp.getAge());
                empRow.createCell(3).setCellValue(emp.getEmail());
                
                // 使用公式计算入职年限(假设年龄减去25)
                Cell tenureCell = empRow.createCell(4);
                tenureCell.setCellFormula("C" + empRowIdx + "-25");
            }
            
            // 自动调整列宽
            for (int i = 0; i < 5; i++) {
                deptSheet.autoSizeColumn(i);
            }
        }
        
        // 创建总计行
        Row totalRow = summarySheet.createRow(rowIdx);
        Cell totalLabelCell = totalRow.createCell(0);
        totalLabelCell.setCellValue("总计");
        totalLabelCell.setCellStyle(headerStyle);
        
        // 合并总计标签单元格
        summarySheet.addMergedRegion(new CellRangeAddress(rowIdx, rowIdx, 0, 2));
        
        Cell totalValueCell = totalRow.createCell(3);
        totalValueCell.setCellValue(totalEmployees);
        totalValueCell.setCellStyle(headerStyle);
        
        // 自动调整列宽
        for (int i = 0; i < 4; i++) {
            summarySheet.autoSizeColumn(i);
        }
        
        // 添加图表
        XSSFSheet chartSheet = (XSSFSheet) workbook.createSheet("部门统计图");
        
        // 复制部门数据到图表数据表
        Row chartHeaderRow = chartSheet.createRow(0);
        chartHeaderRow.createCell(0).setCellValue("部门");
        chartHeaderRow.createCell(1).setCellValue("员工数");
        
        int chartRowIdx = 1;
        for (Department dept : departments) {
            Row row = chartSheet.createRow(chartRowIdx++);
            row.createCell(0).setCellValue(dept.getName());
            row.createCell(1).setCellValue(dept.getEmployees().size());
        }
        
        // 创建图表和数据序列
        XSSFDrawing drawing = chartSheet.createDrawingPatriarch();
        XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 4, 0, 15, 15);
        
        XSSFChart chart = drawing.createChart(anchor);
        chart.setTitleText("部门人员分布");
        chart.setTitleOverlay(false);
        
        XDDFChartLegend legend = chart.getOrAddLegend();
        legend.setPosition(LegendPosition.RIGHT);
        
        // X轴和Y轴
        XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
        bottomAxis.setTitle("部门");
        XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
        leftAxis.setTitle("员工数");
        
        // 创建数据源
        XDDFDataSource<String> departments = XDDFDataSourcesFactory.fromStringCellRange(
                chartSheet, new CellRangeAddress(1, chartRowIdx - 1, 0, 0));
        
        XDDFNumericalDataSource<Double> values = XDDFDataSourcesFactory.fromNumericCellRange(
                chartSheet, new CellRangeAddress(1, chartRowIdx - 1, 1, 1));
        
        // 创建柱状图
        XDDFBarChartData barChart = (XDDFBarChartData) chart.createData(
                ChartTypes.BAR, bottomAxis, leftAxis);
        barChart.setVaryColors(true);
        
        XDDFBarChartData.Series series = (XDDFBarChartData.Series) barChart.addSeries(departments, values);
        series.setTitle("员工数", null);
        
        chart.plot(barChart);
        
        // 写入ByteArrayOutputStream
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        workbook.write(outputStream);
        
        return new ByteArrayInputStream(outputStream.toByteArray());
    }
}

注意:上面的图表代码需要添加以下依赖:

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

4.5 使用模板导出Excel

在某些场景下,我们需要基于预定义的Excel模板生成文件,以下是一个示例:

public ByteArrayInputStream exportFromTemplate(List<User> users) throws IOException {
    // 加载模板文件
    try (InputStream templateStream = getClass().getResourceAsStream("/templates/user_template.xlsx");
         Workbook workbook = WorkbookFactory.create(templateStream)) {
        
        Sheet sheet = workbook.getSheetAt(0);
        
        // 从第二行开始填充数据(第一行是表头)
        int rowIdx = 1;
        for (User user : users) {
            Row row = sheet.createRow(rowIdx++);
            
            row.createCell(0).setCellValue(user.getId());
            row.createCell(1).setCellValue(user.getName());
            row.createCell(2).setCellValue(user.getAge());
            row.createCell(3).setCellValue(user.getEmail());
            row.createCell(4).setCellValue(user.getDepartment());
        }
        
        // 更新模板中的日期单元格(假设在A1位置)
        Row headerRow = sheet.getRow(0);
        if (headerRow.getCell(6) != null) {
            Cell dateCell = headerRow.getCell(6);
            dateCell.setCellValue(new Date());
        }
        
        // 自动调整列宽
        for (int i = 0; i < 5; i++) {
            sheet.autoSizeColumn(i);
        }
        
        // 写入ByteArrayOutputStream
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        workbook.write(outputStream);
        
        return new ByteArrayInputStream(outputStream.toByteArray());
    }
}

5. 使用EasyExcel处理Excel文件

EasyExcel是阿里巴巴开源的基于POI的Excel处理工具,相比原生POI,它提供了更简洁的API,并且在处理大文件时有明显的性能优势。

5.1 使用EasyExcel读取Excel

5.1.1 创建数据模型

使用EasyExcel时,通常使用注解来映射Excel列:

package com.example.excel.model;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import lombok.Data;

import java.util.Date;

@Data
public class Employee {
    
    @ExcelProperty("员工ID")
    private Long id;
    
    @ExcelProperty("姓名")
    private String name;
    
    @ExcelProperty("年龄")
    private Integer age;
    
    @ExcelProperty("邮箱")
    private String email;
    
    @ExcelProperty("部门")
    private String department;
    
    @ExcelProperty("入职日期")
    @DateTimeFormat("yyyy-MM-dd")
    private Date hireDate;
    
    @ExcelProperty("薪资")
    private Double salary;
}
5.1.2 创建读取监听器

EasyExcel采用事件模式读取Excel,需要创建一个监听器来处理读取的数据:

package com.example.excel.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.example.excel.model.Employee;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

@Slf4j
public class EmployeeReadListener extends AnalysisEventListener<Employee> {

    /**
     * 用于暂存读取的数据
     */
    private List<Employee> employeeList = new ArrayList<>();
    
    /**
     * 每读取一行数据就会调用一次invoke方法
     */
    @Override
    public void invoke(Employee employee, AnalysisContext context) {
        log.info("读取到一条数据: {}", employee);
        employeeList.add(employee);
        
        // 达到BATCH_COUNT时,需要存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (employeeList.size() >= 5000) {
            saveData();
            // 清理内存
            employeeList.clear();
        }
    }
    
    /**
     * 所有数据解析完成后调用此方法
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 确保最后一批数据被保存
        saveData();
        log.info("所有数据解析完成!");
    }
    
    /**
     * 保存数据,这里只是打印,实际应用中可以将数据存入数据库
     */
    private void saveData() {
        log.info("{}条数据,开始保存数据库!", employeeList.size());
        // 这里可以调用持久层完成数据入库
        log.info("存储数据库成功!");
    }
    
    /**
     * 获取读取到的数据
     */
    public List<Employee> getEmployeeList() {
        return employeeList;
    }
}
5.1.3 创建Excel读取服务
package com.example.excel.service;

import com.alibaba.excel.EasyExcel;
import com.example.excel.listener.EmployeeReadListener;
import com.example.excel.model.Employee;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;

@Slf4j
@Service
public class EasyExcelService {

    public List<Employee> readEmployeeData(MultipartFile file) throws IOException {
        EmployeeReadListener listener = new EmployeeReadListener();
        
        EasyExcel.read(file.getInputStream(), Employee.class, listener).sheet().doRead();
        
        return listener.getEmployeeList();
    }
}
5.1.4 创建Controller
package com.example.excel.controller;

import com.example.excel.model.Employee;
import com.example.excel.service.EasyExcelService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/api/easyexcel")
public class EasyExcelController {

    @Autowired
    private EasyExcelService easyExcelService;
    
    @PostMapping("/upload")
    public ResponseEntity<List<Employee>> uploadExcel(@RequestParam("file") MultipartFile file) {
        try {
            List<Employee> employees = easyExcelService.readEmployeeData(file);
            return ResponseEntity.ok(employees);
        } catch (IOException e) {
            log.error("Excel读取失败", e);
            return ResponseEntity.badRequest().build();
        }
    }
}

5.2 使用EasyExcel导出Excel

5.2.1 简单导出示例
package com.example.excel.service;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.example.excel.model.Employee;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

@Service
public class EasyExcelExportService {

    /**
     * 导出员工数据到Excel文件
     */
    public void exportEmployees(List<Employee> employees, OutputStream outputStream) {
        EasyExcel.write(outputStream, Employee.class)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())  // 自动调整列宽
                .sheet("员工数据")
                .doWrite(employees);
    }
    
    /**
     * 导出员工数据到指定文件
     */
    public void exportEmployeesToFile(List<Employee> employees, String fileName) throws IOException {
        // 确保目录存在
        File file = new File(fileName);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        
        EasyExcel.write(fileName, Employee.class)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .sheet("员工数据")
                .doWrite(employees);
    }
    
    /**
     * 导出多个Sheet的Excel
     */
    public void exportMultipleSheets(List<List<Employee>> departmentEmployees, 
                                    List<String> sheetNames, 
                                    OutputStream outputStream) {
        // 创建ExcelWriter
        try (var excelWriter = EasyExcel.write(outputStream, Employee.class)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .build()) {
            
            // 同一个对象多个Sheet写入
            for (int i = 0; i < departmentEmployees.size(); i++) {
                // 获取Sheet名称
                String sheetName = i < sheetNames.size() ? sheetNames.get(i) : "Sheet" + (i + 1);
                
                // 创建新的Sheet
                var writeSheet = EasyExcel.writerSheet(i, sheetName).build();
                
                // 写入数据
                excelWriter.write(departmentEmployees.get(i), writeSheet);
            }
        }
    }
}
5.2.2 创建Controller
package com.example.excel.controller;

import com.example.excel.model.Employee;
import com.example.excel.service.EasyExcelExportService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

@RestController
@RequestMapping("/api/easyexcel")
public class EasyExcelExportController {

    @Autowired
    private EasyExcelExportService exportService;
    
    @GetMapping("/export")
    public void exportEmployees(HttpServletResponse response) throws IOException {
        // 设置响应内容
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        
        // 设置文件名
        String fileName = URLEncoder.encode("员工数据", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        
        // 获取测试数据
        List<Employee> employees = getTestEmployees();
        
        // 导出Excel
        exportService.exportEmployees(employees, response.getOutputStream());
    }
    
    @GetMapping("/export-multiple-sheets")
    public void exportMultipleSheets(HttpServletResponse response) throws IOException {
        // 设置响应内容
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        
        // 设置文件名
        String fileName = URLEncoder.encode("部门员工数据", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        
        // 获取测试数据 - 三个部门的员工
        List<List<Employee>> departmentEmployees = new ArrayList<>();
        departmentEmployees.add(getEmployeesByDepartment("研发部"));
        departmentEmployees.add(getEmployeesByDepartment("市场部"));
        departmentEmployees.add(getEmployeesByDepartment("行政部"));
        
        // Sheet名称
        List<String> sheetNames = Arrays.asList("研发部员工", "市场部员工", "行政部员工");
        
        // 导出Excel
        exportService.exportMultipleSheets(departmentEmployees, sheetNames, response.getOutputStream());
    }
    
    /**
     * 生成测试员工数据
     */
    private List<Employee> getTestEmployees() {
        List<Employee> employees = new ArrayList<>();
        
        // 添加测试数据
        for (int i = 1; i <= 10; i++) {
            Employee employee = new Employee();
            employee.setId((long) i);
            employee.setName("员工" + i);
            employee.setAge(20 + i);
            employee.setEmail("employee" + i + "@example.com");
            employee.setDepartment(i % 3 == 0 ? "研发部" : (i % 3 == 1 ? "市场部" : "行政部"));
            employee.setHireDate(new Date());
            employee.setSalary(5000.0 + i * 1000);
            
            employees.add(employee);
        }
        
        return employees;
    }
    
    /**
     * 根据部门获取员工
     */
    private List<Employee> getEmployeesByDepartment(String department) {
        List<Employee> allEmployees = getTestEmployees();
        List<Employee> departmentEmployees = new ArrayList<>();
        
        for (Employee employee : allEmployees) {
            if (department.equals(employee.getDepartment())) {
                departmentEmployees.add(employee);
            }
        }
        
        return departmentEmployees;
    }

    // ... 5.2.3 使用自定义样式和复杂表头

    /**
     * 导出自定义样式的Excel
     */
    public void exportWithCustomStyle(List<Employee> employees, OutputStream outputStream) {
        // 设置自定义拦截器来处理样式
        EasyExcel.write(outputStream, Employee.class)
                // 自动调整列宽
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                // 设置表头样式
                .registerWriteHandler(new AbstractRowHeightStyleStrategy() {
                    @Override
                    protected void setHeadColumnHeight(Row row, int relativeRowIndex) {
                        // 设置表头行高
                        row.setHeight((short) 500);
                    }

                    @Override
                    protected void setContentColumnHeight(Row row, int relativeRowIndex) {
                        // 设置内容行高
                        row.setHeight((short) 400);
                    }
                })
                // 设置单元格样式
                .registerWriteHandler(new CellWriteHandler() {
                    @Override
                    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, 
                                              Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
                        // 设置表头样式
                        if (isHead) {
                            Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
                            CellStyle style = workbook.createCellStyle();
                            Font font = workbook.createFont();
                            font.setBold(true);
                            font.setFontHeightInPoints((short) 12);
                            font.setColor(IndexedColors.WHITE.getIndex());
                            style.setFont(font);
                            style.setFillForegroundColor(IndexedColors.ROYAL_BLUE.getIndex());
                            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                            style.setAlignment(HorizontalAlignment.CENTER);
                            style.setVerticalAlignment(VerticalAlignment.CENTER);
                            cell.setCellStyle(style);
                        }
                    }

                    @Override
                    public void afterCellDataConverted(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) {
                            // 偶数行设置背景色
                            if (relativeRowIndex % 2 == 0) {
                                Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
                                CellStyle style = workbook.createCellStyle();
                                style.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());
                                style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                                style.setAlignment(HorizontalAlignment.CENTER);
                                style.setVerticalAlignment(VerticalAlignment.CENTER);
                                cell.setCellStyle(style);
                            }
                        }
                    }
                })
                .sheet("员工数据")
                .doWrite(employees);
    }

    /**
     * 导出复杂表头的Excel
     */
    public void exportWithComplexHead(List<Employee> employees, OutputStream outputStream) {
        // 构建复杂表头
        List<List<String>> head = new ArrayList<>();
        
        // 第一列 ID
        List<String> head1 = new ArrayList<>();
        head1.add("基本信息");
        head1.add("员工ID");
        head.add(head1);
        
        // 第二列 姓名
        List<String> head2 = new ArrayList<>();
        head2.add("基本信息");
        head2.add("姓名");
        head.add(head2);
        
        // 第三列 年龄
        List<String> head3 = new ArrayList<>();
        head3.add("基本信息");
        head3.add("年龄");
        head.add(head3);
        
        // 第四列 邮箱
        List<String> head4 = new ArrayList<>();
        head4.add("联系方式");
        head4.add("邮箱");
        head.add(head4);
        
        // 第五列 部门
        List<String> head5 = new ArrayList<>();
        head5.add("工作信息");
        head5.add("部门");
        head.add(head5);
        
        // 第六列 入职日期
        List<String> head6 = new ArrayList<>();
        head6.add("工作信息");
        head6.add("入职日期");
        head.add(head6);
        
        // 第七列 薪资
        List<String> head7 = new ArrayList<>();
        head7.add("薪资信息");
        head7.add("月薪(元)");
        head.add(head7);
        
        // 将数据转为List<List<Object>>格式
        List<List<Object>> dataList = new ArrayList<>();
        for (Employee employee : employees) {
            List<Object> data = new ArrayList<>();
            data.add(employee.getId());
            data.add(employee.getName());
            data.add(employee.getAge());
            data.add(employee.getEmail());
            data.add(employee.getDepartment());
            data.add(employee.getHireDate());
            data.add(employee.getSalary());
            dataList.add(data);
        }
        
        // 写入Excel
        EasyExcel.write(outputStream)
                .head(head)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .sheet("员工数据")
                .doWrite(dataList);
    }
}

6. 处理大型Excel文件的策略

6.1 使用Apache POI SXSSF模式

SXSSF(Streaming Xlsx Writer)是POI提供的流式写入方式,可以大大减少内存使用:

public void exportLargeExcel(String fileName, int rowCount) throws IOException {
    try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) { // 100表示内存中保留的行数
        Sheet sheet = workbook.createSheet("大数据");
        
        // 创建表头
        Row headerRow = sheet.createRow(0);
        for (int i = 0; i < 10; i++) {
            headerRow.createCell(i).setCellValue("列 " + (i + 1));
        }
        
        // 创建数据行
        for (int i = 0; i < rowCount; i++) {
            Row row = sheet.createRow(i + 1);
            for (int j = 0; j < 10; j++) {
                row.createCell(j).setCellValue("数据 " + (i + 1) + "-" + (j + 1));
            }
            
            // 每生成10000行清理一次临时文件
            if (i % 10000 == 0) {
                ((SXSSFSheet)sheet).flushRows();
            }
        }
        
        // 写入文件
        try (FileOutputStream outputStream = new FileOutputStream(fileName)) {
            workbook.write(outputStream);
        }
        
        // 清理临时文件
        workbook.dispose();
    }
}

注意事项:

  1. 使用完毕后一定要调用dispose()方法清理临时文件
  2. SXSSF仅支持写入操作,不支持读取
  3. 不支持某些高级特性(如合并单元格等)

6.2 使用EasyExcel处理大文件

EasyExcel在设计上就考虑了大文件处理,采用SAX方式逐行读取,内存占用小:

// 读取大文件
public void readLargeExcel(String fileName) {
    // 使用SAX方式读取
    EasyExcel.read(fileName, Employee.class, new EmployeeReadListener())
            .sheet()
            .doRead();
}

// 写入大文件
public void writeLargeExcel(String fileName, int batchSize) {
    // 分批获取数据
    try (ExcelWriter excelWriter = EasyExcel.write(fileName, Employee.class)
            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
            .build()) {
        
        // 获取WriteSheet对象
        WriteSheet writeSheet = EasyExcel.writerSheet("员工数据").build();
        
        // 模拟分批获取数据
        int totalCount = 100000; // 总数据量
        for (int i = 0; i < totalCount; i += batchSize) {
            // 获取当前批次数据
            List<Employee> data = getBatchData(i, Math.min(i + batchSize, totalCount));
            // 写入Excel
            excelWriter.write(data, writeSheet);
        }
    }
}

// 模拟分批获取数据
private List<Employee> getBatchData(int start, int end) {
    List<Employee> list = new ArrayList<>();
    for (int i = start; i < end; i++) {
        Employee employee = new Employee();
        employee.setId((long) i);
        employee.setName("员工" + i);
        employee.setAge(20 + (i % 20));
        employee.setEmail("employee" + i + "@example.com");
        employee.setDepartment(i % 3 == 0 ? "研发部" : (i % 3 == 1 ? "市场部" : "行政部"));
        employee.setHireDate(new Date());
        employee.setSalary(5000.0 + (i % 10) * 1000);
        
        list.add(employee);
    }
    return list;
}

6.3 使用CSV代替Excel

对于极大的数据集,考虑使用CSV格式替代Excel:

public void exportToCsv(List<Employee> employees, String fileName) throws IOException {
    try (FileWriter writer = new FileWriter(fileName);
         CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT
                 .withHeader("ID", "姓名", "年龄", "邮箱", "部门", "入职日期", "薪资"))) {
                 
        for (Employee employee : employees) {
            csvPrinter.printRecord(
                employee.getId(),
                employee.getName(),
                employee.getAge(),
                employee.getEmail(),
                employee.getDepartment(),
                employee.getHireDate(),
                employee.getSalary()
            );
        }
        
        csvPrinter.flush();
    }
}

注意:使用CSV需要添加依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-csv</artifactId>
    <version>1.9.0</version>
</dependency>

6.4 分页导出大型数据集

对于Web应用中需要导出的大型数据集,可以考虑分页导出:

@GetMapping("/export/paged")
public ResponseEntity<String> exportPaged() {
    // 生成唯一任务ID
    String taskId = UUID.randomUUID().toString();
    
    // 启动异步任务
    CompletableFuture.runAsync(() -> {
        try {
            // 导出文件路径
            String filePath = "/temp/" + taskId + ".xlsx";
            
            // 分页查询数据并写入Excel
            int pageSize = 1000;
            int totalPages = getTotalPages(pageSize);
            
            try (ExcelWriter excelWriter = EasyExcel.write(filePath, Employee.class)
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .build()) {
                
                WriteSheet writeSheet = EasyExcel.writerSheet("员工数据").build();
                
                // 分页导出
                for (int pageNum = 0; pageNum < totalPages; pageNum++) {
                    // 从数据库分页查询数据
                    List<Employee> pageData = getPageData(pageNum, pageSize);
                    // 写入Excel
                    excelWriter.write(pageData, writeSheet);
                    // 更新进度
                    updateExportProgress(taskId, (pageNum + 1) * 100 / totalPages);
                }
            }
            
            // 更新导出状态为完成
            updateExportStatus(taskId, "COMPLETED", filePath);
            
        } catch (Exception e) {
            // 更新导出状态为失败
            updateExportStatus(taskId, "FAILED", null);
        }
    });
    
    // 返回任务ID
    return ResponseEntity.ok(taskId);
}

@GetMapping("/export/status/{taskId}")
public ResponseEntity<Map<String, Object>> getExportStatus(@PathVariable String taskId) {
    // 获取任务状态
    Map<String, Object> status = getTaskStatus(taskId);
    return ResponseEntity.ok(status);
}

@GetMapping("/export/download/{taskId}")
public ResponseEntity<Resource> downloadExportedFile(@PathVariable String taskId) {
    // 获取导出文件路径
    String filePath = getExportedFilePath(taskId);
    
    if (filePath == null) {
        return ResponseEntity.notFound().build();
    }
    
    // 创建文件资源
    Resource resource = new FileSystemResource(filePath);
    
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, 
                    "attachment; filename=employee_data.xlsx")
            .contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
            .body(resource);
}

7. 实际应用场景和最佳实践

7.1 动态列导出

在某些业务场景中,需要根据用户选择动态确定导出的列:

public ByteArrayInputStream exportDynamicColumns(List<Employee> employees, List<String> selectedColumns) throws IOException {
    // 定义所有可能的列
    Map<String, String> allColumns = new HashMap<>();
    allColumns.put("id", "员工ID");
    allColumns.put("name", "姓名");
    allColumns.put("age", "年龄");
    allColumns.put("email", "邮箱");
    allColumns.put("department", "部门");
    allColumns.put("hireDate", "入职日期");
    allColumns.put("salary", "薪资");
    
    try (Workbook workbook = new XSSFWorkbook()) {
        Sheet sheet = workbook.createSheet("员工数据");
        
        // 创建表头行
        Row headerRow = sheet.createRow(0);
        
        // 设置表头样式
        CellStyle headerStyle = workbook.createCellStyle();
        Font headerFont = workbook.createFont();
        headerFont.setBold(true);
        headerStyle.setFont(headerFont);
        
        // 填充表头
        int colIdx = 0;
        for (String column : selectedColumns) {
            if (allColumns.containsKey(column)) {
                Cell cell = headerRow.createCell(colIdx++);
                cell.setCellValue(allColumns.get(column));
                cell.setCellStyle(headerStyle);
            }
        }
        
        // 填充数据
        int rowIdx = 1;
        for (Employee employee : employees) {
            Row row = sheet.createRow(rowIdx++);
            
            colIdx = 0;
            for (String column : selectedColumns) {
                Cell cell = row.createCell(colIdx++);
                
                // 根据列名设置单元格值
                switch (column) {
                    case "id":
                        cell.setCellValue(employee.getId());
                        break;
                    case "name":
                        cell.setCellValue(employee.getName());
                        break;
                    case "age":
                        cell.setCellValue(employee.getAge());
                        break;
                    case "email":
                        cell.setCellValue(employee.getEmail());
                        break;
                    case "department":
                        cell.setCellValue(employee.getDepartment());
                        break;
                    case "hireDate":
                        if (employee.getHireDate() != null) {
                            cell.setCellValue(employee.getHireDate());
                            
                            // 设置日期格式
                            CellStyle dateStyle = workbook.createCellStyle();
                            CreationHelper createHelper = workbook.getCreationHelper();
                            dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd"));
                            cell.setCellStyle(dateStyle);
                        }
                        break;
                    case "salary":
                        cell.setCellValue(employee.getSalary());
                        break;
                }
            }
        }
        
        // 自动调整列宽
        for (int i = 0; i < selectedColumns.size(); i++) {
            sheet.autoSizeColumn(i);
        }
        
        // 输出
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        workbook.write(outputStream);
        return new ByteArrayInputStream(outputStream.toByteArray());
    }
}

7.2 Excel模板填充

使用Freemarker或其他模板引擎生成Excel:

public ByteArrayInputStream fillTemplate(Map<String, Object> data) throws Exception {
    // 加载模板
    Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
    cfg.setClassLoaderForTemplateLoading(getClass().getClassLoader(), "templates");
    cfg.setDefaultEncoding("UTF-8");
    
    // 获取模板
    Template template = cfg.getTemplate("excel_template.ftl");
    
    // 输出目录
    File tempDir = new File(System.getProperty("java.io.tmpdir"));
    File tempFile = new File(tempDir, "temp_" + System.currentTimeMillis() + ".xlsx");
    
    // 填充模板
    try (Writer out = new FileWriter(tempFile)) {
        template.process(data, out);
    }
    
    // 读取填充后的文件
    try (FileInputStream fis = new FileInputStream(tempFile)) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = fis.read(buffer)) > -1) {
            baos.write(buffer, 0, len);
        }
        baos.flush();
        
        // 删除临时文件
        tempFile.delete();
        
        return new ByteArrayInputStream(baos.toByteArray());
    }
}

7.3 Excel文件校验

在导入Excel文件前进行数据校验:

public class ExcelValidationListener extends AnalysisEventListener<Employee> {
    
    private List<Employee> validEmployees = new ArrayList<>();
    private List<Map<String, Object>> errorRecords = new ArrayList<>();
    private int rowIndex = 1; // 从1开始,0是表头
    
    @Override
    public void invoke(Employee employee, AnalysisContext context) {
        rowIndex++;
        
        // 验证数据
        List<String> errors = validateEmployee(employee);
        
        if (errors.isEmpty()) {
            // 数据有效
            validEmployees.add(employee);
        } else {
            // 记录错误
            Map<String, Object> errorRecord = new HashMap<>();
            errorRecord.put("rowIndex", rowIndex);
            errorRecord.put("data", employee);
            errorRecord.put("errors", errors);
            errorRecords.add(errorRecord);
        }
    }
    
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 处理完成
    }
    
    // 验证员工数据
    private List<String> validateEmployee(Employee employee) {
        List<String> errors = new ArrayList<>();
        
        // 验证姓名
        if (employee.getName() == null || employee.getName().trim().isEmpty()) {
            errors.add("姓名不能为空");
        }
        
        // 验证年龄
        if (employee.getAge() == null) {
            errors.add("年龄不能为空");
        } else if (employee.getAge() < 18 || employee.getAge() > 65) {
            errors.add("年龄必须在18-65岁之间");
        }
        
        // 验证邮箱
        if (employee.getEmail() != null && !employee.getEmail().isEmpty()) {
            String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@" +
                    "(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
            if (!employee.getEmail().matches(emailRegex)) {
                errors.add("邮箱格式不正确");
            }
        }
        
        // 验证部门
        if (employee.getDepartment() == null || employee.getDepartment().trim().isEmpty()) {
            errors.add("部门不能为空");
        }
        
        // 验证薪资
        if (employee.getSalary() != null && employee.getSalary() < 0) {
            errors.add("薪资不能为负数");
        }
        
        return errors;
    }
    
    public List<Employee> getValidEmployees() {
        return validEmployees;
    }
    
    public List<Map<String, Object>> getErrorRecords() {
        return errorRecords;
    }
    
    public boolean hasErrors() {
        return !errorRecords.isEmpty();
    }
}

7.4 统一异常处理

为Excel处理添加统一的异常处理:

@ControllerAdvice
public class ExcelExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(ExcelExceptionHandler.class);
    
    @ExceptionHandler(IOException.class)
    public ResponseEntity<Map<String, String>> handleIOException(IOException e) {
        logger.error("文件读写异常", e);
        
        Map<String, String> response = new HashMap<>();
        response.put("error", "文件读写异常");
        response.put("message", e.getMessage());
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
    }
    
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<Map<String, String>> handleIllegalArgumentException(IllegalArgumentException e) {
        logger.error("参数异常", e);
        
        Map<String, String> response = new HashMap<>();
        response.put("error", "参数异常");
        response.put("message", e.getMessage());
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, String>> handleGenericException(Exception e) {
        logger.error("Excel处理异常", e);
        
        Map<String, String> response = new HashMap<>();
        response.put("error", "Excel处理异常");
        response.put("message", e.getMessage());
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
    }
}

8. 性能优化和注意事项

8.1 性能优化建议

使用适当的Excel库

避免一次性加载整个文件

合理设置缓冲区大小

减少样式对象

使用异步处理

8.2 注意事项

内存管理

安全考虑

编码问题

并发控制

临时文件清理

总结

Spring Boot提供了强大而灵活的Excel处理能力,通过结合Apache POI和EasyExcel等工具,可以轻松实现Excel文件的读取、创建和导出功能。在实际应用中,应根据具体需求和数据量选择合适的处理策略,既要保证功能完整,又要注重性能和资源使用。

无论是简单的数据导出,还是复杂的报表生成,或是大数据量的文件处理,都可以通过本文介绍的方法灵活实现。重点是要根据实际业务场景,选择合适的技术方案,并注意性能优化和异常处理。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文