# lesscoding-util **Repository Path**: chargeduck/lesscoding-util ## Basic Information - **Project Name**: lesscoding-util - **Description**: This repository is used to record my commonly used utility classes. Currently, it only contains the Excel import and export tool classes, as well as custom annotations for synchronizing relational databases to Neo4j. - **Primary Language**: Java - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-06 - **Last Updated**: 2026-08-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # LessCoding Util Java 常用工具类库,基于 Apache POI 提供 Excel 模板导出/导入能力,聚焦减少重复编码。 ## 特性 - **单行表头导出**:定义列映射,一行代码完成数据写入 - **多级合并表头导出**:递归树结构,支持任意层数的合并表头 - **公式下拉验证**:基于隐藏 Sheet + 名称管理器,支持 VLOOKUP 级联下拉 - **Excel 导入解析**:指定表头行,自动解析为 `List` 结构,支持字段名替换 - **HTTP 响应头工具**:一行设置文件下载响应头,支持时间戳文件名 - **零额外依赖**:仅需 `poi-ooxml` + `slf4j-api`(Servlet 为 `provided`) - **Java 8+**:兼容 JDK 8 ## Maven 依赖 ```xml io.github.chargeduck lesscoding-util 1.0.0 ``` ## 快速开始 ### 导出 Excel(单行表头) ```java // 1. 定义表头 List> headers = Stream.of( new ExcelHeaderVo<>("商品名称", Goods::getName), new ExcelHeaderVo<>("商品分类", Goods::getCategoryStr), new ExcelHeaderVo<>("单位", Goods::getUnit), new ExcelHeaderVo<>("单价", Goods::getPrice) ).collect(Collectors.toList()); // 2. 创建 Workbook 并写入数据 Workbook workbook = new XSSFWorkbook(); TemplateUtil.writeDataSheet(workbook, "商品列表", headers, goodsList, null); // 3. 设置响应头并输出 TemplateUtil.setResponseHeader(response, "商品列表.xlsx", true); workbook.write(response.getOutputStream()); ``` ### 导出带下拉的模板 ```java Workbook workbook = new XSSFWorkbook(); // 写入隐藏 Sheet(下拉数据源) TemplateUtil.writeHiddenSheet(workbook, "category", categoryList, GoodsCategory::getName, GoodsCategory::getId); // 配置公式下拉 List formulas = Stream.of( new ExcelFormula() .setCellIndex(1) .setByFormula(true) .setNameManager(TemplateUtil.getSheetNameManagerName("category")) .setFormulaStr("INDEX(categoryName,0,1)") .setSimpleCellIndex(headers.size()) .setSimpleHeader("分类ID") .setSimpleCellDataCell(2) ).collect(Collectors.toList()); TemplateUtil.writeDataSheet(workbook, "商品模板", headers, dataList, formulas); workbook.write(outputStream); ``` ### 省市区三级级联下拉 完整的三级级联下拉模板示例。隐藏 Sheet 存储 `[取值列, 过滤列]` 映射,`ExcelFormula` 通过名称管理器将列串联起来。 ```java // 数据源实体:value = 下级(城市/区县),parent = 上级(省份/城市) class Region { private final String value; private final String parent; Region(String value, String parent) { this.value = value; this.parent = parent; } String getValue() { return value; } String getParent() { return parent; } } ``` ```java List provinceCityList = Arrays.asList( new Region("广州市", "广东省"), new Region("深圳市", "广东省"), new Region("杭州市", "浙江省")); List cityDistrictList = Arrays.asList( new Region("天河区", "广州市"), new Region("南山区", "深圳市"), new Region("西湖区", "杭州市")); Workbook workbook = new XSSFWorkbook(); // 1. 写入隐藏 Sheet:每个隐藏 Sheet 为 [取值列, 过滤列] TemplateUtil.writeHiddenSheets(workbook, Arrays.asList( new HiddenSheetVo<>("ProvinceCity", provinceCityList, Arrays.asList(Region::getValue, Region::getParent)), new HiddenSheetVo<>("CityDistrict", cityDistrictList, Arrays.asList(Region::getValue, Region::getParent)))); // 2. 数据 Sheet 三列表头 List> headers = Arrays.asList( new ExcelHeaderVo<>("省", RegionRow::getProvince), new ExcelHeaderVo<>("市", RegionRow::getCity), new ExcelHeaderVo<>("区", RegionRow::getDistrict)); TemplateUtil.reorderExcelHeaderList(headers); // 3. 三级级联验证: // 省列(索引 0)→ 显式列表下拉 // 市列(索引 1)→ 根据省列(索引 0)从 ProvinceCity 级联过滤 // 区列(索引 2)→ 根据市列(索引 1)从 CityDistrict 级联过滤 List formulas = Arrays.asList( new ExcelFormula().setCellIndex(0).setByFormula(false) .setDataList(Arrays.asList("广东省", "浙江省", "江苏省")), new ExcelFormula().setCellIndex(1).setByFormula(true) .setSpecialFormulaFlag(true) .setSpecialFilterNM(TemplateUtil.getSheetNameManagerName("ProvinceCity")) .setSpecialFilterNMCell(2) // 过滤列(省份名)在名称管理器中的列索引(1-based) .setSpecialFilterNMOffset(1) // 取值列(城市名)相对过滤列向左偏移 1 列 .setSpecialFilterByCell(0), // 数据 Sheet 中用于过滤的「省」列索引 new ExcelFormula().setCellIndex(2).setByFormula(true) .setSpecialFormulaFlag(true) .setSpecialFilterNM(TemplateUtil.getSheetNameManagerName("CityDistrict")) .setSpecialFilterNMCell(2) // 过滤列(城市名)在名称管理器中的列索引(1-based) .setSpecialFilterNMOffset(1) // 取值列(区县名)相对过滤列向左偏移 1 列 .setSpecialFilterByCell(1)); // 数据 Sheet 中用于过滤的「市」列索引 // 4. 生成模板(dataList=null 时生成 1000 行空模板) TemplateUtil.writeDataSheet(workbook, "省市区模板", headers, null, formulas); ``` > **注意**:隐藏 Sheet 的列顺序很重要——第 1 列是「取值列」(城市/区县),第 2 列是「过滤列」(省份/城市)。`RegionRow` 是提供 `province` / `city` / `district` getter 的简单 POJO。 ### 导出合并表头 ```java List> mergeHeaders = Arrays.asList( ExcelMergeHeaderVo.group("基本信息", ExcelMergeHeaderVo.leaf("姓名", new ExcelHeaderVo<>("姓名", User::getName)), ExcelMergeHeaderVo.leaf("年龄", new ExcelHeaderVo<>("年龄", User::getAge)) ), ExcelMergeHeaderVo.group("联系方式", ExcelMergeHeaderVo.leaf("电话", new ExcelHeaderVo<>("电话", User::getPhone)), ExcelMergeHeaderVo.leaf("邮箱", new ExcelHeaderVo<>("邮箱", User::getEmail)) ) ); Sheet sheet = TemplateUtil.writeDataSheetByMerge(workbook, "用户表", mergeHeaders, formulas); // sheet 已生成表头 + 1000 行模板,可继续填入数据 // 传入额外参数 templateRow 可自定义空模板行数: // TemplateUtil.writeDataSheetByMerge(workbook, "用户表", mergeHeaders, formulas, 500); ``` ### 导入解析 ```java Workbook workbook = WorkbookFactory.create(inputStream); List> data = TemplateUtil.parseBaseAndExtra( workbook, "商品列表", 0, null); // 每行为 LinkedHashMap,key=表头文本,value=单元格值 ``` ## 核心 API ### TemplateUtil(门面工具类) 所有对外 API 均通过 `TemplateUtil` 静态方法调用: | 方法 | 说明 | |---|---| | `setResponseHeader(response, fileName)` | 设置 HTTP 文件下载响应头 | | `setResponseHeader(response, fileName, addTimeStr)` | 设置响应头,可选追加时间戳 | | `writeHiddenSheet(workbook, sheetName, dataList, mapperList...)` | 写入隐藏 Sheet,变长参数版 | | `writeHiddenSheet(workbook, sheetName, dataList, mapperList)` | 写入隐藏 Sheet,List 版 | | `writeHiddenSheets(workbook, hiddenSheetVoList)` | 批量写入隐藏 Sheet | | `writeDataSheet(workbook, sheetName, headers, dataList, formulas)` | 单行表头数据 Sheet | | `writeDataSheet(workbook, sheetName, headers, dataList, formulas, templateRow)` | 单行表头数据 Sheet,可自定义空模板行数 | | `writeDataSheetByMerge(workbook, sheetName, mergeHeaders, formulas)` | 多级合并表头 Sheet | | `writeDataSheetByMerge(workbook, sheetName, mergeHeaders, formulas, templateRow)` | 多级合并表头 Sheet,可自定义空模板行数 | | `parseBaseAndExtra(workbook, sheetName, headerRowIndex, replaceMap)` | 解析 Excel 为 `List` | | `getSheetNameManagerName(sheetName)` | 获取 Sheet 对应的名称管理器名 | | `getLetterByIndex(index, plusFlag)` | 列索引 → Excel 列字母 | | `reorderExcelHeaderList(list)` | 重排表头 cellIndex | | `reorderExcelFormula(list, headerSize)` | 为公式自动分配辅助列索引 | ### 数据模型 | 类 | 说明 | |---|---| | `ExcelHeaderVo` | 表头列定义:列索引、表头文本、取值函数 | | `ExcelMergeHeaderVo` | 多级合并表头树节点,支持 `group()`/`leaf()` 工厂方法 | | `HiddenSheetVo` | 隐藏 Sheet 定义:Sheet 名、数据列表、列映射 | | `ExcelFormula` | 公式/下拉验证定义:支持显式列表和公式引用两种模式 | | `TemplateUtil.InnerDict` | 内置字典键值对(code/name),快速构造隐藏 Sheet 数据 | ## 项目结构 ``` src/main/java/io/github/chargeduck/excel/ ├── TemplateUtil.java # 门面工具类(所有公开 API 入口) ├── export/ │ ├── SimpleExporter.java # 单行表头导出器 │ └── MergeExporter.java # 多级合并表头导出器(递归树) ├── parser/ │ └── ExcelParser.java # Excel 导入解析器 ├── model/ │ ├── ExcelHeaderVo.java # 表头列定义 │ ├── ExcelMergeHeaderVo.java# 合并表头树节点 │ ├── ExcelFormula.java # 公式/下拉验证定义 │ └── HiddenSheetVo.java # 隐藏 Sheet 定义 └── internal/ ├── LetterHelper.java # 列字母转换工具 ├── NameManagerHelper.java # 名称管理器工具 ├── StrFormatter.java # 字符串格式化工具 └── GroupRange.java # 合并表头跨度辅助 ``` ## 许可证 MIT License