当前位置: 首页 > news >正文

基于 Apache POI 5.2.5 构建高效 Excel 工具类:从零到生产级实践

文章目录

    • 一、为什么选择 Apache POI?
    • 二、工具类设计目标
    • 三、核心功能详解
      • 1. 创建并填充数据表
      • 2. 支持按“列”组织的数据结构
      • 3. 防止科学计数法:设置文本格式
      • 4. 内置常用数据验证规则
      • 5. 实现跨 Sheet 数据联动(VLOOKUP)
      • 6. 获取 Excel 总行数(用于导入预判)
    • 四、完整使用示例
    • 五、完整excel工具类

在企业级 Java 开发中,Excel 文件的导入导出是高频需求。无论是报表生成、批量数据处理还是用户上传模板,都需要一个稳定、灵活且功能丰富的 Excel 操作工具。

本文将基于 Apache POI 5.2.5 版本,结合实际项目经验,分享一个生产可用的 Excel 工具类(ApachePoiUtils) 的设计思路与核心实现,并深入解析其关键特性,帮助你快速构建高质量的 Excel 处理能力。

一、为什么选择 Apache POI?

Apache POI 是 Java 领域最主流的 Office 文档操作库,支持 .xls 和 .xlsx 格式。它提供了对 Excel 的精细控制,适用于复杂场景。

✅ 优势:
完全开源免费
支持读写 .xlsx (XSSF) 和 .xls (HSSF)
可精确控制单元格样式、公式、数据验证等
社区活跃,版本迭代稳定
⚠️ 注意事项(POI 5.2.5):
使用 XSSF 模式时内存消耗较高,大数据量建议配合 SXSSF 或流式读取
依赖较多,需注意版本兼容性(推荐使用 BOM 管理)

<!-- Maven 引入 -->
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.5</version>
</dependency>

二、工具类设计目标

我们期望这个工具类能解决以下常见痛点:

问题	解决方案
数据错乱(如手机号变科学计数法)	设置列格式为文本
用户输入非法数据	添加数据验证规则
批量录入效率低	支持下拉框和自动填充
表头样式不统一	自动设置标题样式
多Sheet管理混乱	提供统一创建接口

为此,我们封装了 ApachePoiUtils 类,具备如下能力:

✅ 创建带样式的 Sheet

✅ 支持 Map/列表结构数据填充

✅ 设置文本格式防止精度丢失

✅ 内置常用字段验证(手机号、身份证、车牌号、姓名)

✅ 支持下拉选择与跨表联动

✅ 自动计算行高列宽

三、核心功能详解

1. 创建并填充数据表

方法签名:

public static Sheet createSheetAndSetValue(List<String> titleNameList, List<List<String>> dataList,String dataSheetName, Workbook workbook)

功能说明:
自动生成标题行,加粗 + 背景色 + 居中
设置默认字体大小为 14pt
每行高度设为 300,标题行 400
所有列默认设置为文本格式,避免数字被自动转换
列宽统一设置为 28 * 256 单位(约 28 字符宽度)
示例调用:

Workbook wb = new XSSFWorkbook();
List<String> titles = Arrays.asList("姓名", "电话", "身份证");
List<List<String>> data = Arrays.asList(Arrays.asList("张三", "13812345678", "51010419900307XXXX")
);
Sheet sheet = ApachePoiUtils.createSheetAndSetValue(titles, data, "用户信息", wb);

2. 支持按“列”组织的数据结构

有时数据是以列为单位存储的,比如前端传来的 JSON 结构:

{"names": ["张三", "李四"],"phones": ["138...", "139..."]
}

为此提供专用方法:

public static Sheet createSheetAndSetValueByCol(List<String> titleNameList, List<List<String>> colDataList,String dataSheetName, Workbook workbook)

该方法会自动转置数据,同时支持交替背景色提升可读性。

3. 防止科学计数法:设置文本格式

这是最容易被忽视的问题!当导出长数字(如身份证、手机号)时,Excel 默认将其识别为数值类型,导致显示异常或精度丢失。

