Spring Boot 实现动态配置导出,同时支持公式和动态下拉框渲染和性能优化案例示范

在业务系统中,数据导出是一个非常常见且重要的功能,本文将详细介绍如何在 Spring Boot 中实现这一功能,并结合 MySQL 数据库、MyBatis 作为数据访问层,EasyExcel 作为导出工具,展示如何在电商交易系统中搭建灵活、可扩展且高性能的导出系统。


1. 需求分析

在电商交易系统中,导出的数据类型可能包括订单数据、商品数据、用户数据等。用户希望通过界面配置导出的字段,按需生成不同格式的 Excel 文件。这一需求可以归纳为:

  • 动态参数配置:用户可以通过前端选择导出字段和导出条件。
  • 动态列导出:根据用户的选择,导出时生成不同列的 Excel 文件。
  • 高效性能:针对大规模数据导出,系统需要优化性能,防止内存溢出和执行超时。
  • 可扩展性:支持未来新模块、新业务数据的导出需求。

技术选型

  1. Spring Boot:轻量级 Java 框架,简化了 Spring 的配置和开发工作。
  2. MySQL:用于存储电商系统的交易数据。
  3. MyBatis:简化数据库访问,提供灵活的 SQL 语句管理。
  4. EasyExcel:高效的 Excel 导出工具,提供良好的性能和丰富的功能,适用于大数据量导出。
  5. Lombok:减少样板代码(Boilerplate code),简化实体类和 DTO(Data Transfer Object)的开发。

2. 项目结构

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── ecommerce/
│   │           ├── config/               # 配置类
│   │           ├── controller/           # 控制器层
│   │           ├── service/              # 服务层
│   │           ├── mapper/               # MyBatis 映射层
│   │           ├── model/                # 数据实体类
│   │           ├── dto/                  # 数据传输对象
│   │           └── utils/                # 工具类
│   └── resources/
│       ├── application.yml               # Spring Boot 配置文件
│       └── mapper/                       # MyBatis 映射文件
└── pom.xml                               # Maven 依赖配置

3. Maven 依赖

为了实现以上功能,我们需要引入以下 Maven 依赖:

<dependencies><!-- Spring Boot Web Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- MyBatis Starter --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.0</version></dependency><!-- MySQL Connector --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- EasyExcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.5</version></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><!-- Spring Boot DevTools (optional) --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope></dependency>
</dependencies>

4. 数据库表设计

在电商交易系统中,我们假设有一张订单表 orders,结构如下:

