Spring Boot 实现动态配置导出,同时支持公式和动态下拉框渲染和性能优化案例示范
在业务系统中,数据导出是一个非常常见且重要的功能,本文将详细介绍如何在 Spring Boot 中实现这一功能,并结合 MySQL 数据库、MyBatis 作为数据访问层,EasyExcel 作为导出工具,展示如何在电商交易系统中搭建灵活、可扩展且高性能的导出系统。
1. 需求分析
在电商交易系统中,导出的数据类型可能包括订单数据、商品数据、用户数据等。用户希望通过界面配置导出的字段,按需生成不同格式的 Excel 文件。这一需求可以归纳为:
- 动态参数配置:用户可以通过前端选择导出字段和导出条件。
- 动态列导出:根据用户的选择,导出时生成不同列的 Excel 文件。
- 高效性能:针对大规模数据导出,系统需要优化性能,防止内存溢出和执行超时。
- 可扩展性:支持未来新模块、新业务数据的导出需求。
技术选型
- Spring Boot:轻量级 Java 框架,简化了 Spring 的配置和开发工作。
- MySQL:用于存储电商系统的交易数据。
- MyBatis:简化数据库访问,提供灵活的 SQL 语句管理。
- EasyExcel:高效的 Excel 导出工具,提供良好的性能和丰富的功能,适用于大数据量导出。
- 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_number
、customer_name
、total_amount
和 order_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 >= #{exportRequest.startDate}</if><if test="exportRequest.endDate != null">AND created_at <= #{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 时,
EasyExcel
的write
方法是线程安全的,因此可以直接使用。 - 合理设置线程数:线程数不宜过多,通常根据服务器的 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
是用户请求的导出类型(例如orders
、products
、users
),通过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 扩展性优势
通过以上设计,我们将导出功能抽象为接口,并使用一个统一的控制器处理不同模块的数据导出需求。以下是该设计的几个扩展性优势:
- 模块解耦:每个模块的导出逻辑都是独立的,互不干扰。新增一个业务模块只需实现
ExportService
接口,并在配置类中注册即可。 - 代码复用:通过
AbstractExportService
实现通用的导出逻辑,减少了重复代码,提高了代码的可维护性。 - 灵活扩展:未来如果增加新业务模块,比如用户数据导出,只需新增
UserExportService
并实现对应的queryData
方法,无需改动控制器或其他业务代码。 - 动态选择导出类型:控制器层的设计允许通过 URL 动态选择导出类型,方便扩展和管理不同类型的数据导出。
10. Excel 导出实现公式和样式
在实际的业务场景中,Excel 不仅仅用于展示数据,还需要具备一定的格式化和交互功能。通过 EasyExcel,我们可以为导出的 Excel 文件设置多种样式和功能,比如设置字体颜色、插入批注、添加动态下拉框等。这些功能能够提升导出文件的专业性和可读性,同时帮助用户更直观地分析数据。
接下来,我们将详细讲解如何使用 EasyExcel 实现以下几种功能:
- 设置列的字体颜色,例如红色字体
- 为单元格添加批注
- 为列添加动态下拉框
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
添加到 EasyExcel
的 ExcelWriter
中即可。
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 文件中,用户打开后,系统会自动进行计算并显示结果。
相关文章:

Spring Boot 实现动态配置导出,同时支持公式和动态下拉框渲染和性能优化案例示范
在业务系统中,数据导出是一个非常常见且重要的功能,本文将详细介绍如何在 Spring Boot 中实现这一功能,并结合 MySQL 数据库、MyBatis 作为数据访问层,EasyExcel 作为导出工具,展示如何在电商交易系统中搭建灵活、可扩…...

一网打尽 运维必封的50个高危端口清单,零基础入门到精通,收藏这一篇就够了
文件传输相关端口: • TCP 20、21:FTP 服务(文件传输协议)端口,FTP 传输数据时未加密,容易受到攻击,如匿名上传下载、爆破、嗅探、远程执行等攻击,可能导致敏感文件泄露。 • TCP …...

方法 WebDriverWait
定义: WebDriverWait是Selenium WebDriver提供的一个工具类,它允许你设置等待条件,直到这个条件成立,才继续执行代码。这对于处理网页上的异步加载元素特别有用,比如等待某个元素变得可见、可点击等。 from se…...

LOESS(Locally Estimated Scatterplot Smoothing)
文章目录 LOESS 原理详解:LOESS 的优点:LOESS 的缺点:Python 实现代码:代码说明: LOESS(Locally Estimated Scatterplot Smoothing),即局部加权回归,是一种非参数回归方法…...