解决方案:设置列样式为文本格式 @

CellStyle textStyle = workbook.createCellStyle();
textStyle.setDataFormat(createHelper.createDataFormat().getFormat("@"));
currentSheet.setDefaultColumnStyle(colIndex, textStyle);

我们在所有数据列上都应用了此样式,确保内容原样展示。

4. 内置常用数据验证规则

通过 DataValidation 实现 Excel 原生校验功能,提升用户体验。

(1)手机号验证
校验逻辑:

必须为 11 位
必须以 1 开头
全部由数字组成

String phoneFormula = "AND(LEN(A2)=11, ISNUMBER(VALUE(A2)), LEFT(A2,1)=\"1\")";

(2)身份证号验证
综合判断:

长度 18 位
前17位为数字
最后一位为数字或 X
出生日期 ≤ 当前日期

=AND(LEN(A2)=18,ISNUMBER(VALUE(LEFT(A2,17))),OR(ISNUMBER(VALUE(RIGHT(A2,1))),UPPER(RIGHT(A2,1))="X"),DATEVALUE(TEXT(MID(A2,7,8),"0000-00-00"))<=TODAY()
)

(3)车牌号验证(蓝牌 & 新能源绿牌)
支持两种格式:

蓝牌:京A12345(7位)
绿牌:京AB12345(8位)
首字符必须为中文(通过 LENB(LEFT(A2,1))=2 判断双字节)

=OR(AND(LEN(A2)=7, LENB(LEFT(A2,1))=2, CODE(MID(A2,2,1))>=65),AND(LEN(A2)=8, LENB(LEFT(A2,1))=2, CODE(MID(A2,2,1))>=65)
)

(4)姓名验证(仅限中文)
限制条件:

2~15 个字符
全部为中文(双字节)
不含空格或特殊符号

=AND(LEN(A2)>=2, LEN(A2)<=15,LENB(A2)=LEN(A2)*2,EXACT(A2,CLEAN(A2))
)

(5)下拉框选择
常用于性别、状态等枚举字段:

setDropdownValidation(sheet, "\"男,女\"", 2, 2); // C列只能选“男”或“女”

也可引用其他 Sheet 的区域:

setDropdownValidation(sheet, "地区表!$A$2:$A$100", 3, 3);

5. 实现跨 Sheet 数据联动(VLOOKUP)

这是高级功能,可用于省市区三级联动、部门-负责人关联等。

思路:
在辅助 Sheet 中维护映射关系(如:部门 → 负责人)
主表中使用 VLOOKUP 公式动态获取结果

ApachePoiUtils.setDataLinkage(mainSheet, 0, 1, "部门映射表");

生成的公式示例:

=IFERROR(VLOOKUP(A2, 部门映射表!$A$2:$B$100000, 2, FALSE), "")

6. 获取 Excel 总行数(用于导入预判)

在文件上传阶段,可先统计有效行数,便于后续分页或资源分配。

try (InputStream is = file.getInputStream()) {long rowCount = ApachePoiUtils.getTotalRows(is);System.out.println("共 " + rowCount + " 行数据");
}

内部通过遍历 Row 并判断是否为空行来统计,比 getLastRowNum() 更准确。

四、完整使用示例

@Test
public void testExport() throws IOException {Workbook workbook = new XSSFWorkbook();// 1. 创建主表List<String> titles = Arrays.asList("姓名", "电话", "身份证", "车牌号");Map<String, String> dataMap = new LinkedHashMap<>();dataMap.put("张三", "13812345678");dataMap.put("李四", "13987654321");Sheet sheet = ApachePoiUtils.createSheetAndSetValueByMap(titles, dataMap, "用户数据", workbook);// 2. 设置验证规则ApachePoiUtils.setMobileValidation(sheet, 1, 1);     // B列:手机号ApachePoiUtils.setIdCardValidation(sheet, 2, 2);     // C列:身份证ApachePoiUtils.setLicensePlateValidation(sheet, 3, 3); // D列:车牌ApachePoiUtils.setNameValidation(sheet, 0, 0);       // A列:姓名// 3. 添加下拉框(假设第4列是性别)ApachePoiUtils.setDropdownValidation(sheet, "\"男,女\"", 4, 4);// 4. 保存文件try (FileOutputStream out = new FileOutputStream("用户导入模板.xlsx")) {workbook.write(out);}
}

