在企业级应用开发中,数据导出是高频需求。本文介绍一种支持动态列选择、灵活配置的通用 Excel 导出方案,通过前后端协同设计,实现导出字段、列顺序、数据格式的自定义,满足多样化业务场景。
一、功能架构设计
核心特性
- 动态字段选择:支持通过前端勾选动态指定导出字段,包含字段名(逻辑标识)与显示名(业务含义)的映射
- 行数据过滤:支持按用户 ID 筛选导出特定行数据
- 多 Sheet 支持:可扩展支持单个 Excel 文件包含多个 Sheet 页
- 格式自适应:自动处理日期、数字等数据类型的格式化显示
技术栈
- 前端:Thymeleaf 模板引擎 + XMLHttpRequest 文件下载
- 后端:Spring Boot + EasyExcel + Hutool 工具集
- 核心组件:
- 请求参数:ExcelExportRequest(包含基础配置与字段列表)
- 响应结构:ExcelExportResponse(封装文件元信息与 Sheet 数据)
二、核心实现细节
1. 前后端数据协议设计
入参结构(ExcelExportRequest)
@Data
public class UserExportRequest extends ExcelExportRequest {
private List<Integer> userIdList; // 待导出的用户ID列表(可选)
}
@Data
public class ExcelExportRequest {
private String excelName; // Excel文件名
private String sheetName; // Sheet页名称
private List<ExcelExportField> fieldList; // 导出字段列表(有序)
}
@Data
public class ExcelExportField {
private String fieldName; // 实体类字段名(如"userId")
private String fieldDesc; // 表格显示名称(如"用户ID")
}
出参结构(ExcelExportResponse)
@Data
public class ExcelExportResponse {
private String excelName; // 导出文件名
private List<ExcelSheet> sheetList; // Sheet数据集合
@Data
public static class ExcelSheet {
private String sheetName; // Sheet名称
private List<ExcelHead> headList; // 表头信息
private List<Map<String, String>> dataList; // 行数据(键值对形式)
@Data
public static class ExcelHead {
private String fieldName; // 字段名
private String fieldDesc; // 显示名
}
}
}
2. 前端交互实现
动态列选择组件
<!-- 案例1:仅列选择 -->
<table border="1">
<caption>
<span class="title">案例1:勾选需要导出的列</span>
<button onclick="exportExcel1(event)">导出</button>
</caption>
<tr>
<th><label><input type="checkbox" class="exportCol" data-field-name="userId" data-field-desc="用户id"> 用户id</label></th>
<th><label><input type="checkbox" class="exportCol" data-field-name="userName" data-field-desc="用户名">用户名</label></th>
<!-- 更多字段... -->
</tr>
</table>
<!-- 案例2:列选择+行筛选 -->
<table border="1">
<caption>
<span class="title">案例2:勾选需要导出的列 & 行</span>
<button onclick="exportExcel2(event)">导出</button>
</caption>
<tr>
<th>选择记录</th>
<th><label><input type="checkbox" class="exportCol" data-field-name="userId" data-field-desc="用户id"> 用户id</label></th>
<!-- 更多字段... -->
</tr>
<tr th:each="user:${userList}">
<td><input type="checkbox" class="userId" th:data-user-id="${user.userId}"></td>
<td th:text="${user.userId}"></td>
<!-- 数据行展示... -->
</tr>
</table>
文件下载逻辑
function download(data, url) {
const xhr = new XMLHttpRequest();
xhr.open("POST", url);
xhr.responseType = 'blob';
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8');
xhr.onload = function() {
if (this.status === 200) {
const blob = this.response;
if (blob.size > 0) {
// 从响应头解析文件名
const fileName = getFileNameFromResponse(this.getResponseHeader("content-disposition"));
// 创建临时链接下载
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = fileName;
a.click();
}
}
};
xhr.send(JSON.stringify(data));
}
// 文件名解析工具
function getFileNameFromResponse(disposition) {
const match = /filename=(.*)/.exec(disposition);
return decodeURIComponent(match[1].replace(/['"]/g, ''));
}
3. 后端核心处理
控制器设计
@Controller
@CrossOrigin
public class UserController {
@Resource private UserService userService;
// 页面跳转
@GetMapping("/userList")
public String userList(Model model) {
model.addAttribute("userList", userService.getUserList());
return "userList";
}
// 导出接口
@PostMapping("/userExport")
public void userExport(@RequestBody UserExportRequest request) throws IOException {
ExcelExportResponse response = userService.userExport(request);
ExcelExportUtils.writeExcelToResponse(response);
}
}
业务层逻辑
@Service
public class UserServiceImpl implements UserService {
@Override
public ExcelExportResponse userExport(UserExportRequest request) {
List<User> dataList;
// 处理行筛选逻辑
if (CollectionUtil.isEmpty(request.getUserIdList())) {
dataList = getUserList(); // 导出全部数据
} else {
dataList = getUserList(request.getUserIdList()); // 按ID筛选
}
// 构建导出数据
return ExcelExportUtils.build(dataList, request);
}
// 模拟数据获取
private List<User> getUserList() {
List<User> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
list.add(new User(i, "用户名-" + i, 20 + i, "地址-" + i));
}
return list;
}
}
导出工具类
public class ExcelExportUtils {
public static ExcelExportResponse build(List<?> dataList, ExcelExportRequest request) {
ExcelExportResponse result = new ExcelExportResponse();
result.setExcelName(request.getExcelName());
List<ExcelSheet> sheetList = new ArrayList<>();
ExcelSheet sheet = new ExcelSheet();
sheet.setSheetName(request.getSheetName());
// 构建表头(保持字段顺序)
sheet.setHeadList(buildSheetHeadList(request.getFieldList()));
// 构建数据行(通过反射获取字段值)
sheet.setDataList(buildSheetDataList(dataList, request.getFieldList()));
sheetList.add(sheet);
result.setSheetList(sheetList);
return result;
}
private static List<ExcelSheet.ExcelHead> buildSheetHeadList(List<ExcelExportField> fields) {
return fields.stream()
.map(field -> new ExcelSheet.ExcelHead(field.getFieldName(), field.getFieldDesc()))
.collect(Collectors.toList());
}
// 反射获取对象字段值
private static List<Map<String, String>> buildSheetDataList(List<?> dataList, List<ExcelExportField> fields) {
return dataList.stream().map(obj -> {
Map<String, String> row = new HashMap<>();
fields.forEach(field -> {
Object value = ReflectUtil.getFieldValue(obj, field.getFieldName());
row.put(field.getFieldName(), Objects.toString(value, ""));
});
return row;
}).collect(Collectors.toList());
}
// 响应输出处理
public static void writeExcelToResponse(ExcelExportResponse result) throws IOException {
HttpServletResponse response = getResponse();
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition",
"attachment; filename=" + URLEncodeUtil.encode(result.getExcelName() + ".xlsx"));
try (ExcelWriter writer = EasyExcel.write(response.getOutputStream()).build()) {
result.getSheetList().forEach(sheet -> {
WriteSheet writeSheet = EasyExcel.writerSheet(sheet.getSheetName()).build();
// 写入表头与数据
writer.write(buildEasyExcelData(sheet), writeSheet);
});
}
}
}
三、方案优势分析
- 灵活性:通过fieldList实现导出字段的动态排序与筛选,适应不同业务视图需求
- 扩展性:支持添加多 Sheet、数据格式化(如日期转换)、样式配置等扩展功能
- 易用性:前端可视化勾选操作,后端自动处理反射映射,降低使用门槛
- 性能优化:通过工具类封装重复逻辑,减少代码冗余,提升开发效率
四、应用场景
- 数据报表导出:支持不同角色用户自定义报表字段
- 批量数据下载:结合行筛选功能实现精准数据提取
- 系统对接:为第三方系统提供标准化 Excel 数据输出接口
通过该方案,开发者可快速实现具备灵活配置能力的 Excel 导出功能,同时保持代码的可维护性与扩展性。实际应用中可根据业务需求,进一步扩展数据格式化、单元格样式、多语言支持等高级功能。