若依 ruoyi poi Excel合并行的导入
本文仅针对文字相关的合并做了处理 ,图片合并及保存需要另做处理!!
目标:Excel合并行内容的导入
结果:
1. ExcelUtil.java 类,新增方法:判断是否是合并行
/*** 新增 合并行相关代码:判断是否是合并行** @param sheet* @param row* @param column* @return*/private boolean isMergedRow(Sheet sheet, int row, int column) {int sheetMergeCount = sheet.getNumMergedRegions();//当前表格(sheet)中,所有合单元格总数//遍历所有合并单元格for (int i = 0; i < sheetMergeCount; i++) {CellRangeAddress cellAddresses = sheet.getMergedRegion(i);//当前合并单元格对象int firstColumn = cellAddresses.getFirstColumn();//当前合并单元格的首列int lastColumn = cellAddresses.getLastColumn();//当前合并单元格的末列int firstRow = cellAddresses.getFirstRow();//当前合并单元格的首行int lastRow = cellAddresses.getLastRow();//当前合并单元格的末行//指定的行 包含在合并单元格中if (row >= firstRow && row <= lastRow) {//指定的列 包含在合并单元格中if (column >= firstColumn && column <= lastColumn) {return true;}}}return false;}
2. ExcelUtil.java 类,新增方法:获取合并行单元格数据
思路:
POI对于Excel的处理:合并行中 只有首列能获取到值 非首列的其他列数据默认是空。由于是合并行,则可以认为 合并行内所有列的数据 都与合并行的首列数据相同。
所以,以下代码中,判断该列如果包含在合并行内,则该列值默认赋值为 所在合并行的首列值。
图1 Excel中的2/3/4行内的合并行内容相同:
POI对Excel处理时,默认获取到的值大概如图2:下面代码则在图2逻辑上 对3/4行的空内容做赋值操作
/*** 新增 合并行相关代码:获取合并行的数据** @param sheet* @param row* @param column* @return*/private String getMergedRegionValue(Sheet sheet, int row, int column) {// 获得该sheet所有合并单元格数量int sheetMergeCount = sheet.getNumMergedRegions();for (int i = 0; i < sheetMergeCount; i++) {// 获得合并区域CellRangeAddress cellAddresses = sheet.getMergedRegion(i);int firstColumn = cellAddresses.getFirstColumn();int lastColumn = cellAddresses.getLastColumn();int firstRow = cellAddresses.getFirstRow();int lastRow = cellAddresses.getLastRow();/*判断传入的单元格的行号列号是否在合并单元格的范围内,如果在合并单元格的范围内,择返回合并区域的首单元格格值*/if (row >= firstRow && row <= lastRow) {if (column >= firstColumn && column <= lastColumn) {Row firRow = sheet.getRow(firstRow);/* 合并行使用的获取单元格内容方法Cell firCell = firRow.getCell(firstColumn);return getCellValue(firCell);*/return getCellValue(firRow, firstColumn).toString();//改为使用 若依自带获取单元格内容方法}}}// 如果该单元格行号列号不在任何一个合并区域,则返回nullreturn null;}
3. ExcelUtil.java的importExcel()方法中 遍历行内所有单元格内容下,新增: 合并行的判断 和 其内单元格内容获取
// ============== 新增 合并行相关代码块 ================// 判断是否合并行 isMergedRow(sheet, rowNum, column):是合并行,则获取合并行数据 重新赋值给当前列
if (isMergedRow(sheet, i, entry.getKey())) val = getMergedRegionValue(sheet, i, entry.getKey());// ============== 新增 合并行相关代码块 ================
importExcel()方法完整代码
/*** 对excel表单指定表格索引名转换成list* * @param sheetName 表格索引名* @param titleNum 标题占用行数* @param is 输入流* @return 转换后集合*/public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception{this.type = Type.IMPORT;this.wb = WorkbookFactory.create(is);List<T> list = new ArrayList<T>();// 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheetSheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);if (sheet == null){throw new IOException("文件sheet不存在");}boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook);//获取文档指定sheet中所有图片:图片位置、文件流Map<String, PictureData> pictures;if (isXSSFWorkbook){pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb);}else{pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb);}// 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1int rows = sheet.getLastRowNum();if (rows > 0){// 定义一个map用于存放excel列的序号和field.Map<String, Integer> cellMap = new HashMap<String, Integer>();// 获取表头Row heard = sheet.getRow(titleNum);for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++){Cell cell = heard.getCell(i);if (StringUtils.isNotNull(cell)){String value = this.getCellValue(heard, i).toString();cellMap.put(value, i);}else{cellMap.put(null, i);}}// 有数据时才处理 得到类的所有field.List<Object[]> fields = this.getFields();Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();for (Object[] objects : fields){Excel attr = (Excel) objects[1];Integer column = cellMap.get(attr.name());if (column != null){fieldsMap.put(column, objects);}}//遍历每一行for (int i = titleNum + 1; i <= rows; i++){// 从第2行开始取数据,默认第一行是表头.Row row = sheet.getRow(i);// 判断当前行是否是空行if (isRowEmpty(row)){continue;}T entity = null;//遍历当前行所有列 逐列字段值获取并赋值给entity对象的各属性for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet()){Object val = this.getCellValue(row, entry.getKey());//获取当前列 默认值// ============== 新增 合并行相关代码块 ================// 判断是否合并行 isMergedRow(sheet, rowNum, column):是合并行,则获取合并行首列数据 重新赋值给当前列if (isMergedRow(sheet, i, entry.getKey())) val = getMergedRegionValue(sheet, i, entry.getKey());// ============== 新增 合并行相关代码块 ================// 如果不存在实例则新建.entity = (entity == null ? clazz.newInstance() : entity);// 从map中得到对应列的field.Field field = (Field) entry.getValue()[0];Excel attr = (Excel) entry.getValue()[1];// 取得类型,并根据对象类型设置值.Class<?> fieldType = field.getType();if (String.class == fieldType){String s = Convert.toStr(val);if (StringUtils.endsWith(s, ".0")){val = StringUtils.substringBefore(s, ".0");}else{String dateFormat = field.getAnnotation(Excel.class).dateFormat();if (StringUtils.isNotEmpty(dateFormat)){val = parseDateToStr(dateFormat, val);}else{val = Convert.toStr(val);}}}else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))){val = Convert.toInt(val);}else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))){val = Convert.toLong(val);}else if (Double.TYPE == fieldType || Double.class == fieldType){val = Convert.toDouble(val);}else if (Float.TYPE == fieldType || Float.class == fieldType){val = Convert.toFloat(val);}else if (BigDecimal.class == fieldType){val = Convert.toBigDecimal(val);}else if (Date.class == fieldType){if (val instanceof String){val = DateUtils.parseDate(val);}else if (val instanceof Double){val = DateUtil.getJavaDate((Double) val);}}else if (Boolean.TYPE == fieldType || Boolean.class == fieldType){val = Convert.toBool(val, false);}if (StringUtils.isNotNull(fieldType)){String propertyName = field.getName();if (StringUtils.isNotEmpty(attr.targetAttr())){propertyName = field.getName() + "." + attr.targetAttr();}if (StringUtils.isNotEmpty(attr.readConverterExp())){val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());}else if (StringUtils.isNotEmpty(attr.dictType())){val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());}else if (!attr.handler().equals(ExcelHandlerAdapter.class)){val = dataFormatHandlerAdapter(val, attr, null);}else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)){//获取指定行指定列中的图片文件:从获取的文件集合中获取PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey());if (image == null){val = "";}else{byte[] data = image.getData();val = FileUtils.writeImportBytes(data);//图片文件写入到 application.yml文件 ruoyi.profile属性对应的文件目录下}}ReflectUtils.invokeSetter(entity, propertyName, val);}}list.add(entity);}}return list;}
最终,工具类 ExcelUtil.java:
package com.ruoyi.common.utils.poi;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFPicture;
import org.apache.poi.hssf.usermodel.HSSFPictureData;
import org.apache.poi.hssf.usermodel.HSSFShape;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Name;
import org.apache.poi.ss.usermodel.PictureData;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFPicture;
import org.apache.poi.xssf.usermodel.XSSFShape;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.annotation.Excel.ColumnType;
import com.ruoyi.common.annotation.Excel.Type;
import com.ruoyi.common.annotation.Excels;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.UtilException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileTypeUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.file.ImageUtils;
import com.ruoyi.common.utils.reflect.ReflectUtils;/*** Excel相关处理* * @author ruoyi*/
public class ExcelUtil<T>
{private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);public static final String FORMULA_REGEX_STR = "=|-|\\+|@";public static final String[] FORMULA_STR = { "=", "-", "+", "@" };/*** 用于dictType属性数据存储,避免重复查缓存*/public Map<String, String> sysDictMap = new HashMap<String, String>();/*** Excel sheet最大行数,默认65536*/public static final int sheetSize = 65536;/*** 工作表名称*/private String sheetName;/*** 导出类型(EXPORT:导出数据;IMPORT:导入模板)*/private Type type;/*** 工作薄对象*/private Workbook wb;/*** 工作表对象*/private Sheet sheet;/*** 样式列表*/private Map<String, CellStyle> styles;/*** 导入导出数据列表*/private List<T> list;/*** 注解列表*/private List<Object[]> fields;/*** 当前行号*/private int rownum;/*** 标题*/private String title;/*** 最大高度*/private short maxHeight;/*** 合并后最后行数*/private int subMergedLastRowNum = 0;/*** 合并后开始行数*/private int subMergedFirstRowNum = 1;/*** 对象的子列表方法*/private Method subMethod;/*** 对象的子列表属性*/private List<Field> subFields;/*** 统计列表*/private Map<Integer, Double> statistics = new HashMap<Integer, Double>();/*** 数字格式*/private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");/*** 实体对象*/public Class<T> clazz;/*** 需要排除列属性*/public String[] excludeFields;public ExcelUtil(Class<T> clazz){this.clazz = clazz;}/*** 隐藏Excel中列属性** @param fields 列属性名 示例[单个"name"/多个"id","name"]* @throws Exception*/public void hideColumn(String... fields){this.excludeFields = fields;}public void init(List<T> list, String sheetName, String title, Type type){if (list == null){list = new ArrayList<T>();}this.list = list;this.sheetName = sheetName;this.type = type;this.title = title;createExcelField();createWorkbook();createTitle();createSubHead();}/*** 创建excel第一行标题*/public void createTitle(){if (StringUtils.isNotEmpty(title)){subMergedFirstRowNum++;subMergedLastRowNum++;int titleLastCol = this.fields.size() - 1;if (isSubList()){titleLastCol = titleLastCol + subFields.size() - 1;}Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0);titleRow.setHeightInPoints(30);Cell titleCell = titleRow.createCell(0);titleCell.setCellStyle(styles.get("title"));titleCell.setCellValue(title);sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol));}}/*** 创建对象的子列表名称*/public void createSubHead(){if (isSubList()){subMergedFirstRowNum++;subMergedLastRowNum++;Row subRow = sheet.createRow(rownum);int excelNum = 0;for (Object[] objects : fields){Excel attr = (Excel) objects[1];Cell headCell1 = subRow.createCell(excelNum);headCell1.setCellValue(attr.name());headCell1.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));excelNum++;}int headFirstRow = excelNum - 1;int headLastRow = headFirstRow + subFields.size() - 1;if (headLastRow > headFirstRow){sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow));}rownum++;}}/*** 对excel表单默认第一个索引名转换成list* * @param is 输入流* @return 转换后集合*/public List<T> importExcel(InputStream is){List<T> list = null;try{list = importExcel(is, 0);}catch (Exception e){log.error("导入Excel异常{}", e.getMessage());throw new UtilException(e.getMessage());}finally{IOUtils.closeQuietly(is);}return list;}/*** 对excel表单默认第一个索引名转换成list* * @param is 输入流* @param titleNum 标题占用行数* @return 转换后集合*/public List<T> importExcel(InputStream is, int titleNum) throws Exception{return importExcel(StringUtils.EMPTY, is, titleNum);}/*** 对excel表单指定表格索引名转换成list* * @param sheetName 表格索引名* @param titleNum 标题占用行数* @param is 输入流* @return 转换后集合*/public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception{this.type = Type.IMPORT;this.wb = WorkbookFactory.create(is);List<T> list = new ArrayList<T>();// 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheetSheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);if (sheet == null){throw new IOException("文件sheet不存在");}boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook);//获取文档指定sheet中所有图片:图片位置、文件流Map<String, PictureData> pictures;if (isXSSFWorkbook){pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb);}else{pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb);}// 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1int rows = sheet.getLastRowNum();if (rows > 0){// 定义一个map用于存放excel列的序号和field.Map<String, Integer> cellMap = new HashMap<String, Integer>();// 获取表头Row heard = sheet.getRow(titleNum);for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++){Cell cell = heard.getCell(i);if (StringUtils.isNotNull(cell)){String value = this.getCellValue(heard, i).toString();cellMap.put(value, i);}else{cellMap.put(null, i);}}// 有数据时才处理 得到类的所有field.List<Object[]> fields = this.getFields();Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();for (Object[] objects : fields){Excel attr = (Excel) objects[1];Integer column = cellMap.get(attr.name());if (column != null){fieldsMap.put(column, objects);}}for (int i = titleNum + 1; i <= rows; i++){// 从第2行开始取数据,默认第一行是表头.Row row = sheet.getRow(i);// 判断当前行是否是空行if (isRowEmpty(row)){continue;}T entity = null;//遍历所有列 逐列字段值获取并赋值给entity对象的各属性for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet()){Object val = this.getCellValue(row, entry.getKey());//获取当前列 默认值// ============== 新增 合并行相关代码块 ================// 判断是否合并行 isMergedRow(sheet, rowNum, column):是合并行,则获取合并行数据 重新赋值给当前列if (isMergedRow(sheet, i, entry.getKey())) val = getMergedRegionValue(sheet, i, entry.getKey());// ============== 新增 合并行相关代码块 ================// 如果不存在实例则新建.entity = (entity == null ? clazz.newInstance() : entity);// 从map中得到对应列的field.Field field = (Field) entry.getValue()[0];Excel attr = (Excel) entry.getValue()[1];// 取得类型,并根据对象类型设置值.Class<?> fieldType = field.getType();if (String.class == fieldType){String s = Convert.toStr(val);if (StringUtils.endsWith(s, ".0")){val = StringUtils.substringBefore(s, ".0");}else{String dateFormat = field.getAnnotation(Excel.class).dateFormat();if (StringUtils.isNotEmpty(dateFormat)){val = parseDateToStr(dateFormat, val);}else{val = Convert.toStr(val);}}}else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))){val = Convert.toInt(val);}else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))){val = Convert.toLong(val);}else if (Double.TYPE == fieldType || Double.class == fieldType){val = Convert.toDouble(val);}else if (Float.TYPE == fieldType || Float.class == fieldType){val = Convert.toFloat(val);}else if (BigDecimal.class == fieldType){val = Convert.toBigDecimal(val);}else if (Date.class == fieldType){if (val instanceof String){val = DateUtils.parseDate(val);}else if (val instanceof Double){val = DateUtil.getJavaDate((Double) val);}}else if (Boolean.TYPE == fieldType || Boolean.class == fieldType){val = Convert.toBool(val, false);}if (StringUtils.isNotNull(fieldType)){String propertyName = field.getName();if (StringUtils.isNotEmpty(attr.targetAttr())){propertyName = field.getName() + "." + attr.targetAttr();}if (StringUtils.isNotEmpty(attr.readConverterExp())){val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());}else if (StringUtils.isNotEmpty(attr.dictType())){val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());}else if (!attr.handler().equals(ExcelHandlerAdapter.class)){val = dataFormatHandlerAdapter(val, attr, null);}else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)){PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey());if (image == null){val = "";}else{byte[] data = image.getData();val = FileUtils.writeImportBytes(data);//图片文件写入到 application.yml文件 ruoyi.profile属性对应的文件目录下}}ReflectUtils.invokeSetter(entity, propertyName, val);}}list.add(entity);}}return list;}/*** 新增 合并行相关代码:判断是否是合并行** @param sheet* @param row* @param column* @return*/private boolean isMergedRow(Sheet sheet, int row, int column) {int sheetMergeCount = sheet.getNumMergedRegions();for (int i = 0; i < sheetMergeCount; i++) {CellRangeAddress cellAddresses = sheet.getMergedRegion(i);int firstColumn = cellAddresses.getFirstColumn();int lastColumn = cellAddresses.getLastColumn();int firstRow = cellAddresses.getFirstRow();int lastRow = cellAddresses.getLastRow();if (row >= firstRow && row <= lastRow) {if (column >= firstColumn && column <= lastColumn) {return true;}}}return false;}/*** 新增 合并行相关代码:获取合并行的数据** @param sheet* @param row* @param column* @return*/private String getMergedRegionValue(Sheet sheet, int row, int column) {// 获得该sheet所有合并单元格数量int sheetMergeCount = sheet.getNumMergedRegions();for (int i = 0; i < sheetMergeCount; i++) {// 获得合并区域CellRangeAddress cellAddresses = sheet.getMergedRegion(i);int firstColumn = cellAddresses.getFirstColumn();int lastColumn = cellAddresses.getLastColumn();int firstRow = cellAddresses.getFirstRow();int lastRow = cellAddresses.getLastRow();/*判断传入的单元格的行号列号是否在合并单元格的范围内,如果在合并单元格的范围内,择返回合并区域的首单元格格值*/if (row >= firstRow && row <= lastRow) {if (column >= firstColumn && column <= lastColumn) {Row firRow = sheet.getRow(firstRow);/* 合并行使用的获取单元格内容方法Cell firCell = firRow.getCell(firstColumn);return getCellValue(firCell);*/return getCellValue(firRow, firstColumn).toString();//改为使用 若依自带获取单元格内容方法}}}// 如果该单元格行号列号不在任何一个合并区域,则返回nullreturn null;}/*** 对list数据源将其里面的数据导入到excel表单* * @param list 导出数据集合* @param sheetName 工作表的名称* @return 结果*/public AjaxResult exportExcel(List<T> list, String sheetName){return exportExcel(list, sheetName, StringUtils.EMPTY);}/*** 对list数据源将其里面的数据导入到excel表单* * @param list 导出数据集合* @param sheetName 工作表的名称* @param title 标题* @return 结果*/public AjaxResult exportExcel(List<T> list, String sheetName, String title){this.init(list, sheetName, title, Type.EXPORT);return exportExcel();}/*** 对list数据源将其里面的数据导入到excel表单* * @param response 返回数据* @param list 导出数据集合* @param sheetName 工作表的名称* @return 结果*/public void exportExcel(HttpServletResponse response, List<T> list, String sheetName){exportExcel(response, list, sheetName, StringUtils.EMPTY);}/*** 对list数据源将其里面的数据导入到excel表单* * @param response 返回数据* @param list 导出数据集合* @param sheetName 工作表的名称* @param title 标题* @return 结果*/public void exportExcel(HttpServletResponse response, List<T> list, String sheetName, String title){response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");this.init(list, sheetName, title, Type.EXPORT);exportExcel(response);}/*** 对list数据源将其里面的数据导入到excel表单* * @param sheetName 工作表的名称* @return 结果*/public AjaxResult importTemplateExcel(String sheetName){return importTemplateExcel(sheetName, StringUtils.EMPTY);}/*** 对list数据源将其里面的数据导入到excel表单* * @param sheetName 工作表的名称* @param title 标题* @return 结果*/public AjaxResult importTemplateExcel(String sheetName, String title){this.init(null, sheetName, title, Type.IMPORT);return exportExcel();}/*** 对list数据源将其里面的数据导入到excel表单* * @param sheetName 工作表的名称* @return 结果*/public void importTemplateExcel(HttpServletResponse response, String sheetName){importTemplateExcel(response, sheetName, StringUtils.EMPTY);}/*** 对list数据源将其里面的数据导入到excel表单* * @param sheetName 工作表的名称* @param title 标题* @return 结果*/public void importTemplateExcel(HttpServletResponse response, String sheetName, String title){response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");this.init(null, sheetName, title, Type.IMPORT);exportExcel(response);}/*** 对list数据源将其里面的数据导入到excel表单* * @return 结果*/public void exportExcel(HttpServletResponse response){try{writeSheet();wb.write(response.getOutputStream());}catch (Exception e){log.error("导出Excel异常{}", e.getMessage());}finally{IOUtils.closeQuietly(wb);}}/*** 对list数据源将其里面的数据导入到excel表单* * @return 结果*/public AjaxResult exportExcel(){OutputStream out = null;try{writeSheet();String filename = encodingFilename(sheetName);out = new FileOutputStream(getAbsoluteFile(filename));wb.write(out);return AjaxResult.success(filename);}catch (Exception e){log.error("导出Excel异常{}", e.getMessage());throw new UtilException("导出Excel失败,请联系网站管理员!");}finally{IOUtils.closeQuietly(wb);IOUtils.closeQuietly(out);}}/*** 创建写入数据到Sheet*/public void writeSheet(){// 取出一共有多少个sheet.int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize));for (int index = 0; index < sheetNo; index++){createSheet(sheetNo, index);// 产生一行Row row = sheet.createRow(rownum);int column = 0;// 写入各个字段的列头名称for (Object[] os : fields){Field field = (Field) os[0];Excel excel = (Excel) os[1];if (Collection.class.isAssignableFrom(field.getType())){for (Field subField : subFields){Excel subExcel = subField.getAnnotation(Excel.class);this.createHeadCell(subExcel, row, column++);}}else{this.createHeadCell(excel, row, column++);}}if (Type.EXPORT.equals(type)){fillExcelData(index, row);addStatisticsRow();}}}/*** 填充excel数据* * @param index 序号* @param row 单元格行*/@SuppressWarnings("unchecked")public void fillExcelData(int index, Row row){int startNo = index * sheetSize;int endNo = Math.min(startNo + sheetSize, list.size());int rowNo = (1 + rownum) - startNo;for (int i = startNo; i < endNo; i++){rowNo = isSubList() ? (i > 1 ? rowNo + 1 : rowNo + i) : i + 1 + rownum - startNo;row = sheet.createRow(rowNo);// 得到导出对象.T vo = (T) list.get(i);Collection<?> subList = null;if (isSubList()){if (isSubListValue(vo)){subList = getListCellValue(vo);subMergedLastRowNum = subMergedLastRowNum + subList.size();}else{subMergedFirstRowNum++;subMergedLastRowNum++;}}int column = 0;for (Object[] os : fields){Field field = (Field) os[0];Excel excel = (Excel) os[1];if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList)){boolean subFirst = false;for (Object obj : subList){if (subFirst){rowNo++;row = sheet.createRow(rowNo);}List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);int subIndex = 0;for (Field subField : subFields){if (subField.isAnnotationPresent(Excel.class)){subField.setAccessible(true);Excel attr = subField.getAnnotation(Excel.class);this.addCell(attr, row, (T) obj, subField, column + subIndex);}subIndex++;}subFirst = true;}this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size();}else{this.addCell(excel, row, vo, field, column++);}}}}/*** 创建表格样式* * @param wb 工作薄对象* @return 样式列表*/private Map<String, CellStyle> createStyles(Workbook wb){// 写入各条记录,每条记录对应excel表中的一行Map<String, CellStyle> styles = new HashMap<String, CellStyle>();CellStyle style = wb.createCellStyle();style.setAlignment(HorizontalAlignment.CENTER);style.setVerticalAlignment(VerticalAlignment.CENTER);Font titleFont = wb.createFont();titleFont.setFontName("Arial");titleFont.setFontHeightInPoints((short) 16);titleFont.setBold(true);style.setFont(titleFont);styles.put("title", style);style = wb.createCellStyle();style.setAlignment(HorizontalAlignment.CENTER);style.setVerticalAlignment(VerticalAlignment.CENTER);style.setBorderRight(BorderStyle.THIN);style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());style.setBorderLeft(BorderStyle.THIN);style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());style.setBorderTop(BorderStyle.THIN);style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());style.setBorderBottom(BorderStyle.THIN);style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());Font dataFont = wb.createFont();dataFont.setFontName("Arial");dataFont.setFontHeightInPoints((short) 10);style.setFont(dataFont);styles.put("data", style);style = wb.createCellStyle();style.setAlignment(HorizontalAlignment.CENTER);style.setVerticalAlignment(VerticalAlignment.CENTER);Font totalFont = wb.createFont();totalFont.setFontName("Arial");totalFont.setFontHeightInPoints((short) 10);style.setFont(totalFont);styles.put("total", style);styles.putAll(annotationHeaderStyles(wb, styles));styles.putAll(annotationDataStyles(wb));return styles;}/*** 根据Excel注解创建表格头样式* * @param wb 工作薄对象* @return 自定义样式列表*/private Map<String, CellStyle> annotationHeaderStyles(Workbook wb, Map<String, CellStyle> styles){Map<String, CellStyle> headerStyles = new HashMap<String, CellStyle>();for (Object[] os : fields){Excel excel = (Excel) os[1];String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor());if (!headerStyles.containsKey(key)){CellStyle style = wb.createCellStyle();style.cloneStyleFrom(styles.get("data"));style.setAlignment(HorizontalAlignment.CENTER);style.setVerticalAlignment(VerticalAlignment.CENTER);style.setFillForegroundColor(excel.headerBackgroundColor().index);style.setFillPattern(FillPatternType.SOLID_FOREGROUND);Font headerFont = wb.createFont();headerFont.setFontName("Arial");headerFont.setFontHeightInPoints((short) 10);headerFont.setBold(true);headerFont.setColor(excel.headerColor().index);style.setFont(headerFont);headerStyles.put(key, style);}}return headerStyles;}/*** 根据Excel注解创建表格列样式* * @param wb 工作薄对象* @return 自定义样式列表*/private Map<String, CellStyle> annotationDataStyles(Workbook wb){Map<String, CellStyle> styles = new HashMap<String, CellStyle>();for (Object[] os : fields){Excel excel = (Excel) os[1];String key = StringUtils.format("data_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor());if (!styles.containsKey(key)){CellStyle style = wb.createCellStyle();style.setAlignment(excel.align());style.setVerticalAlignment(VerticalAlignment.CENTER);style.setBorderRight(BorderStyle.THIN);style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());style.setBorderLeft(BorderStyle.THIN);style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());style.setBorderTop(BorderStyle.THIN);style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());style.setBorderBottom(BorderStyle.THIN);style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());style.setFillPattern(FillPatternType.SOLID_FOREGROUND);style.setFillForegroundColor(excel.backgroundColor().getIndex());Font dataFont = wb.createFont();dataFont.setFontName("Arial");dataFont.setFontHeightInPoints((short) 10);dataFont.setColor(excel.color().index);style.setFont(dataFont);styles.put(key, style);}}return styles;}/*** 创建单元格*/public Cell createHeadCell(Excel attr, Row row, int column){// 创建列Cell cell = row.createCell(column);// 写入列信息cell.setCellValue(attr.name());setDataValidation(attr, row, column);cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())));if (isSubList()){// 填充默认样式,防止合并单元格样式失效sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor())));if (attr.needMerge()){sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column));}}return cell;}/*** 设置单元格信息* * @param value 单元格值* @param attr 注解相关* @param cell 单元格信息*/public void setCellVo(Object value, Excel attr, Cell cell){if (ColumnType.STRING == attr.cellType()){String cellValue = Convert.toStr(value);// 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。if (StringUtils.startsWithAny(cellValue, FORMULA_STR)){cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0");}if (value instanceof Collection && StringUtils.equals("[]", cellValue)){cellValue = StringUtils.EMPTY;}cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix());}else if (ColumnType.NUMERIC == attr.cellType()){if (StringUtils.isNotNull(value)){cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value));}}else if (ColumnType.IMAGE == attr.cellType()){ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1);String imagePath = Convert.toStr(value);if (StringUtils.isNotEmpty(imagePath)){byte[] data = ImageUtils.getImage(imagePath);getDrawingPatriarch(cell.getSheet()).createPicture(anchor,cell.getSheet().getWorkbook().addPicture(data, getImageType(data)));}}}/*** 获取画布*/public static Drawing<?> getDrawingPatriarch(Sheet sheet){if (sheet.getDrawingPatriarch() == null){sheet.createDrawingPatriarch();}return sheet.getDrawingPatriarch();}/*** 获取图片类型,设置图片插入类型*/public int getImageType(byte[] value){String type = FileTypeUtils.getFileExtendName(value);if ("JPG".equalsIgnoreCase(type)){return Workbook.PICTURE_TYPE_JPEG;}else if ("PNG".equalsIgnoreCase(type)){return Workbook.PICTURE_TYPE_PNG;}return Workbook.PICTURE_TYPE_JPEG;}/*** 创建表格样式*/public void setDataValidation(Excel attr, Row row, int column){if (attr.name().indexOf("注:") >= 0){sheet.setColumnWidth(column, 6000);}else{// 设置列宽sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256));}if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0){if (attr.combo().length > 15 || StringUtils.join(attr.combo()).length() > 255){// 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到setXSSFValidationWithHidden(sheet, attr.combo(), attr.prompt(), 1, 100, column, column);}else{// 提示信息或只能选择不能输入的列内容.setPromptOrValidation(sheet, attr.combo(), attr.prompt(), 1, 100, column, column);}}}/*** 添加单元格*/public Cell addCell(Excel attr, Row row, T vo, Field field, int column){Cell cell = null;try{// 设置行高row.setHeight(maxHeight);// 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.if (attr.isExport()){// 创建cellcell = row.createCell(column);if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()){CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column);sheet.addMergedRegion(cellAddress);}cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor())));// 用于读取对象中的属性Object value = getTargetValue(vo, field, attr);String dateFormat = attr.dateFormat();String readConverterExp = attr.readConverterExp();String separator = attr.separator();String dictType = attr.dictType();if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)){cell.setCellValue(parseDateToStr(dateFormat, value));}else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)){cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator));}else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)){if (!sysDictMap.containsKey(dictType + value)){String lable = convertDictByExp(Convert.toStr(value), dictType, separator);sysDictMap.put(dictType + value, lable);}cell.setCellValue(sysDictMap.get(dictType + value));}else if (value instanceof BigDecimal && -1 != attr.scale()){cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue());}else if (!attr.handler().equals(ExcelHandlerAdapter.class)){cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell));}else{// 设置列类型setCellVo(value, attr, cell);}addStatisticsData(column, Convert.toStr(value), attr);}}catch (Exception e){log.error("导出Excel失败{}", e);}return cell;}/*** 设置 POI XSSFSheet 单元格提示或选择框* * @param sheet 表单* @param textlist 下拉框显示的内容* @param promptContent 提示内容* @param firstRow 开始行* @param endRow 结束行* @param firstCol 开始列* @param endCol 结束列*/public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow,int firstCol, int endCol){DataValidationHelper helper = sheet.getDataValidationHelper();DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1");CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);DataValidation dataValidation = helper.createValidation(constraint, regions);if (StringUtils.isNotEmpty(promptContent)){// 如果设置了提示信息则鼠标放上去提示dataValidation.createPromptBox("", promptContent);dataValidation.setShowPromptBox(true);}// 处理Excel兼容性问题if (dataValidation instanceof XSSFDataValidation){dataValidation.setSuppressDropDownArrow(true);dataValidation.setShowErrorBox(true);}else{dataValidation.setSuppressDropDownArrow(false);}sheet.addValidationData(dataValidation);}/*** 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框).* * @param sheet 要设置的sheet.* @param textlist 下拉框显示的内容* @param promptContent 提示内容* @param firstRow 开始行* @param endRow 结束行* @param firstCol 开始列* @param endCol 结束列*/public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol){String hideSheetName = "combo_" + firstCol + "_" + endCol;Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据for (int i = 0; i < textlist.length; i++){hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]);}// 创建名称,可被其他单元格引用Name name = wb.createName();name.setNameName(hideSheetName + "_data");name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length);DataValidationHelper helper = sheet.getDataValidationHelper();// 加载下拉列表内容DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data");// 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);// 数据有效性对象DataValidation dataValidation = helper.createValidation(constraint, regions);if (StringUtils.isNotEmpty(promptContent)){// 如果设置了提示信息则鼠标放上去提示dataValidation.createPromptBox("", promptContent);dataValidation.setShowPromptBox(true);}// 处理Excel兼容性问题if (dataValidation instanceof XSSFDataValidation){dataValidation.setSuppressDropDownArrow(true);dataValidation.setShowErrorBox(true);}else{dataValidation.setSuppressDropDownArrow(false);}sheet.addValidationData(dataValidation);// 设置hiddenSheet隐藏wb.setSheetHidden(wb.getSheetIndex(hideSheet), true);}/*** 解析导出值 0=男,1=女,2=未知* * @param propertyValue 参数值* @param converterExp 翻译注解* @param separator 分隔符* @return 解析后值*/public static String convertByExp(String propertyValue, String converterExp, String separator){StringBuilder propertyString = new StringBuilder();String[] convertSource = converterExp.split(",");for (String item : convertSource){String[] itemArray = item.split("=");if (StringUtils.containsAny(propertyValue, separator)){for (String value : propertyValue.split(separator)){if (itemArray[0].equals(value)){propertyString.append(itemArray[1] + separator);break;}}}else{if (itemArray[0].equals(propertyValue)){return itemArray[1];}}}return StringUtils.stripEnd(propertyString.toString(), separator);}/*** 反向解析值 男=0,女=1,未知=2* * @param propertyValue 参数值* @param converterExp 翻译注解* @param separator 分隔符* @return 解析后值*/public static String reverseByExp(String propertyValue, String converterExp, String separator){StringBuilder propertyString = new StringBuilder();String[] convertSource = converterExp.split(",");for (String item : convertSource){String[] itemArray = item.split("=");if (StringUtils.containsAny(propertyValue, separator)){for (String value : propertyValue.split(separator)){if (itemArray[1].equals(value)){propertyString.append(itemArray[0] + separator);break;}}}else{if (itemArray[1].equals(propertyValue)){return itemArray[0];}}}return StringUtils.stripEnd(propertyString.toString(), separator);}/*** 解析字典值* * @param dictValue 字典值* @param dictType 字典类型* @param separator 分隔符* @return 字典标签*/public static String convertDictByExp(String dictValue, String dictType, String separator){return DictUtils.getDictLabel(dictType, dictValue, separator);}/*** 反向解析值字典值* * @param dictLabel 字典标签* @param dictType 字典类型* @param separator 分隔符* @return 字典值*/public static String reverseDictByExp(String dictLabel, String dictType, String separator){return DictUtils.getDictValue(dictType, dictLabel, separator);}/*** 数据处理器* * @param value 数据值* @param excel 数据注解* @return*/public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell){try{Object instance = excel.handler().newInstance();Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class });value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb);}catch (Exception e){log.error("不能格式化数据 " + excel.handler(), e.getMessage());}return Convert.toStr(value);}/*** 合计统计信息*/private void addStatisticsData(Integer index, String text, Excel entity){if (entity != null && entity.isStatistics()){Double temp = 0D;if (!statistics.containsKey(index)){statistics.put(index, temp);}try{temp = Double.valueOf(text);}catch (NumberFormatException e){}statistics.put(index, statistics.get(index) + temp);}}/*** 创建统计行*/public void addStatisticsRow(){if (statistics.size() > 0){Row row = sheet.createRow(sheet.getLastRowNum() + 1);Set<Integer> keys = statistics.keySet();Cell cell = row.createCell(0);cell.setCellStyle(styles.get("total"));cell.setCellValue("合计");for (Integer key : keys){cell = row.createCell(key);cell.setCellStyle(styles.get("total"));cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));}statistics.clear();}}/*** 编码文件名*/public String encodingFilename(String filename){filename = UUID.randomUUID() + "_" + filename + ".xlsx";return filename;}/*** 获取下载路径* * @param filename 文件名称*/public String getAbsoluteFile(String filename){String downloadPath = RuoYiConfig.getDownloadPath() + filename;File desc = new File(downloadPath);if (!desc.getParentFile().exists()){desc.getParentFile().mkdirs();}return downloadPath;}/*** 获取bean中的属性值* * @param vo 实体对象* @param field 字段* @param excel 注解* @return 最终的属性值* @throws Exception*/private Object getTargetValue(T vo, Field field, Excel excel) throws Exception{Object o = field.get(vo);if (StringUtils.isNotEmpty(excel.targetAttr())){String target = excel.targetAttr();if (target.contains(".")){String[] targets = target.split("[.]");for (String name : targets){o = getValue(o, name);}}else{o = getValue(o, target);}}return o;}/*** 以类的属性的get方法方法形式获取值* * @param o* @param name* @return value* @throws Exception*/private Object getValue(Object o, String name) throws Exception{if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)){Class<?> clazz = o.getClass();Field field = clazz.getDeclaredField(name);field.setAccessible(true);o = field.get(o);}return o;}/*** 得到所有定义字段*/private void createExcelField(){this.fields = getFields();this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());this.maxHeight = getRowHeight();}/*** 获取字段注解信息*/public List<Object[]> getFields(){List<Object[]> fields = new ArrayList<Object[]>();List<Field> tempFields = new ArrayList<>();tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));for (Field field : tempFields){if (!ArrayUtils.contains(this.excludeFields, field.getName())){// 单注解if (field.isAnnotationPresent(Excel.class)){Excel attr = field.getAnnotation(Excel.class);if (attr != null && (attr.type() == Type.ALL || attr.type() == type)){field.setAccessible(true);fields.add(new Object[] { field, attr });}if (Collection.class.isAssignableFrom(field.getType())){subMethod = getSubMethod(field.getName(), clazz);ParameterizedType pt = (ParameterizedType) field.getGenericType();Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);}}// 多注解if (field.isAnnotationPresent(Excels.class)){Excels attrs = field.getAnnotation(Excels.class);Excel[] excels = attrs.value();for (Excel attr : excels){if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr())&& (attr != null && (attr.type() == Type.ALL || attr.type() == type))){field.setAccessible(true);fields.add(new Object[] { field, attr });}}}}}return fields;}/*** 根据注解获取最大行高*/public short getRowHeight(){double maxHeight = 0;for (Object[] os : this.fields){Excel excel = (Excel) os[1];maxHeight = Math.max(maxHeight, excel.height());}return (short) (maxHeight * 20);}/*** 创建一个工作簿*/public void createWorkbook(){this.wb = new SXSSFWorkbook(500);this.sheet = wb.createSheet();wb.setSheetName(0, sheetName);this.styles = createStyles(wb);}/*** 创建工作表* * @param sheetNo sheet数量* @param index 序号*/public void createSheet(int sheetNo, int index){// 设置工作表的名称.if (sheetNo > 1 && index > 0){this.sheet = wb.createSheet();this.createTitle();wb.setSheetName(index, sheetName + index);}}/*** 获取单元格值* * @param row 获取的行* @param column 获取单元格列号* @return 单元格值*/public Object getCellValue(Row row, int column){if (row == null){return row;}Object val = "";try{Cell cell = row.getCell(column);if (StringUtils.isNotNull(cell)){if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA){val = cell.getNumericCellValue();if (DateUtil.isCellDateFormatted(cell)){val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换}else{if ((Double) val % 1 != 0){val = new BigDecimal(val.toString());}else{val = new DecimalFormat("0").format(val);}}}else if (cell.getCellType() == CellType.STRING){val = cell.getStringCellValue();}else if (cell.getCellType() == CellType.BOOLEAN){val = cell.getBooleanCellValue();}else if (cell.getCellType() == CellType.ERROR){val = cell.getErrorCellValue();}}}catch (Exception e){return val;}return val;}/*** 判断是否是空行* * @param row 判断的行* @return*/private boolean isRowEmpty(Row row){if (row == null){return true;}for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++){Cell cell = row.getCell(i);if (cell != null && cell.getCellType() != CellType.BLANK){return false;}}return true;}/*** 获取Excel2003图片** @param sheet 当前sheet对象* @param workbook 工作簿对象* @return Map key:图片单元格索引(1_1)String,value:图片流PictureData*/public static Map<String, PictureData> getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook){Map<String, PictureData> sheetIndexPicMap = new HashMap<String, PictureData>();List<HSSFPictureData> pictures = workbook.getAllPictures();if (!pictures.isEmpty()){for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()){HSSFClientAnchor anchor = (HSSFClientAnchor) shape.getAnchor();if (shape instanceof HSSFPicture){HSSFPicture pic = (HSSFPicture) shape;int pictureIndex = pic.getPictureIndex() - 1;HSSFPictureData picData = pictures.get(pictureIndex);String picIndex = anchor.getRow1() + "_" + anchor.getCol1();sheetIndexPicMap.put(picIndex, picData);}}return sheetIndexPicMap;}else{return sheetIndexPicMap;}}/*** 获取Excel2007图片** @param sheet 当前sheet对象* @param workbook 工作簿对象* @return Map key:图片单元格索引(1_1)String,value:图片流PictureData*/public static Map<String, PictureData> getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook){Map<String, PictureData> sheetIndexPicMap = new HashMap<String, PictureData>();for (POIXMLDocumentPart dr : sheet.getRelations()){if (dr instanceof XSSFDrawing){XSSFDrawing drawing = (XSSFDrawing) dr;List<XSSFShape> shapes = drawing.getShapes();for (XSSFShape shape : shapes){if (shape instanceof XSSFPicture){XSSFPicture pic = (XSSFPicture) shape;XSSFClientAnchor anchor = pic.getPreferredSize();CTMarker ctMarker = anchor.getFrom();String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol();sheetIndexPicMap.put(picIndex, pic.getPictureData());}}}}return sheetIndexPicMap;}/*** 格式化不同类型的日期对象* * @param dateFormat 日期格式* @param val 被格式化的日期对象* @return 格式化后的日期字符*/public String parseDateToStr(String dateFormat, Object val){if (val == null){return "";}String str;if (val instanceof Date){str = DateUtils.parseDateToStr(dateFormat, (Date) val);}else if (val instanceof LocalDateTime){str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val));}else if (val instanceof LocalDate){str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val));}else{str = val.toString();}return str;}/*** 是否有对象的子列表*/public boolean isSubList(){return StringUtils.isNotNull(subFields) && subFields.size() > 0;}/*** 是否有对象的子列表,集合不为空*/public boolean isSubListValue(T vo){return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0;}/*** 获取集合的值*/public Collection<?> getListCellValue(Object obj){Object value;try{value = subMethod.invoke(obj, new Object[] {});}catch (Exception e){return new ArrayList<Object>();}return (Collection<?>) value;}/*** 获取对象的子列表方法* * @param name 名称* @param pojoClass 类对象* @return 子列表方法*/public Method getSubMethod(String name, Class<?> pojoClass){StringBuffer getMethodName = new StringBuffer("get");getMethodName.append(name.substring(0, 1).toUpperCase());getMethodName.append(name.substring(1));Method method = null;try{method = pojoClass.getMethod(getMethodName.toString(), new Class[] {});}catch (Exception e){log.error("获取对象异常{}", e.getMessage());}return method;}
}
======================================================
参考自:
https://www.iteye.com/blog/357029540-2438298
下面工具类为该文下的POI处理表格数据的源码:
package com.chinamobile.util;import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;/*** @author liaoyubo* @version 1.0* @date 2019/2/27* @description*/
@Slf4j
public class POIUtils {private final static String XLS = "xls";private final static String XLSX = "xlsx";private static SimpleDateFormat sdf;/*** 读入excel文件,解析后返回** @param file* @throws IOException*/public static List<String[]> readExcel(MultipartFile file) throws IOException {//检查文件checkFile(file);//获得Workbook工作薄对象Workbook workbook = getWorkBook(file);//创建返回对象,把每行中的值作为一个数组,所有行作为一个集合返回List<String[]> list = new ArrayList<>();if (workbook != null) {for (int sheetNum = 0; sheetNum < workbook.getNumberOfSheets(); sheetNum++) {//获得当前sheet工作表Sheet sheet = workbook.getSheetAt(sheetNum);if (sheet == null) {continue;}//获得当前sheet的开始行int firstRowNum = sheet.getFirstRowNum();//获得当前sheet的结束行int lastRowNum = sheet.getLastRowNum();//循环除了第一行的所有行list = rowList(sheet,firstRowNum + 1,lastRowNum);}workbook.close();}return list;}public static List<String[]> readExcel(MultipartFile file,int sheetNum,int startRow) throws IOException {//检查文件checkFile(file);//获得Workbook工作薄对象Workbook workbook = getWorkBook(file);//创建返回对象,把每行中的值作为一个数组,所有行作为一个集合返回List<String[]> list = new ArrayList<>();if (workbook != null) {//获得当前sheet工作表Sheet sheet = workbook.getSheetAt(sheetNum);if (!Optional.ofNullable(sheet).isPresent()) {return null;}//获得当前sheet的结束行int lastRowNum = sheet.getLastRowNum();//循环指定的所有行list = rowList(sheet,startRow,lastRowNum);workbook.close();}return list;}/*** 检查文件是否正确** @param file* @throws IOException*/private static void checkFile(MultipartFile file) throws IOException {//判断文件是否存在if (null == file) {log.error("文件不存在!");throw new FileNotFoundException("文件不存在!");}//获得文件名String fileName = file.getOriginalFilename();//判断文件是否是excel文件if (!fileName.endsWith(XLS) && !fileName.endsWith(XLSX)) {log.error(fileName + "不是excel文件");throw new IOException(fileName + "不是excel文件");}}/*** 对文件解析** @param file* @return*/private static Workbook getWorkBook(MultipartFile file) {//获得文件名String fileName = file.getOriginalFilename();//创建Workbook工作薄对象,表示整个excelWorkbook workbook = null;try {//获取excel文件的io流InputStream is = file.getInputStream();//根据文件后缀名不同(xls和xlsx)获得不同的Workbook实现类对象if (fileName.endsWith(XLS)) {//2003workbook = new HSSFWorkbook(is);} else if (fileName.endsWith(XLSX)) {//2007workbook = new XSSFWorkbook(is);}} catch (IOException e) {log.info(e.getMessage());}return workbook;}/*** 判断是否是合并行** @param sheet* @param row* @param column* @return*/private static boolean isMergedRow(Sheet sheet, int row, int column) {int sheetMergeCount = sheet.getNumMergedRegions();for (int i = 0; i < sheetMergeCount; i++) {CellRangeAddress cellAddresses = sheet.getMergedRegion(i);int firstColumn = cellAddresses.getFirstColumn();int lastColumn = cellAddresses.getLastColumn();int firstRow = cellAddresses.getFirstRow();int lastRow = cellAddresses.getLastRow();if (row >= firstRow && row <= lastRow) {if (column >= firstColumn && column <= lastColumn) {return true;}}}return false;}/*** 获取合并行的数据** @param sheet* @param row* @param column* @return*/private static String getMergedRegionValue(Sheet sheet, int row, int column) {// 获得该sheet所有合并单元格数量int sheetMergeCount = sheet.getNumMergedRegions();for (int i = 0; i < sheetMergeCount; i++) {// 获得合并区域CellRangeAddress cellAddresses = sheet.getMergedRegion(i);int firstColumn = cellAddresses.getFirstColumn();int lastColumn = cellAddresses.getLastColumn();int firstRow = cellAddresses.getFirstRow();int lastRow = cellAddresses.getLastRow();/*判断传入的单元格的行号列号是否在合并单元格的范围内,如果在合并单元格的范围内,择返回合并区域的首单元格格值*/if (row >= firstRow && row <= lastRow) {if (column >= firstColumn && column <= lastColumn) {Row firRow = sheet.getRow(firstRow);Cell firCell = firRow.getCell(firstColumn);return getCellValue(firCell);}}}// 如果该单元格行号列号不在任何一个合并区域,则返回nullreturn null;}/*** 获取行的集合* @param sheet* @param startRow* @param lastRowNum* @return*/private static List<String[]> rowList(Sheet sheet,int startRow,int lastRowNum){List<String[]> list = new ArrayList<>();for (int rowNum = startRow; rowNum <= lastRowNum; rowNum++) {//获得当前行Row row = sheet.getRow(rowNum);if (!Optional.ofNullable(row).isPresent()) {continue;}//获得当前行的开始列int firstColumn = row.getFirstCellNum();//获得当前行的列数int lastColumn = row.getPhysicalNumberOfCells();String[] cells = new String[lastColumn];//行内所有列的值//循环当前行的所有列for (int column = firstColumn; column < lastColumn; column++) {Cell cell = row.getCell(column);//遍历中的某列// 判断是否合并行boolean isMerge = isMergedRow(sheet, rowNum, column);if (isMerge) {//是合并行,则获取合并行数据cells[column] = getMergedRegionValue(sheet, rowNum, column);} else {cells[column] = getCellValue(cell);//默认当前列内值}}list.add(cells);}return list;}/*** 获取单元格值** @param cell* @return*/private static String getCellValue(Cell cell) {String cellValue = "";if (cell == null) {return cellValue;}// 把数字当成String来读,避免出现1读成1.0的情况if (cell.getCellType() == CellType.NUMERIC) {cell.setCellType(CellType.STRING);}// 判断数据的类型switch (cell.getCellType()) {// 文本case STRING:cellValue = cell.getStringCellValue();break;// 数字、日期case NUMERIC:if (DateUtil.isCellDateFormatted(cell)) {sdf = new SimpleDateFormat("yyyy/MM/dd");// 日期型cellValue = sdf.format(cell.getDateCellValue());} else {// 数字cellValue = String.valueOf(cell.getNumericCellValue());}break;// 布尔型case BOOLEAN:cellValue = String.valueOf(cell.getBooleanCellValue());break;// 空白case BLANK:cellValue = cell.getStringCellValue();break;// 错误case ERROR:cellValue = "";break;// 公式case FORMULA:cellValue = "";break;default:cellValue = "";}return cellValue;}}
==========================================
其他POI处理合并行的参考:(未验证是否可用)
https://www.cnblogs.com/zhou-pan/p/10037438.html
相关文章:

若依 ruoyi poi Excel合并行的导入
本文仅针对文字相关的合并做了处理 ,图片合并及保存需要另做处理!! 目标:Excel合并行内容的导入 结果: 1. ExcelUtil.java 类,新增方法:判断是否是合并行 /*** 新增 合并行相关代码:…...

优化算法:1.遗传算法(GA)及Python实现
一、定义 遗传算法就像是在模拟“优胜劣汰”的进化过程,通过选择最优秀的个体,交配产生下一代,并引入一定的变异,逐步优化解决问题。 二、具体步骤 初始化种群(Initialization): 假设你要找到一个迷宫的最佳出口路径。…...

企业化运维(8)Docker容器技术
###1.Docker介绍### 什么是Docker Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间…...

Unity C#底层原理(二)
委托 方法的容器:委托可以存储一个或多个方法的引用。可以使用委托对象来调用这些方法。函数/方法的变量类型:委托类型可以像变量一样声明和使用,存储方法的引用。存储、传递方法:委托可以作为参数传递给方法,也可以作…...

计算机网络-配置路由器ACL(访问控制列表)
配置访问控制列表ACL 拓扑结构 拓扑结构如下: 要配置一个ACL,禁止PC0访问PC3,禁止PC4访问PC0,其它正常。 配置Router0 配置接口IP地址: interface fastethernet 0/0 ip address 192.168.1.1 255.255.255.0 no shu…...

51单片机嵌入式开发:20、STC89C52R基于C51嵌入式点阵广告屏的设计
STC89C52R基于C51嵌入式点阵广告屏的设计 1 概述2 LED点阵介绍2.1 特点和优势2.2 工作原理:2.3 使用方法: 3 LED点阵原理3.1 Led点阵内部电路3.2 原理图电路3.3 74HC595 4 软件实现点阵图案的滑动4.1 软件工程代码4.2 Protues仿真 5 总结 配套示例程序 1…...

VLC输出NDI媒体流
目录 1. 下载安装VLC Play 2. 首先在电脑上安装NDI Tools 3. 运行VLC进行输出配置 4. 播放视频 5. 验证 (1)用Studio Monitor验证 (2)用OBS验证 NDI(Network Device Interface)即网络设备接口,是由美国 NewTek 公司开发的免费标准,它可使兼容的视频产品以高质量…...

WiFi 局域网通信 - 发现服务和解析
1. nsdManager nsdManager requireContext().getSystemService(Context.NSD_SERVICE) as NsdManager2. NsdManager.DiscoveryListener 注意:在onStartDiscoveryFailed 和 onStopDiscoveryFailed里不要调用nsdManager.stopServiceDiscovery(this) 方法࿰…...

ChatGPT建议前端学习计划
HTML&CSS基础 - 学习HTML标签、CSS属性、页面布局等基础知识 JavaScript基础 - 学习变量、数据类型、控制流、函数等基础知识 jQuery - 学习如何使用jQuery处理文档对象模型(DOM)、事件、动画等 Ajax - 全称为 Asynchronous JavaScript and XML&…...

YOLO5项目目录最强解析
YOLO5项目目录解析 YOLOv5 项目目录下的文件和目录的结构,以下是对每个目录和文件的解释: 目录 📁 .github: 存放 GitHub 相关配置和文件,如 GitHub Actions 工作流文件、Issue 模板等,用于自动化构建和持续集成等功…...

【python】sklearn基础教程及示例
【python】sklearn基础教程及示例 Scikit-learn(简称sklearn)是一个非常流行的Python机器学习库,提供了许多常用的机器学习算法和工具。以下是一个基础教程的概述: 1. 安装scikit-learn 首先,确保你已经安装了Python和…...

Linux:传输层(2) -- TCP协议(2)
目录 1. 流量控制 2. 滑动窗口 3. 拥塞控制 4. 延迟应答 5. 捎带应答 6. 面向字节流 7. 粘包问题 8. TCP异常情况 1. 流量控制 接收端处理数据的速度是有限的. 如果发送端发的太快 , 导致接收端的缓冲区被打满 , 这个时候如果发送端继续发送 , 就会造成丢包, 继而引…...

AcWing 802. 区间和
var说明add存储了插入操作,在指定 x x x下标所在位置 a [ x ] c a[x]c a[x]cquery是求 [ L , R ] [L,R] [L,R]区间和用到的数组,最后才用到alls 是存储离散化之后的值 , 对于会访问到的每个下标,统统丢到 a l l s 里面 ,会把 x 和 [ L , R …...

实验2-2-1 温度转换
#include<stdio.h> #include <math.h> int main(){int c,f150;c5*(f-32)/9;printf("fahr 150, celsius %d",c); }...

Spark实时(六):Output Sinks案例演示
文章目录 Output Sinks案例演示 一、File sink 二、Memory Sink 三、Foreach Sink 1、foreachBatch 2、foreach Output Sinks案例演示 当我们对流式…...

在SQL编程中DROP、DELETE和TRUNCATE的区别
在SQL编程中,DROP、DELETE和TRUNCATE都是用于删除数据的命令,但它们之间有着显著的区别,主要体现在它们删除数据的范围、操作的不可逆性、对表结构的影响、性能以及事务日志的影响上。 DROP: 作用:DROP命令用于删除整个表及其所有…...

【AI大模型】Prompt 提示词工程使用详解
目录 一、前言 二、Prompt 提示词工程介绍 2.1 Prompt提示词工程是什么 2.1.1 Prompt 构成要素 2.2 Prompt 提示词工程有什么作用 2.2.1 Prompt 提示词工程使用场景 2.3 为什么要学习Prompt 提示词工程 三、Prompt 提示词工程元素构成与操作实践 3.1 前置准备 3.2 Pro…...

学习记录day18——数据结构 算法
算法的相关概念 程序 数据结构 算法 算法是程序设计的灵魂,结构式程序设计的肉体 算法:计算机解决问题的方法护额步骤 算法的特性 1、确定性:算法中每一条语句都有确定的含义,不能模棱两可 2、有穷性:程序执行一…...

一篇文章带你学完Java所有的时间与日期类
目录 一、传统时间与日期类 1.Date类 构造方法 获取日期和时间信息的方法 设置日期和时间信息的方法 2.Calendar类 主要特点和功能 常用方法 1. 获取当前日历对象 2. 获取日历中的某个信息 3. 获取日期对象 4. 获取时间毫秒值 5. 修改日历的某个信息 6. 为某个信息增…...

利用GPT4o Captcha工具和AI技术全面识别验证码
利用GPT4o Captcha工具和AI技术全面识别验证码 🧠🚀 摘要 GPT4o Captcha工具是一款命令行工具,通过Python和Selenium测试各种类型的验证码,包括拼图、文本、复杂文本和reCAPTCHA,并使用OpenAI GPT-4帮助解决验证码问…...

大学生算法高等数学学习平台设计方案 (第一版)
目录 目标用户群体的精准定位 初阶探索者 进阶学习者 资深研究者 功能需求的深度拓展 个性化学习路径定制 概念图谱构建 公式推导展示 交互式问题解决系统 新功能和创新点的引入 虚拟教室环境 数学建模工具集成 算法可视化平台 学术论文资源库 技术实现的前瞻性…...

机器学习算法与Python实战 | 两行代码即可应用 40 个机器学习模型--lazypredict 库!
本文来源公众号“机器学习算法与Python实战”,仅用于学术分享,侵权删,干货满满。 原文链接:两行代码即可应用 40 个机器学习模型 今天和大家一起学习使用 lazypredict 库,我们可以用一行代码在我们的数据集上实现许多…...

使用WebSocket协议调用群发方法将消息返回客户端页面
目录 一.C/S架构: 二.Http协议与WebSocket协议的区别: 1.Http协议与WebSocket协议的区别: 2.WebSocket协议的使用场景: 三.项目实际操作: 1.导入依赖: 2.通过WebSocket实现页面与服务端保持长连接&a…...

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十七章 Linux中断实验
i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…...

每日一题~961div2A+B+C(阅读题,思维,数学log)
A 题意:给你 n*n 的表格和k 个筹码。每个格子上至多放一个 问至少占据多少对角线。 显然,要先 格数的多的格子去放。 n n-1 n-2 …1 只有n 的是一个(主对角线),其他的是两个。 #include <bits/stdc.h> using na…...

Fireflyrk3288 ubuntu18.04添加Qt开发环境、安装mysql-server
1、创建一台同版本的ubuntu18.04的虚拟机 2、下载rk3288_ubuntu_18.04_armhf_ext4_v2.04_20201125-1538_DESKTOP.img 3、创建空img镜像容器 dd if/dev/zero ofubuntu_rootfs.img bs1M count102404、将该容器格式化成ext4文件系统 mkfs.ext4 ubuntu_rootfs.img5、将该镜像文件…...

简化mybatis @Select IN条件的编写
最近从JPA切换到Mybatis,使用无XML配置,Select注解直接写到interface上,发现IN条件的编写相当麻烦。 一般得写成这样: Select({"<script>","SELECT *", "FROM blog","WHERE id IN&quo…...

Windows图形界面(GUI)-MFC-C/C++ - Control
公开视频 -> 链接点击跳转公开课程博客首页 -> 链接点击跳转博客主页 目录 Control 资源编辑器 添加控件 设置控件属性 添加控件变量 添加消息处理 处理控件事件 控件焦点顺序 Control 资源编辑器 资源编辑器:用于可视化地编辑对话框和控件。…...

SQL Server数据库安全:策略制定与实践指南
SQL Server数据库安全:策略制定与实践指南 在当今数字化时代,数据安全是每个组织的核心关注点。SQL Server作为广泛使用的关系型数据库管理系统,提供了一套强大的安全特性来保护存储的数据。制定有效的数据库安全策略是确保数据完整性、可用…...

Spring Boot入门指南:留言板
一.留言板 1.输⼊留⾔信息,点击提交.后端把数据存储起来. 2.⻚⾯展⽰输⼊的表⽩墙的信息 规范: 1.写一个类MessageInfo对象,添加构造方法 虽然有快捷键,但是还是不够偷懒 项目添加Lombok。 Lombok是⼀个Java⼯具库,通过添加注…...