在企業級應用開發中,數據導出是高頻需求。本文介紹一種支持動態列選擇、靈活配置的通用 Excel 導出方案,通過前后端協同設計,實現導出字段、列順序、數據格式的自定義,滿足多樣化業務場景。
一、功能架構設計
核心特性
- 動態字段選擇:支持通過前端勾選動態指定導出字段,包含字段名(邏輯標識)與顯示名(業務含義)的映射
- 行數據過濾:支持按用戶 ID 篩選導出特定行數據
- 多 Sheet 支持:可擴展支持單個 Excel 文件包含多個 Sheet 頁
- 格式自適應:自動處理日期、數字等數據類型的格式化顯示
技術棧
- 前端:Thymeleaf 模板引擎 + XMLHttpRequest 文件下載
- 后端:Spring Boot + EasyExcel + Hutool 工具集
- 核心組件:
- 請求參數:ExcelExportRequest(包含基礎配置與字段列表)
- 響應結構:ExcelExportResponse(封裝文件元信息與 Sheet 數據)
二、核心實現細節
1. 前后端數據協議設計
入參結構(ExcelExportRequest)
@Datapublic class UserExportRequest extends ExcelExportRequest {private List<Integer> userIdList; // 待導出的用戶ID列表(可選)}@Datapublic class ExcelExportRequest {private String excelName; // Excel文件名private String sheetName; // Sheet頁名稱private List<ExcelExportField> fieldList; // 導出字段列表(有序)}@Datapublic class ExcelExportField {private String fieldName; // 實體類字段名(如"userId")private String fieldDesc; // 表格顯示名稱(如"用戶ID")}
出參結構(ExcelExportResponse)
@Datapublic class ExcelExportResponse {private String excelName; // 導出文件名private List<ExcelSheet> sheetList; // Sheet數據集合@Datapublic static class ExcelSheet {private String sheetName; // Sheet名稱private List<ExcelHead> headList; // 表頭信息private List<Map<String, String>> dataList; // 行數據(鍵值對形式)@Datapublic 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@CrossOriginpublic 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);}}
業務層邏輯
@Servicepublic class UserServiceImpl implements UserService {@Overridepublic 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 導出功能,同時保持代碼的可維護性與擴展性。實際應用中可根據業務需求,進一步擴展數據格式化、單元格樣式、多語言支持等高級功能。