每天学习一个技术栈 ——【Django Channels】篇(1)
在当今快速发展的技术领域,掌握多种技术栈已经成为开发者提升竞争力的关键。随着实时应用需求的不断增加,如何高效地处理并发请求和实时通信变得尤为重要。在众多解决方案中,Django Channels作为Django框架的强大扩展,能够轻松实现…...

js设计模式-工厂模式 单例模式 观察者模式 发布订阅模式 原型模式 代理模式 迭代器模式
1 工厂模式 // 工厂模式: 调用函数返回对象function factory(name, age){return {name: name,age: age} }const person1 factory(Tom, 18); // 类似的库使用工厂函数的有: jQuery, React.createElement,axios.create,vue.createApp等 2 单例模式 // 单例模式:单…...

关于Java中的List<User>如何进行深拷贝
联调中发现了一个很初级,但有容易被忽略的拷贝问题: 错误方式:List<User> us new ArrayList<>(); // name "张三"List<User> us1 new ArrayList<>(us);for (User u : us) {...u.setName("douzi&q…...

2025 年 IT 前景:机遇与挑战并存,人工智能和云计算成重点
云计算de小白 投资人工智能:平衡潜力与实用性 到 2025 年,人工智能将成为 IT 支出的重要驱动力,尤其是在生成式人工智能领域。人工智能的前景在于它有可能彻底改变业务流程、增强决策能力并开辟新的收入来源。然而,现实情况更加微…...

Cortex-A7和Cortex-M7架构处理器取中断向量全流程分析
0 参考资料 Cortex M3权威指南(中文).pdf ARM Cortex-A(armV7)编程手册V4.0.pdf1 Cortex-A7和Cortex-M7处理器架构取中断向量全流程分析 1.1 什么是中断向量? 中断向量就是中断服务函数入口地址,例如我们发生了EXTI0中断,就需要执行EXT0中…...

MODELS 2024震撼续章:科技与可持续性的未来交响曲
MODELS 2024国际会议正如火如荼地进行着,每一天都充满了新的发现与启迪,每一场分享都是对技术前沿的一次深刻探索,更是对现实世界可持续性挑战的一次积极回应。现在让我们继续这场科技盛宴,看看小编为您精选几场的学术分享吧~ 会议…...

CICD 持续集成与持续交付
一 、CICD是什么 CI/CD 是指持续集成(Continuous Integration)和持续部署(Continuous Deployment)或持续交付(Continuous Delivery) 1.1 持续集成(Continuous Integration) 持续集…...

“数据面”(Data Plane)是指负责实际数据处理和转发的部分
在计算机网络和服务架构中,“数据面”(Data Plane)是指负责实际数据处理和转发的部分。数据面负责执行具体的网络通信任务,如接收、处理和转发数据包。与数据面对应的是“控制面”(Control Plane)ÿ…...

面试题:MySQL你用过WITH吗?领免费激活码
感谢Java面试教程的Java多线程文章,点击查看>原文 Java面试教程,发mmm116可获取IDEA-jihuoma 在MySQL中,WITH子句用于定义临时表或视图,也称为公共表表达式(CTE)。它允许你在一个查询中定义一个临时结果…...

consul 介绍与使用,以及spring boot 项目的集成
目录 前言一、Consul 介绍二、Consul 的使用三、Spring Boot 项目集成 Consul总结前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 提示:以下是…...

Linux常用命令shell常用知识 。。。。面试被虐之后,吐血整理。。。。
Linux三剑客&常用命令&shell常识 Linux三剑客grep - print lines matching a patternsed - stream editor for filtering and transforming textawkman awk Linux常用命令dd命令ssh命令tar命令curl命令top命令tr命令xargs命令sort命令du/df/free命令 shell 知识functio…...

压力测试指南-压力测试基础入门
压力测试基础入门 在当今快速迭代的软件开发环境中,确保应用程序在高负载情况下仍能稳定运行变得至关重要。这正是压力测试大显身手的时刻。本文将带领您深入了解压力测试的基础知识,介绍实用工具,并指导您设计、执行压力测试,最…...

Linux:LCD驱动开发
目录 1.不同接口的LCD硬件操作原理 应用工程师眼中看到的LCD 1.1像素的颜色怎么表示 编辑 1.2怎么把颜色发给LCD 驱动工程师眼中看到的LCD 统一的LCD硬件模型 8080接口 TFTRGB接口 什么是MIPI Framebuffer驱动程序框架 怎么编写Framebuffer驱动框架 硬件LCD时序分析…...