五、完整excel工具类


import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSONObject;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.ss.util.CellReference;import java.io.IOException;
import java.io.InputStream;
import java.util.*;/*** @author super hero* @Description*/
public class ApachePoiUtils {////////////////////////////////////// 创建数据表开始 //////////////////////////////////////*** 创建sheet并且设置数据** @param titleNameList 标题名称* @param dataMap       数据,适用于只有两列的情况* @param dataSheetName sheet名称* @param workbook      工作表* @return 当前sheet*/public static Sheet createSheetAndSetValueByMap(List<String> titleNameList, Map<String, String> dataMap,String dataSheetName, Workbook workbook) {List<List<String>> regionDataList = new ArrayList<>();if (CollUtil.isNotEmpty(dataMap)) {Iterator<Map.Entry<String, String>> iterator = dataMap.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<String, String> next = iterator.next();String code = Optional.ofNullable(next.getKey()).orElse("");String name = Optional.ofNullable(next.getValue()).orElse("");regionDataList.add(Arrays.asList(code, name));}}return createSheetAndSetValue(titleNameList, regionDataList, dataSheetName, workbook);}/*** 创建sheet并且设置数据** @param titleNameList 标题名称* @param dataList      数据,数据是一行 一行的* @param dataSheetName sheet名称* @param workbook      工作表* @return 当前sheet*/public static Sheet createSheetAndSetValue(List<String> titleNameList, List<List<String>> dataList,String dataSheetName, Workbook workbook) {// 创建数据表Sheet dataSheet = workbook.createSheet(dataSheetName);// 设置标题Row rowTitle = dataSheet.createRow(0);// 创建并配置标题的单元格样式Font font = workbook.createFont();// 加粗字体font.setBold(true);// 设置字体大小为 14font.setFontHeightInPoints((short) 14);CellStyle headerCellStyle = workbook.createCellStyle();headerCellStyle.setFont(font);// 背景颜色headerCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);// 设置边框和居中setBorderAndCenter(headerCellStyle, true);int colTotalSize = titleNameList.size();for (int i = 0; i < colTotalSize; i++) {String currentTitleName = Optional.ofNullable(titleNameList.get(i)).orElse("");Cell cell = rowTitle.createCell(i);cell.setCellValue(currentTitleName);// 应用样式到标题单元格cell.setCellStyle(headerCellStyle);}// 设置行高rowTitle.setHeight((short) 400);for (int row = 0; row < dataList.size(); row++) {List<String> rowDataList = dataList.get(row);if (CollUtil.isEmpty(rowDataList)) {continue;}int dataSize = rowDataList.size();if (colTotalSize != dataSize) {throw new RuntimeException("第" + (row + 1) + "条数据,缺少数据," + JSONObject.toJSONString(rowDataList));}Row rowInfo = dataSheet.createRow(row + 1);// 文本行rowInfo.setHeight((short) 300);for (int col = 0; col < colTotalSize; col++) {rowInfo.createCell(col).setCellValue(rowDataList.get(col));}}// === 列可编辑 + 文本格式(避免科学计数法)===CreationHelper createHelper = workbook.getCreationHelper();CellStyle phoneCellStyle = workbook.createCellStyle();phoneCellStyle.setDataFormat(createHelper.createDataFormat().getFormat("@")); // 文本格式// 设置边框和居中setBorderAndCenter(phoneCellStyle, false);
//                phoneCellStyle.setLocked(false); // ✅ 必须可编辑for (int i = 0; i < colTotalSize; i++) {dataSheet.setDefaultColumnStyle(i, phoneCellStyle);dataSheet.setColumnWidth(i, 28 * 256);}return dataSheet;}/*** 创建sheet并且设置数据** @param titleNameList 标题名称* @param colDataList   数据,这里的数据是 一列 一列 的* @param dataSheetName sheet名称* @param workbook      工作表* @return 当前sheet*/public static Sheet createSheetAndSetValueByCol(List<String> titleNameList, List<List<String>> colDataList,String dataSheetName, Workbook workbook) {// 创建数据表Sheet dataSheet = workbook.createSheet(dataSheetName);// 设置标题Row rowTitle = dataSheet.createRow(0);// 创建并配置标题的单元格样式Font font = workbook.createFont();// 加粗字体font.setBold(true);// 设置字体大小为 14font.setFontHeightInPoints((short) 14);CellStyle headerCellStyle = workbook.createCellStyle();headerCellStyle.setFont(font);// 背景颜色headerCellStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());// 实心前景色填充headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);// 设置边框和居中setBorderAndCenter(headerCellStyle, true);CellStyle headerTwoCellStyle = workbook.createCellStyle();headerTwoCellStyle.setFont(font);// 背景颜色headerTwoCellStyle.setFillForegroundColor(IndexedColors.LAVENDER.getIndex());// 实心前景色填充headerTwoCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);// 设置边框和居中setBorderAndCenter(headerTwoCellStyle, true);int colTotalSize = titleNameList.size();for (int i = 0; i < colTotalSize; i++) {String currentTitleName = Optional.ofNullable(titleNameList.get(i)).orElse("");Cell cell = rowTitle.createCell(i);cell.setCellValue(currentTitleName);cell.setCellStyle(i % 2 == 0 ? headerCellStyle : headerTwoCellStyle);}// 设置行高rowTitle.setHeight((short) 400);int maxSize = 1;for (List<String> stringList : colDataList) {int size = stringList.size();if (size > maxSize) {maxSize = size;}}List<Row> rowList = new ArrayList<>();for (int row = 1; row <= maxSize; row++) {// 创建行Row row1 = dataSheet.createRow(row);// 文本行row1.setHeight((short) 300);rowList.add(row1);}for (int i = 0; i < colDataList.size(); i++) {List<String> colData = colDataList.get(i);if (CollUtil.isEmpty(colData)) {continue;}for (int j = 0; j < colData.size(); j++) {Row cells = rowList.get(j);cells.createCell(i).setCellValue(colData.get(j));}}// === 列可编辑 + 文本格式(避免科学计数法)===CreationHelper createHelper = workbook.getCreationHelper();CellStyle txtOneCellStyle = workbook.createCellStyle();txtOneCellStyle.setDataFormat(createHelper.createDataFormat().getFormat("@")); // 文本格式
//                phoneCellStyle.setLocked(false); // ✅ 必须可编辑// 设置边框和居中setBorderAndCenter(txtOneCellStyle, false);// 背景颜色txtOneCellStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());// 实心前景色填充txtOneCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);CellStyle txtTwoCellStyle = workbook.createCellStyle();txtTwoCellStyle.setDataFormat(createHelper.createDataFormat().getFormat("@")); // 文本格式
//                phoneCellStyle.setLocked(false); // ✅ 必须可编辑// 设置边框和居中setBorderAndCenter(txtTwoCellStyle, false);// 背景颜色txtTwoCellStyle.setFillForegroundColor(IndexedColors.LAVENDER.getIndex());// 实心前景色填充txtTwoCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);for (int i = 0; i < colTotalSize; i++) {dataSheet.setDefaultColumnStyle(i, i % 2 == 0 ? txtOneCellStyle : txtTwoCellStyle);dataSheet.setColumnWidth(i, 28 * 256);}return dataSheet;}/*** 设置单元格样式, 设置边框,并且设置字体居中* @param cellStyle 样式* @param center 是否居中*/private static void setBorderAndCenter(CellStyle cellStyle, boolean center){// 添加顶部边框cellStyle.setBorderTop(BorderStyle.THIN);// 添加底部边框cellStyle.setBorderBottom(BorderStyle.THIN);// 添加左侧边框cellStyle.setBorderLeft(BorderStyle.THIN);// 添加右侧边框cellStyle.setBorderRight(BorderStyle.THIN);if (center){// 水平居中cellStyle.setAlignment(HorizontalAlignment.CENTER);// 垂直居中cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);}}////////////////////////////////////// 创建数据表结束 /////////////////////////////////////////////////////////////////////////// 设置样式开始 //////////////////////////////////////*** 设置列为文本格式** @param currentSheet 当前sheet* @param firstCol     开始列 ,从0开始* @param lastCol      结束刘,从0开始*/public static void setColumnStyleToText(Sheet currentSheet, int firstCol, int lastCol) {Workbook workbook = currentSheet.getWorkbook();// 1. 设置单元格样式为【文本】CellStyle textStyle = workbook.createCellStyle();// 设置为不锁定textStyle.setLocked(false);// 创建一个简单的文本格式DataFormat format = workbook.createDataFormat();// "@" 表示文本格式textStyle.setDataFormat(format.getFormat("@"));// 将指定列的默认列样式设为文本(适用于新输入的内容)for (int col = firstCol; col <= lastCol; col++) {currentSheet.setDefaultColumnStyle(col, textStyle);}}////////////////////////////////////// 设置样式结束 /////////////////////////////////////////////////////////////////////////// 设置验证规则开始 //////////////////////////////////////*** 设置所有类型车牌号验证规则(支持蓝牌、新能源)* 支持格式:* - 普通蓝牌:京A12345(7位)* - 新能源绿牌:京AB12345(8位)** @param currentSheet 当前工作表* @param firstCol     开始列索引(从0开始)* @param lastCol      结束列索引(从0开始)*/public static void setLicensePlateValidation(Sheet currentSheet, int firstCol, int lastCol) {DataValidationHelper helper = currentSheet.getDataValidationHelper();String colLetter = CellReference.convertNumToColString(firstCol);// 构建综合车牌验证公式String formula ="OR(" +// 1. 普通蓝牌:7位,中文 + 字母 + 5位(数字/字母)"AND(LEN(" + colLetter + "2)=7," +"LENB(LEFT(" + colLetter + "2,1))=2," +  // 首字符中文"AND(CODE(MID(" + colLetter + "2,2,1))>=65, CODE(MID(" + colLetter + "2,2,1))<=90)" +  // 第2位 A-Z")," +// 2. 新能源绿牌:8位,中文 + 字母 + 6位(数字/字母)"AND(LEN(" + colLetter + "2)=8," +"LENB(LEFT(" + colLetter + "2,1))=2," +"AND(CODE(MID(" + colLetter + "2,2,1))>=65, CODE(MID(" + colLetter + "2,2,1))<=90)" +")" +")";DataValidationConstraint constraint = helper.createCustomConstraint(formula);CellRangeAddressList addressList = new CellRangeAddressList(1, 100000, firstCol, lastCol);DataValidation validation = helper.createValidation(constraint, addressList);validation.setErrorStyle(DataValidation.ErrorStyle.STOP);validation.createErrorBox("无效车牌号", "请输入合法车牌号,例如:\n普通车:川A12345(7位)\n新能源:川AB12345(8位)");validation.setShowErrorBox(true);currentSheet.addValidationData(validation);}/*** 设置手机号码验证规则** @param currentSheet 当前工作表* @param firstCol     开始第几列,从0开始* @param lastCol      结束第几列,从0开始*/public static void setMobileValidation(Sheet currentSheet, int firstCol, int lastCol) {DataValidationHelper dataValidationHelper = currentSheet.getDataValidationHelper();// 动态将列索引转换为列字母,例如 0 -> "A", 3 -> "D"String colLetter = CellReference.convertNumToColString(firstCol);// 构建 A1 风格公式:校验是否为 11 位、纯数字、且以 "1" 开头String phoneFormula ="AND(" +"LEN(" + colLetter + "2)=11," +                    // 长度为11"ISNUMBER(VALUE(" + colLetter + "2))," +           // 整个单元格内容可转为数字(隐含:全为数字)"LEFT(" + colLetter + "2,1)=\"1\"" +               // 第一个字符是 "1"")";DataValidationConstraint phoneConstraint = dataValidationHelper.createCustomConstraint(phoneFormula);CellRangeAddressList addressListD = new CellRangeAddressList(1, 100000, firstCol, lastCol);DataValidation dvD = dataValidationHelper.createValidation(phoneConstraint, addressListD);dvD.setErrorStyle(DataValidation.ErrorStyle.STOP);dvD.createErrorBox("无效电话号码", "请输入合法的11位手机号码(以1开头,如13912345678)。");dvD.setShowErrorBox(true);currentSheet.addValidationData(dvD);}/*** 设置证件号码验证规则** @param currentSheet 当前工作表* @param firstCol     开始第几列,从0开始* @param lastCol      结束第几列,从0开始*/public static void setIdCardValidation(Sheet currentSheet, int firstCol, int lastCol) {DataValidationHelper dataValidationHelper = currentSheet.getDataValidationHelper();// 构造身份证校验公式(包含格式 + 出生日期 <= 当前日期)// 将列索引转换为列字母,例如 0 -> "A", 1 -> "B"String colLetter = CellReference.convertNumToColString(firstCol);// 构建 A1 风格的公式,从第2行开始(对应 Excel 第2行)// 注意:这里使用 "2" 表示第二行,Excel 会自动相对引用到每一行String idCardFormula ="AND(" +"LEN(" + colLetter + "2)=18," +                                 // 长度为18"ISNUMBER(VALUE(LEFT(" + colLetter + "2,17)))," +               // 前17位是数字"OR(ISNUMBER(VALUE(RIGHT(" + colLetter + "2,1))),UPPER(RIGHT(" + colLetter + "2,1))=\"X\")," + // 最后一位是数字或X"LEN(MID(" + colLetter + "2,7,8))=8," +                         // 出生年月日部分为8位"ISNUMBER(VALUE(MID(" + colLetter + "2,7,8)))," +               // 出生年月日是数字"DATEVALUE(TEXT(MID(" + colLetter + "2,7,8),\"0000-00-00\"))<=TODAY()" + // 出生日期 <= 当前日期")";DataValidationConstraint idCardConstraint = dataValidationHelper.createCustomConstraint(idCardFormula);CellRangeAddressList addressListC = new CellRangeAddressList(1, 100000, firstCol, lastCol);DataValidation dvC = dataValidationHelper.createValidation(idCardConstraint, addressListC);dvC.setErrorStyle(DataValidation.ErrorStyle.STOP);dvC.createErrorBox("无效身份证号", "请输入合法的18位身份证号(最后一位可以是X),且不能重复");dvC.setShowErrorBox(true);currentSheet.addValidationData(dvC);}/*** 设置姓名验证规则:仅允许 2-15 位中文字符(双字节),不能包含字母、数字、符号或不可见字符** @param currentSheet 当前工作表* @param firstCol     开始列(从 0 开始)* @param lastCol      结束列(从 0 开始)*/public static void setNameValidation(Sheet currentSheet, int firstCol, int lastCol) {DataValidationHelper helper = currentSheet.getDataValidationHelper();// 使用 A1 风格引用,假设 firstCol 对应列字母String colLetter = CellReference.convertNumToColString(firstCol);String nameFormula = "AND(LEN(" + colLetter + "2)>=2, "+ "LEN(" + colLetter + "2)<=15, "+ "LENB(" + colLetter + "2)=LEN(" + colLetter + "2)*2, "+ "EXACT(" + colLetter + "2,CLEAN(" + colLetter + "2)))";// ✅ 使用字符串公式(唯一可用方式)DataValidationConstraint constraint = helper.createCustomConstraint(nameFormula);// 应用范围:第2行到第10000行(行索引从0开始,所以是1~10000)CellRangeAddressList addressList = new CellRangeAddressList(1, 100000, firstCol, lastCol);DataValidation validation = helper.createValidation(constraint, addressList);validation.setErrorStyle(DataValidation.ErrorStyle.STOP);validation.createErrorBox("无效姓名", "请输入2-15位中文姓名,不能包含字母、数字、符号或空格。");validation.setShowErrorBox(true);currentSheet.addValidationData(validation);}/*** 设置下拉选择框** @param currentSheet 当前工作表* @param dataSource   数据源* @param firstCol     下拉列表-开始列(从 0 开始)* @param lastCol      下拉列表-结束列(从 0 开始)*/public static void setDropdownValidation(Sheet currentSheet, String dataSource, int firstCol, int lastCol) {// ==================== 设置名称列下拉列表 ====================DataValidationHelper dvHelper = currentSheet.getDataValidationHelper();DataValidationConstraint dvConstraint = dvHelper.createFormulaListConstraint(dataSource);// 作用范围: C列 (第2行到第10000行)CellRangeAddressList addressList = new CellRangeAddressList(1, 100000, firstCol, lastCol);DataValidation validation = dvHelper.createValidation(dvConstraint, addressList);validation.setErrorStyle(DataValidation.ErrorStyle.STOP);validation.createErrorBox("无效输入", "请选择下拉列表中的有效值。");validation.setShowErrorBox(true);currentSheet.addValidationData(validation);}////////////////////////////////////// 设置验证规则结束 ///////////////////////////////////////    /**
//     * 设置数据联动
//     * 【注】:不支持多线程调用,当多个线程同时调用:createRow(j)、getRow(j)  就会导致:ConcurrentModificationException、NullPointerException
//     * @param currentSheet 当前工作表
//     * @param sourceColumn 源头列
//     * @param resultColumn 结果列
//     * @param dataSheetName 数据表名
//     */
//    public static void setDataLinkage(Sheet currentSheet,int sourceColumn,int resultColumn,String dataSheetName){
//        Workbook workbook = currentSheet.getWorkbook();
//        CellStyle lockedCellStyle = workbook.createCellStyle();
//        // 设置锁定
//        lockedCellStyle.setLocked(true);
//        // 单元格设置公式
//        for (int j = 1; j <= 100000 ; j++) {
//            // 获取对应行
//            Row formulaRow = currentSheet.getRow(j);
//            if (formulaRow == null) {
//                formulaRow = currentSheet.createRow(j);
//            }
//            // (设置对应列,从左到右,从0开始)
//            Cell formulaCell = formulaRow.createCell(resultColumn);
//            // 下标转字母
//            String sourceName = CellReference.convertNumToColString(sourceColumn);
//            // 正确写法:公式字符串不包含等号
//            formulaCell.setCellFormula("IFERROR(VLOOKUP("+ sourceName + (j+1) + ", "+ dataSheetName +"!$A$2:$B$100000, 2, FALSE), \"\")");
//            formulaCell.setCellStyle(lockedCellStyle);
//        }
//        boolean protect = currentSheet.getProtect();
//        if (!protect){
//            // 保护工作表
//            currentSheet.protectSheet("");
//        }
//    }/*** 设置数据联动(优化版)* 【注】:不支持多线程调用同一个 Sheet** @param currentSheet  当前工作表* @param sourceColumn  源头列* @param resultColumn  结果列* @param dataSheetName 数据表名*/public static void setDataLinkage(Sheet currentSheet, int sourceColumn, int resultColumn, String dataSheetName) {Workbook workbook = currentSheet.getWorkbook();// 复用 CellStyleCellStyle lockedCellStyle = workbook.createCellStyle();lockedCellStyle.setDataFormat(workbook.createDataFormat().getFormat("@"));lockedCellStyle.setLocked(true);// 预计算公式字符串String sourceName = CellReference.convertNumToColString(sourceColumn);String formulaPrefix = "IFERROR(VLOOKUP(" + sourceName;String formulaSuffix = ", " + dataSheetName + "!$A$2:$B$100000, 2, FALSE), \"\")";// 预创建行(可选)final int startRow = 1, endRow = 100000;Row[] rows = new Row[endRow - startRow + 1];for (int j = startRow; j <= endRow; j++) {Row row = currentSheet.getRow(j);if (row == null) {row = currentSheet.createRow(j);}rows[j - startRow] = row;// 设置公式Cell cell = row.createCell(resultColumn);// 先强制指定为字符串cell.setCellValue("");cell.setCellFormula(formulaPrefix + (j + 1) + formulaSuffix);cell.setCellStyle(lockedCellStyle);}// 由调用方统一保护,避免重复if (!currentSheet.getProtect()) {currentSheet.protectSheet("");}}/*** 获取总行数(包含标题行)** @param inputStream* @return* @throws IOException*/public static long getTotalRows(InputStream inputStream) throws IOException {try (Workbook workbook = WorkbookFactory.create(inputStream)) {// 默认读第一个 sheetSheet sheet = workbook.getSheetAt(0);long rowCount = 0;// 遍历所有物理存在的行(跳过空行判断,只计最大行号)for (Row row : sheet) {// 判断是否为空行(可选:更精确统计有效数据行)if (isRowEmpty(row)) {continue; // 如果你想跳过空行,取消这行注释}rowCount++;}// 或者直接获取最后一行的行号(包含空行)// long rowCount = sheet.getLastRowNum() + 1; // 包含标题行return rowCount;}}/*** 判断某行是否为空(所有单元格都为空)*/private static boolean isRowEmpty(Row row) {if (row == null) {return true;}for (int c = row.getFirstCellNum(); c < row.getLastCellNum(); c++) {Cell cell = row.getCell(c);if (cell != null && cell.getCellType() != CellType.BLANK) {return false;}}return true;}}
http://www.dtcms.com/a/516270.html

相关文章:

  • 直接插入排序详解
  • 网站界面切片做程序宁波免费建站seo排名
  • Leetcode 33
  • 济南制作网站制作公司策划采购网有哪些平台
  • conda 换盘符
  • 统一身份认证、权限管理系统设计
  • 福州整站优化网站在线设计
  • 网站如何加入百度网盟重庆市住房和城乡建设厅官方网站
  • 批量删除多个 PDF 文件顶部和底部的文字说明
  • 专题:2025年制造业数智化发展白皮书:数字化转型与智能制造|附130+份报告PDF、数据、绘图模板汇总下载
  • Ubuntu 25.10 “Questing Quokka” 版本解析
  • iOS的动态库和静态库的差异区别
  • AI问答:为什么rust编译器不默认为struct添加#[derive(Debug)]而需要手动添加?
  • 如何正确选择住宅IP?解析适配跨境、流媒体的网络工具
  • 手机网站的文本排版是怎么做的做股东变更要上哪个网站
  • 算法沉淀第九天(Cinema Cashier)
  • 搭建属于自己的网站HEXO静态页(一)
  • [UE学习笔记]—划时代意义的两大功能—lumen和Nanite
  • 杭州协会网站建设公司怎样制作网站
  • Springboot音乐网站系统源码
  • 【css】overflow-x:visible失效:溢出时,想让横轴滚动,竖轴显示
  • 内含32位MCU的无线收发芯片XL2422
  • php开发网站怎么做可以做pos机的网站
  • Jupyter Notebook运行Milvus Lite
  • 双目测距实战5-立体矫正
  • 阿里云配置了加速器还是访问不了docker.io的解决方案。
  • 四川星星建设集团有限公司网站天津建设工程信息王
  • 网站创建人是网站下做二级域名
  • vivado综合报错,但没有明确报错信息
  • 深度解析 DeepSeek-OCR 的“光学压缩”革命