CREATE TABLE orders (id BIGINT PRIMARY KEY AUTO_INCREMENT,order_number VARCHAR(50) NOT NULL,customer_name VARCHAR(100),total_amount DECIMAL(10, 2),order_status VARCHAR(20),created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

此表记录订单的基础信息,实际场景中可能包含更多字段。在本文示例中,我们假设用户可以选择导出 order_numbercustomer_nametotal_amountorder_status 等列。


5. 实现动态参数配置

通过前端界面,用户可以选择希望导出的字段和条件。后端通过接受这些参数,生成对应的 SQL 查询,并将结果导出。

DTO 定义
@Data
public class ExportRequestDTO {private List<String> columns; // 用户选择的导出列private String orderStatus;   // 订单状态筛选条件private String startDate;     // 开始日期private String endDate;       // 结束日期
}
控制器层
@RestController
@RequestMapping("/api/export")
public class ExportController {@Autowiredprivate ExportService exportService;@PostMapping("/orders")public void exportOrders(@RequestBody ExportRequestDTO exportRequest, HttpServletResponse response) throws IOException {response.setContentType("application/vnd.ms-excel");response.setHeader("Content-Disposition", "attachment;filename=orders.xlsx");exportService.exportOrders(exportRequest, response.getOutputStream());}
}

6. 数据导出服务实现

ExportService 中,我们根据用户选择的字段和条件,构造 SQL 查询并通过 EasyExcel 导出结果。

服务层
@Service
public class ExportService {@Autowiredprivate OrderMapper orderMapper;public void exportOrders(ExportRequestDTO exportRequest, OutputStream outputStream) {// 根据用户选择的列动态生成 SQL 查询List<OrderDTO> orders = orderMapper.selectOrders(exportRequest);// 使用 EasyExcel 导出ExcelWriter excelWriter = EasyExcel.write(outputStream, OrderDTO.class).build();WriteSheet writeSheet = EasyExcel.writerSheet("订单数据").build();excelWriter.write(orders, writeSheet);excelWriter.finish();}
}
MyBatis Mapper
@Mapper
public interface OrderMapper {List<OrderDTO> selectOrders(@Param("exportRequest") ExportRequestDTO exportRequest);
}
动态 SQL 生成(MyBatis XML)
<select id="selectOrders" resultType="com.ecommerce.dto.OrderDTO">SELECT<foreach collection="exportRequest.columns" item="column" separator=",">${column}</foreach>FROM ordersWHERE 1 = 1<if test="exportRequest.orderStatus != null">AND order_status = #{exportRequest.orderStatus}</if><if test="exportRequest.startDate != null">AND created_at &gt;= #{exportRequest.startDate}</if><if test="exportRequest.endDate != null">AND created_at &lt;= #{exportRequest.endDate}</if>
</select>

7. 使用 EasyExcel 实现动态列导出

EasyExcel 允许根据动态字段生成对应的 Excel 文件。在本例中,OrderDTO 是我们导出数据的载体。用户选择的列会自动映射到 DTO 中,生成 Excel 文件。

OrderDTO 类
@Data
public class OrderDTO {private String orderNumber;private String customerName;private BigDecimal totalAmount;private String orderStatus;private Timestamp createdAt;
}

8. 性能优化

8.1 分页查询性能优化:轮询分页代替传统分页
8.1.1 传统分页查询的性能问题

通常在大数据导出时,我们会采用分页查询的方式,如每次查询1000条记录并逐页导出,避免一次性加载过多数据。然而,当处理数百万条记录时,传统分页方式会越来越慢,尤其在 OFFSET 较大的情况下,查询效率会显著下降,因为数据库在分页时仍需扫描前面的数据并跳过。

public void exportOrdersPaged(ExportRequestDTO exportRequest, OutputStream outputStream) {ExcelWriter excelWriter = EasyExcel.write(outputStream, OrderDTO.class).build();WriteSheet writeSheet = EasyExcel.writerSheet("订单数据").build();int pageNum = 1;int pageSize = 1000; // 每次查询 1000 条List<OrderDTO> orders;do {orders = orderMapper.selectOrdersPaged(exportRequest, pageNum, pageSize);excelWriter.write(orders, writeSheet);pageNum++;} while (orders.size() == pageSize);excelWriter.finish();
}

这种方式虽然表面上避免了内存占用,但随着 pageNum 的增大,分页查询的 OFFSET 也会增大,导致查询效率下降。

8.1.2 轮询分页:通过主键过滤提升效率

为了避免大偏移量的分页查询,我们可以采用 轮询分页(Keyset Pagination)。轮询分页的核心思想是通过上一次查询结果的主键值作为下一次查询的起点,从而避免 OFFSET 的性能问题。

public void exportOrdersKeyset(ExportRequestDTO exportRequest, OutputStream outputStream) {ExcelWriter excelWriter = EasyExcel.write(outputStream, OrderDTO.class).build();WriteSheet writeSheet = EasyExcel.writerSheet("订单数据").build();Long lastId = null; // 用于记录上次分页的最后一个订单IDint pageSize = 1000;List<OrderDTO> orders;do {orders = orderMapper.selectOrdersKeyset(exportRequest, lastId, pageSize);if (!orders.isEmpty()) {lastId = orders.get(orders.size() - 1).getId(); // 记录最后一个IDexcelWriter.write(orders, writeSheet);}} while (orders.size() == pageSize);excelWriter.finish();
}
8.1.3 轮询分页的 SQL 实现
SELECT * FROM orders 
WHERE id > #{lastId} 
ORDER BY id ASC 
LIMIT #{pageSize}

通过上述 SQL,每次查询从上一次获取的最大 id 之后开始,避免了 OFFSET 的计算。这样,即使数据量再大,查询性能也能保持稳定。


8.2 流式处理:减少内存占用
8.2.1 问题分析

即使使用分页查询,在大数据导出时依然可能会遇到内存压力问题,尤其是在导出上百万条记录的场景下。如果我们将所有查询结果加载到内存中,再一次性写入 Excel,会导致 JVM 堆内存消耗过大,可能引发 OutOfMemoryError

8.2.2 流式处理优化

为了解决内存占用问题,可以使用 EasyExcel 提供的流式写入功能。在流式处理中,数据可以逐条查询并写入 Excel,避免将所有数据加载到内存中。以下是基于 EasyExcel 的流式处理示例:

public void exportOrdersStream(ExportRequestDTO exportRequest, OutputStream outputStream) {ExcelWriter excelWriter = EasyExcel.write(outputStream, OrderDTO.class).build();WriteSheet writeSheet = EasyExcel.writerSheet("订单数据").build();Long lastId = null;int pageSize = 1000;List<OrderDTO> orders;do {orders = orderMapper.selectOrdersKeyset(exportRequest, lastId, pageSize);if (!orders.isEmpty()) {lastId = orders.get(orders.size() - 1).getId();// 使用流式写入excelWriter.write(orders, writeSheet);}} while (orders.size() == pageSize);excelWriter.finish();
}

流式处理的核心是每次查询的数据在处理后立即释放,不会全部加载到内存,从而极大降低了内存的使用压力。


8.3 多线程并行导出:提高导出速度
8.3.1 问题分析

在单线程模式下,尽管我们可以优化查询效率并减少内存占用,但对于超大数据集的导出,单线程处理仍然可能耗费大量时间。为了进一步提升导出性能,可以采用 多线程并行导出 的方式,将数据按块分批处理。

8.3.2 多线程导出实现

通过线程池并行处理多个分页数据,可以显著提高导出效率。每个线程负责查询并处理一部分数据,最终汇总成一个完整的 Excel 文件。

public void exportOrdersParallel(ExportRequestDTO exportRequest, OutputStream outputStream) {ExcelWriter excelWriter = EasyExcel.write(outputStream, OrderDTO.class).build();WriteSheet writeSheet = EasyExcel.writerSheet("订单数据").build();int pageSize = 1000;Long lastId = null;// 创建线程池ExecutorService executor = Executors.newFixedThreadPool(10); List<Future<List<OrderDTO>>> futures = new ArrayList<>();// 分块查询并行处理for (int i = 0; i < 10; i++) {Long finalLastId = lastId;Future<List<OrderDTO>> future = executor.submit(() -> {return orderMapper.selectOrdersKeyset(exportRequest, finalLastId, pageSize);});futures.add(future);}// 汇总每个线程的结果for (Future<List<OrderDTO>> future : futures) {try {List<OrderDTO> orders = future.get();excelWriter.write(orders, writeSheet);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}// 关闭线程池executor.shutdown();excelWriter.finish();
}
8.3.3 注意事项
  • 线程安全性:在并行写入 Excel 时,EasyExcelwrite 方法是线程安全的,因此可以直接使用。
  • 合理设置线程数:线程数不宜过多,通常根据服务器的 CPU 核心数进行设置,以避免过多线程反而导致性能瓶颈。
  • 分页大小控制:每个线程负责的分页大小应适当调整,避免单个分页数据量过大或过小影响性能。

9. 扩展性设计

在复杂的电商系统中,可能涉及多个业务模块,如订单、商品、用户等。为了让这些模块都能灵活实现数据导出功能,并且保证代码的高复用性和可维护性,我们可以将导出逻辑抽象为通用接口。这样每个模块只需要实现这个接口即可完成相应的数据导出功能。

通过这样的设计,可以大幅减少代码冗余,提升系统的可扩展性。我们将从接口的定义开始,逐步实现针对不同业务模块的导出逻辑。


9.1 通用导出接口定义

首先定义一个通用的导出接口 ExportService。这个接口规定了所有导出服务的基本结构,每个模块都需要实现数据查询逻辑以及将数据导出为 Excel 的具体实现。

public interface ExportService<T> {/*** 导出数据** @param exportRequest 导出的参数配置* @param outputStream  输出流,用于生成 Excel 文件*/void exportData(ExportRequestDTO exportRequest, OutputStream outputStream);/*** 根据用户请求的参数动态查询数据** @param exportRequest 导出请求参数* @return 查询到的业务数据列表*/List<T> queryData(ExportRequestDTO exportRequest);
}
  • exportData:定义了数据导出的方法,接收导出请求和输出流,所有具体导出逻辑都在这个方法中实现。
  • queryData:用于动态查询数据,每个业务模块会根据自身的需求来实现。
9.2 通用导出逻辑实现

我们可以实现一个通用的抽象类 AbstractExportService 来减少重复代码。该抽象类实现了通用的 exportData 方法,并将数据查询交给具体业务模块去实现。

public abstract class AbstractExportService<T> implements ExportService<T> {@Overridepublic void exportData(ExportRequestDTO exportRequest, OutputStream outputStream) {// 查询数据List<T> data = queryData(exportRequest);// 使用 EasyExcel 导出数据ExcelWriter excelWriter = EasyExcel.write(outputStream, getModelClass()).build();WriteSheet writeSheet = EasyExcel.writerSheet("导出数据").build();excelWriter.write(data, writeSheet);excelWriter.finish();}/*** 获取导出实体类的类型** @return 实体类的类型*/protected abstract Class<T> getModelClass();
}
  • AbstractExportService 实现了通用的数据导出逻辑,通过 queryData 查询数据,并通过 EasyExcel 将数据导出为 Excel 文件。
  • getModelClass 方法让每个子类提供其具体的业务数据类型,例如订单、商品或用户。

9.3 针对订单模块的导出实现

在电商系统中,订单模块是非常常见的业务场景之一。我们可以通过实现 OrderExportService 来完成订单数据的导出。

@Service
public class OrderExportService extends AbstractExportService<OrderDTO> {@Autowiredprivate OrderMapper orderMapper;@Overridepublic List<OrderDTO> queryData(ExportRequestDTO exportRequest) {return orderMapper.selectOrders(exportRequest);}@Overrideprotected Class<OrderDTO> getModelClass() {return OrderDTO.class;}
}
  • OrderExportService 继承了 AbstractExportService,并实现了 queryData 方法,调用 OrderMapper 来动态查询订单数据。
  • getModelClass 返回订单的 DTO 类型 OrderDTO,以便 EasyExcel 知道如何映射列和数据。

9.4 针对商品模块的导出实现

同样,我们可以实现商品模块的导出逻辑。假设商品的基本信息存储在 products 表中,我们实现 ProductExportService

@Service
public class ProductExportService extends AbstractExportService<ProductDTO> {@Autowiredprivate ProductMapper productMapper;@Overridepublic List<ProductDTO> queryData(ExportRequestDTO exportRequest) {return productMapper.selectProducts(exportRequest);}@Overrideprotected Class<ProductDTO> getModelClass() {return ProductDTO.class;}
}
  • ProductExportService 负责商品数据的查询和导出,通过 ProductMapper 进行数据库查询。
  • 同样地,getModelClass 返回商品的 DTO 类型 ProductDTO

9.5 统一的控制器设计

通过对不同模块导出逻辑的封装,我们可以设计一个统一的控制器,根据请求动态调用不同的导出服务。

@RestController
@RequestMapping("/api/export")
public class ExportController {@Autowiredprivate Map<String, ExportService<?>> exportServices;@PostMapping("/{type}")public void exportData(@PathVariable String type, @RequestBody ExportRequestDTO exportRequest, HttpServletResponse response) throws IOException {response.setContentType("application/vnd.ms-excel");response.setHeader("Content-Disposition", "attachment;filename=" + type + ".xlsx");ExportService<?> exportService = exportServices.get(type);if (exportService == null) {throw new IllegalArgumentException("Unsupported export type: " + type);}exportService.exportData(exportRequest, response.getOutputStream());}
}
  • exportServices 是一个包含所有导出服务的 Map,我们可以根据 URL 中的 type 参数动态选择对应的导出服务。
  • @PostMapping("/{type}") 中,type 是用户请求的导出类型(例如 ordersproductsusers),通过 exportServices.get(type) 获取对应的导出服务。

9.6 服务注册

为了实现不同模块导出的动态调用,我们可以在 Spring Boot 启动时通过 @Bean 自动注册所有导出服务。

@Configuration
public class ExportServiceConfig {@Beanpublic Map<String, ExportService<?>> exportServices(OrderExportService orderExportService, ProductExportService productExportService) {Map<String, ExportService<?>> map = new HashMap<>();map.put("orders", orderExportService);map.put("products", productExportService);return map;}
}
  • ExportServiceConfig 中,将所有导出服务注册到 Map> 中,供控制器调用。
  • 这样,订单导出请求可以通过 type = orders 调用 OrderExportService,商品导出请求可以通过 type = products 调用 ProductExportService

9.7 扩展性优势

通过以上设计,我们将导出功能抽象为接口,并使用一个统一的控制器处理不同模块的数据导出需求。以下是该设计的几个扩展性优势:

  1. 模块解耦:每个模块的导出逻辑都是独立的,互不干扰。新增一个业务模块只需实现 ExportService 接口,并在配置类中注册即可。
  2. 代码复用:通过 AbstractExportService 实现通用的导出逻辑,减少了重复代码,提高了代码的可维护性。
  3. 灵活扩展:未来如果增加新业务模块,比如用户数据导出,只需新增 UserExportService 并实现对应的 queryData 方法,无需改动控制器或其他业务代码。
  4. 动态选择导出类型:控制器层的设计允许通过 URL 动态选择导出类型,方便扩展和管理不同类型的数据导出。

10. Excel 导出实现公式和样式

在实际的业务场景中,Excel 不仅仅用于展示数据,还需要具备一定的格式化和交互功能。通过 EasyExcel,我们可以为导出的 Excel 文件设置多种样式和功能,比如设置字体颜色、插入批注、添加动态下拉框等。这些功能能够提升导出文件的专业性和可读性,同时帮助用户更直观地分析数据。

接下来,我们将详细讲解如何使用 EasyExcel 实现以下几种功能:

  1. 设置列的字体颜色,例如红色字体
  2. 为单元格添加批注
  3. 为列添加动态下拉框

10.1 设置某列字体颜色

为了实现特定列的字体颜色更改,我们可以自定义 WriteHandler 来控制单元格的样式。EasyExcel 提供了 CellWriteHandler 接口来实现这个功能。

我们可以通过实现这个接口来动态控制某列的字体颜色,例如订单金额小于0时,显示为红色字体。

public class CustomCellWriteHandler implements CellWriteHandler {@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// 获取当前行和列索引Integer rowIndex = context.getRowIndex();Integer columnIndex = context.getColumnIndex();// 判断列号,比如订单金额在第5列if (columnIndex == 4) {Cell cell = context.getCell();Double cellValue = Double.valueOf(cell.getStringCellValue());// 如果金额小于 0,设置字体颜色为红色if (cellValue < 0) {Workbook workbook = context.getWriteSheetHolder().getSheet().getWorkbook();Font font = workbook.createFont();font.setColor(Font.COLOR_RED);CellStyle cellStyle = workbook.createCellStyle();cellStyle.setFont(font);cell.setCellStyle(cellStyle);}}}
}

在数据导出时,我们只需将这个 CustomCellWriteHandler 添加到 EasyExcelExcelWriter 中即可。

ExcelWriter excelWriter = EasyExcel.write(outputStream, OrderDTO.class).registerWriteHandler(new CustomCellWriteHandler()).build();
WriteSheet writeSheet = EasyExcel.writerSheet("订单数据").build();
excelWriter.write(orderData, writeSheet);
excelWriter.finish();
  • 逻辑说明:代码通过 CellWriteHandler 实现自定义样式设置。对于某列的数据,如果符合特定条件(例如小于0),将该列的字体颜色设置为红色。

10.2 为单元格添加批注

有时,我们需要在导出的 Excel 文件中为某些数据添加批注(Comment),以便解释某些数据或提供额外的注释信息。可以使用 WriteHandler 实现为指定单元格或指定列添加批注。

public class CommentWriteHandler implements CellWriteHandler {@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {Integer rowIndex = context.getRowIndex();Integer columnIndex = context.getColumnIndex();// 判断需要在哪一列添加批注,比如在第3列if (columnIndex == 2) {Workbook workbook = context.getWriteSheetHolder().getSheet().getWorkbook();Sheet sheet = context.getWriteSheetHolder().getSheet();CreationHelper factory = workbook.getCreationHelper();ClientAnchor anchor = factory.createClientAnchor();Drawing<?> drawing = sheet.createDrawingPatriarch();// 定位批注的位置(列和行)anchor.setCol1(columnIndex);anchor.setCol2(columnIndex + 1);anchor.setRow1(rowIndex);anchor.setRow2(rowIndex + 2);// 创建批注并设置内容Comment comment = drawing.createCellComment(anchor);comment.setString(factory.createRichTextString("此数据为系统自动生成"));Cell cell = context.getCell();cell.setCellComment(comment);}}
}

在 Excel 导出时,类似设置字体颜色,我们需要将 CommentWriteHandler 注册到 EasyExcel 中。

ExcelWriter excelWriter = EasyExcel.write(outputStream, OrderDTO.class).registerWriteHandler(new CommentWriteHandler()).build();
WriteSheet writeSheet = EasyExcel.writerSheet("订单数据").build();
excelWriter.write(orderData, writeSheet);
excelWriter.finish();
  • 逻辑说明:通过在某列上创建批注,提供额外说明或注释。例如,可以在订单编号列中添加批注,说明订单来源或某个数据字段的特别含义。

10.3 为列添加动态下拉框

动态下拉框可以帮助用户在 Excel 文件中快速选择预定义的值。我们可以在 Excel 导出时为特定的列添加下拉框,限制用户输入的内容,确保数据的有效性和一致性。

public class DropdownWriteHandler implements SheetWriteHandler {@Overridepublic void beforeSheetCreate(SheetWriteHandlerContext context) {}@Overridepublic void afterSheetCreate(SheetWriteHandlerContext context) {Sheet sheet = context.getSheet();// 假设需要为第2列(商品类别)添加下拉框String[] options = {"电子产品", "生活用品", "服装", "书籍"};// 定义范围和下拉框内容DataValidationHelper helper = sheet.getDataValidationHelper();CellRangeAddressList addressList = new CellRangeAddressList(1, 100, 1, 1); // 行范围DataValidationConstraint constraint = helper.createExplicitListConstraint(options);DataValidation validation = helper.createValidation(constraint, addressList);// 设置下拉框约束sheet.addValidationData(validation);}
}

在导出时将 DropdownWriteHandler 注册到 EasyExcel 中,动态为某列添加下拉框。

ExcelWriter excelWriter = EasyExcel.write(outputStream, ProductDTO.class).registerWriteHandler(new DropdownWriteHandler()).build();
WriteSheet writeSheet = EasyExcel.writerSheet("商品数据").build();
excelWriter.write(productData, writeSheet);
excelWriter.finish();
  • 逻辑说明:通过 DropdownWriteHandler,我们可以为指定的列(如商品类别)添加下拉框。这样用户在填写或修改该列时,只能选择预设的选项,确保数据输入的正确性。

11. 实现动态公式

在某些业务场景中,数据导出不仅需要展示原始数据,还需要在 Excel 文件中实现动态计算。比如在订单系统中,我们可以在导出时自动为某列设置公式,计算订单的总价。

public class FormulaWriteHandler implements CellWriteHandler {@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {Integer rowIndex = context.getRowIndex();Integer columnIndex = context.getColumnIndex();// 假设需要为第6列(总价)设置公式:总价 = 数量 * 单价if (columnIndex == 5) {Cell cell = context.getCell();String formula = String.format("C%d*D%d", rowIndex + 1, rowIndex + 1); // C列是数量,D列是单价cell.setCellFormula(formula);}}
}

在导出时,使用 FormulaWriteHandler 注册到 EasyExcel 中,实现公式的动态设置。

ExcelWriter excelWriter = EasyExcel.write(outputStream, OrderDTO.class).registerWriteHandler(new FormulaWriteHandler()).build();
WriteSheet writeSheet = EasyExcel.writerSheet("订单数据").build();
excelWriter.write(orderData, writeSheet);
excelWriter.finish();
  • 逻辑说明:通过公式,可以在 Excel 导出时动态计算出订单总价。在 Excel 文件中,用户打开后,系统会自动进行计算并显示结果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/55063.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

64.【C语言】再议结构体(下)

本文衔接第63篇63.【C语言】再议结构体(上) 目录 目录 6.复习 7.修改默认对齐数 8.结构体传参 01.传递非指针参数 02.传递指针参数(传递地址) 03.对比 9.结构体实现位段 01.位段的定义 02.格式 03.例题 答案速查 分析 10.位段跨平台问题 11.位段的应用 12.其他…

scrapy 爬取微博(五)【最新超详细解析】: 爬取微博文章

1 读取配置参数 爬取微博文章首先需要读取settings.py中的设置的配置变量&#xff0c;然后编写爬虫&#xff0c;读取的配置变量主要有爬取的关键词、时间范围、爬取区域等。 class WeiboSearchSpider(scrapy.Spider):name weibo_searchallowed_domains [weibo.com]settings…

完成UI界面的绘制

绘制UI 接上文&#xff0c;在Order90Canvas下创建Image子物体&#xff0c;图片资源ui_fish_lv1&#xff0c;设置锚点&#xff08;CountdownPanelImg同理&#xff09;&#xff0c;命名为LvPanelImg,创建Text子物体&#xff0c;边框宽高各50&#xff0c; &#xff0c;重名为LvT…

影刀---如何进行自动化操作

本文不是广告&#xff0c;没有人给我宣传费&#xff0c;只是单纯的觉得这个软件很好用 感谢大家的多多支持哦 本文 1.基本概念与操作&#xff08;非标准下拉框和上传下载&#xff09;非标准对话框的操作上传对话框、下载的对话框、提示的对话框 2.综合案例3.找不到元素怎么办&a…

css 中 ~ 符号、text-indent、ellipsis、ellipsis-2、text-overflow: ellipsis、::before的使用

1、~的使用直接看代码 <script setup> </script><template><div class"container"><p><a href"javascript:;">纪检委</a><a href"javascript:;">中介为</a><a href"javascript:…

架构师:消息队列的技术指南

1、简述 消息队列(Message Queue, MQ)是一种异步通信机制,允许系统的各个组件通过消息在彼此之间进行通信。消息队列通过解耦系统组件、缓冲高峰期请求和提高系统的可扩展性,成为分布式系统中不可或缺的一部分。 2、工作原理 消息队列的基本工作原理是生产者将消息发布到…

python爬虫案例——抓取链家租房信息(8)

文章目录 1、任务目标2、分析网页3、编写代码1、任务目标 目标站点:链家租房版块(https://bj.lianjia.com/zufang/) 要求:抓取该链接下前5页所有的租房信息,包括:标题、详情信息、详情链接、价格 如: 2、分析网页 用浏览器打开链接,按F12或右键检查,进入开发者模式;因…

Axure-本地发布,局域网内用户访问

目录 前言内容一. 选择Axure发布路径&#xff0c;进行相应配置二.添加IIS服务配置&#xff08;不在这里赘述&#xff01;&#xff09;三&#xff1a;添加IIS配置&#xff0c;创建网站四.添加安全策略&#xff1a;实践&#xff1a; 前言 最近加入了公司的积分系统&#xff0c;由…

Qt网络编程——QUdpSocket

文章目录 Qt网络编程QUdpSocketUdp回显服务器Udp客户端 Qt网络编程 网络编程本质上是写应用层代码&#xff0c;需要传输层提供支持。 而传输层最核心的协议就是UDP和TCP&#xff0c;这两个协议有较大差别&#xff0c;所以Qt提供了两套API。 要是有Qt网络编程的API&#xff0…

【CSS Tricks】css动画详解

目录 引言一、动画关键帧序列二、动画各属性拆解1. animation-name2. animation-duration3. animation-delay3.1 设置delay为正值3.2 设置delay为负值 4. animation-direction5. animation-iteration-count6. animation-fill-mode7. animation-play-state8. animation-timing-f…

Kubernetes云原生存储解决方案之 Rook Ceph实践探究

Kubernetes云原生存储解决方案之 Rook Ceph实践探究 除了手动部署独立的 Ceph 集群并配置与Kubernetes进行对接外&#xff0c;Rook Ceph 支持直接在 Kubernetes 集群上部署 Ceph 集群。 通过Rook Ceph云原生存储编排平台&#xff0c;使得 Kubernetes 集群中启用高可用的 Ceph…

RabbitMQ常用管理命令及管理后台

RabbitMQ管理命令 1、用户管理1.1、新增一个用户1.2、查看当前用户列表1.3、设置用户角色1.4、设置用户权限1.5、查看用户权限 2、RabbitMQ的web管理后台2.1、查看rabbitmq 的插件列表2.2、启用插件2.3、禁用插件2.4、访问RabbitMQ的web后台2.4、通过web页面新建虚拟主机 ./rab…

【计算机网络】详解HTTP请求和响应格式常见请求方法Header报头响应报文状态码URL

一、HTTP协议的定义 在互联网世界中&#xff0c;HTTP &#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是一个至关重要的协议。它定义了客户端&#xff08;如浏览器&#xff09;与服务器之间如何通信&#xff0c;以交换或传输超文本&#xff08…

如何选择合适的跨境网络专线?

选择合适的跨境网络专线对于保障企业的国际业务顺畅运行至关重要。以下是一些选择跨境网络专线时可以参考的关键点&#xff1a; 服务商的信誉和经验&#xff1a;首先考察服务商的市场声誉和行业经验。一个好的服务商应该拥有良好的客户评价和成功案例&#xff0c;这表明他们有能…

【4.5】图搜索算法-BFS和DFS求岛屿的最大面积

一、题目 给定一个包含了一些 0 和 1 的非空二维数组 grid 。一个岛屿是由一些相邻的 1 (代表土地)构成的组合&#xff0c; 这里的「相邻」要求两个1必须在水 平或者竖直方向上相邻 。你可以假设 grid 的四个边缘都被0&#xff08;代表水&#xff09;包围着。 找到给定的二维…

4.数据结构与算法-线性表的顺序表示和实现

2.1 线性表的定义和特点 线性表例子 线性表的逻辑特征 2.2 案例引入 案例2.1 案例2.1&#xff1a;一元多项式的运算 案例2.2&#xff1a;系数多项式的运算 用时间换取空间--线性数组相对链式访问速度快 但这种方法链式更节省空间&#xff0c;所以用时间换取空间 案例2.3&…

Hbase要点简记

Hbase要点简记 Hbase1、底层架构2、表逻辑结构 Hbase HBase是一个分布式的、列式的、实时查询的、非关系型数据库&#xff0c;可以处理PB级别的数据&#xff0c;吞吐量可以到的百万查询/每秒。主要应用于接口等实时数据应用需求&#xff0c;针对具体需求&#xff0c;设计高效率…

Arm Linux 交叉编译openssl 1.1.1

一、openssl 源码下载 OpenSSL的官方网站或源代码仓库下载最新或指定版本的OpenSSL源代码。 官网地址&#xff1a; https://openssl-library.org/source/index.html 旧版本下载&#xff1a; https://openssl-library.org/source/old/index.html 这里以 1.1.1 为例 国内git…

plt.bar函数介绍及实战

目录 plt.bar() 函数实战 plt.bar() 函数 plt.bar() 函数是 Matplotlib 中用于创建柱状图的函数。它用于在图形中绘制一个或多个柱状图&#xff0c;通常用于展示类别型数据的数量或大小的比较。 基本语法&#xff1a; plt.bar(x, height, width0.8, bottomNone, aligncenter…

归并排序,外排序,计数排序(非比较排序)

归并排序&#xff1a;&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每个子序列有序…