QT:常用类与组件
1.设计QQ的界面 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> #include <QLineEdit> #include <QLabel>//自定义类Widget,采用public方式继承QWidget,该类封装了图形化界面的相关操作ÿ…...

企业内训|提示词工程师高阶技术内训-某运营商研发团队
近日,TsingtaoAI为某运营商技术团队交付提示词工程师高级技术培训,本课程为期2天,深入探讨深度学习与大模型技术在提示词生成与优化、客服大模型产品设计等业务场景中的应用。内容涵盖了深度学习前沿理论、大模型技术架构设计与优化、以及如何…...

K8S真正删除pod
假设k8s的某个命名空间如(default)有一个运行nginx 的pod,而这个pod是以kubectl run pod命令运行的 1.错误示范: kubectl delete pod nginx-2756690723-hllbp 结果显示这个pod 是删除了,但k8s很快自动创建新的pod,但是…...

数据结构:队列及其应用
队列(Queue)是一种特殊的线性表,它的主要特点是先进先出(First In First Out,FIFO)。队列只允许在一端(队尾)进行插入操作,而在另一端(队头)进行删…...

26个用好AI大模型的提示词技巧
如果你已深入探索过ChatGPT、Microsoft Copilot、风变AI等前沿的生成式AI工具,那么你对“prompt”(提示词)这一核心概念一定有自己的认知。 作为连接你与AI创意源泉的桥梁,“prompt”不仅是触发无限想象的钥匙,更是塑…...

线性表二——栈stack
第一题 #include<bits/stdc.h> using namespace std; stack<char> s; int n; string ced;//如何匹配 出现的右括号转换成同类型的左括号,方便我们直接和栈顶元素 char cheak(char c){if(c)) return (;if(c]) return [;if(c}) return {;return \0;/…...

浏览器发送请求后关闭,服务器的处理过程
之前在开发中,有些后端服务处理非常慢,页面可能会出现504 Gateway time-out的提示,或者服务器还没返回数据,浏览器就关掉了。我们只是看到了浏览器关掉,但是服务器和客户端的状态都是什么样的呢? 问题 在…...

tee命令:轻松同步输出到屏幕与文件
一、命令简介 tee 命令在 Linux 和 Unix 系统中用于读取标准输入的数据,并将其同时输出到标准输出和文件中。简单来说,tee 命令可以用来分割数据流,使其既能够被输出到屏幕,也能够被写入到文件中。 二、命令参数…...

【经验技巧】如何做好S参数的仿测一致性
根据个人经验,想要做好电路板S参数的仿测一致性,如下的相关信息必须被认真对待: 1. PCB叠构(Stack up),仿真模型需要保证设计参数与板厂供应商的生产参数完全一样,这些参数包括: 叠层结构数据;介电常数;损耗因子;蚀刻因子;表面粗糙度。 2. 仿真中,需要保证信号测试…...

js逆向——webpack实战案例(一)
今日受害者网站:https://www.iciba.com/translate?typetext 首先通过跟栈的方法找到加密位置 我们跟进u函数,发现是通过webpack加载的 向上寻找u的加载位置,然后打上断点,刷新网页,让程序断在加载函数的位置 u r.n…...

Spring Boot 进阶-Spring Boot的全局异常处理机制详解
我们知道在软件运行的过程中,总会出现各种各样的问题,各种各样的异常,而程序员的主要任务之一就是解决在程序运行过程中出现的这些异常。在很多程序员开发的代码中我们会看到在关键的地方为了保证程序能够有一个正常的反馈,大量地使用了try catch finally语句。 大量的try …...

滚雪球学MySQL[7.1讲]:安全管理
全文目录: 前言7. 安全管理7.1 用户与权限管理7.1.1 创建和管理用户7.1.2 权限分配与管理7.1.3 最小权限原则 7.2 安全策略配置7.2.1 使用加密连接7.2.2 强密码策略7.2.3 定期审计和日志管理 7.3 SQL注入防范7.3.1 使用预处理语句7.3.2 输入验证与清理7.3.3 最小化数…...

反射及其应用---->2
目录 1.使用类对象 1.1创建对象 1.2使用对象属性 1.3使用方法 2.反射操作数组 3.反射获得泛型 4.类加载器 4.1双亲委派机制 4.2自定义加载器 1.使用类对象 通过反射使用类对象,主要体现3个部分 创建对象,调用方法,调用属性ÿ…...