|
@@ -0,0 +1,867 @@
|
|
|
|
|
+package cn.gov.customs.wxjy.common.utils.poi;
|
|
|
|
|
+
|
|
|
|
|
+import cn.gov.customs.wxjy.common.annotation.Excel;
|
|
|
|
|
+import cn.gov.customs.wxjy.common.core.text.Convert;
|
|
|
|
|
+import cn.gov.customs.wxjy.common.exception.UtilException;
|
|
|
|
|
+import cn.gov.customs.wxjy.common.utils.DateUtils;
|
|
|
|
|
+import cn.gov.customs.wxjy.common.utils.DictUtils;
|
|
|
|
|
+import cn.gov.customs.wxjy.common.utils.StringUtils;
|
|
|
|
|
+import cn.gov.customs.wxjy.common.utils.reflect.ReflectUtils;
|
|
|
|
|
+import org.apache.commons.lang3.ArrayUtils;
|
|
|
|
|
+import org.apache.poi.ss.usermodel.*;
|
|
|
|
|
+import org.apache.poi.ss.util.CellRangeAddress;
|
|
|
|
|
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
|
+
|
|
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.io.OutputStream;
|
|
|
|
|
+import java.lang.reflect.Field;
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.net.URLEncoder;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 动态列Excel导出工具类(优化版)
|
|
|
|
|
+ * 支持动态列导出、注解配置、样式处理、数据转换、字典转换等功能
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param <T> 数据模型类型
|
|
|
|
|
+ */
|
|
|
|
|
+public class DynamicExcelUtil<T> {
|
|
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(DynamicExcelUtil.class);
|
|
|
|
|
+
|
|
|
|
|
+ // 公式特殊字符,用于防止CSV注入
|
|
|
|
|
+ private static final String[] FORMULA_STR = { "=", "-", "+", "@" };
|
|
|
|
|
+ private static final String FORMULA_REGEX_STR = "=|-|\\+|@";
|
|
|
|
|
+
|
|
|
|
|
+ private Class<T> clazz;
|
|
|
|
|
+ private List<T> dataList;
|
|
|
|
|
+ private Set<String> exportFields;
|
|
|
|
|
+ private Map<String, Excel> excelAnnotationMap;
|
|
|
|
|
+ private Map<String, CellStyle> styles;
|
|
|
|
|
+ private Workbook workbook;
|
|
|
|
|
+ private Sheet sheet;
|
|
|
|
|
+ private List<Object[]> sortedFieldList; // 排序后的字段列表 [Field, Excel注解]
|
|
|
|
|
+ private String title;
|
|
|
|
|
+ private String sheetName;
|
|
|
|
|
+ private boolean includeTitle = false;
|
|
|
|
|
+ private int currentRowNum = 0;
|
|
|
|
|
+ private Map<Integer, Double> statistics = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 字典缓存,避免重复查询
|
|
|
|
|
+ private Map<String, String> dictCache = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构造器
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param clazz 数据模型类
|
|
|
|
|
+ */
|
|
|
|
|
+ public DynamicExcelUtil(Class<T> clazz) {
|
|
|
|
|
+ this.clazz = clazz;
|
|
|
|
|
+ this.exportFields = new LinkedHashSet<>(); // 保持顺序
|
|
|
|
|
+ this.excelAnnotationMap = new HashMap<>();
|
|
|
|
|
+ this.sortedFieldList = new ArrayList<>();
|
|
|
|
|
+ initExcelAnnotations();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 初始化Excel注解映射
|
|
|
|
|
+ */
|
|
|
|
|
+ private void initExcelAnnotations() {
|
|
|
|
|
+ // 获取所有字段(包括父类)
|
|
|
|
|
+ List<Field> allFields = new ArrayList<>();
|
|
|
|
|
+ Class<?> currentClass = clazz;
|
|
|
|
|
+ while (currentClass != null && currentClass != Object.class) {
|
|
|
|
|
+ allFields.addAll(Arrays.asList(currentClass.getDeclaredFields()));
|
|
|
|
|
+ currentClass = currentClass.getSuperclass();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 收集Excel注解
|
|
|
|
|
+ for (Field field : allFields) {
|
|
|
|
|
+ if (field.isAnnotationPresent(Excel.class)) {
|
|
|
|
|
+ Excel excel = field.getAnnotation(Excel.class);
|
|
|
|
|
+ excelAnnotationMap.put(field.getName(), excel);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置需要导出的字段(字符串数组)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param fieldNames 字段名数组
|
|
|
|
|
+ * @return 当前实例
|
|
|
|
|
+ */
|
|
|
|
|
+ public DynamicExcelUtil<T> setExportFields(String... fieldNames) {
|
|
|
|
|
+ if (fieldNames != null) {
|
|
|
|
|
+ exportFields.addAll(Arrays.asList(fieldNames));
|
|
|
|
|
+ }
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置需要导出的字段(集合)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param fieldNames 字段名集合
|
|
|
|
|
+ * @return 当前实例
|
|
|
|
|
+ */
|
|
|
|
|
+ public DynamicExcelUtil<T> setExportFields(Collection<String> fieldNames) {
|
|
|
|
|
+ if (fieldNames != null) {
|
|
|
|
|
+ exportFields.addAll(fieldNames);
|
|
|
|
|
+ }
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置需要导出的字段(逗号分隔的字符串)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param fieldNamesStr 逗号分隔的字段名字符串
|
|
|
|
|
+ * @return 当前实例
|
|
|
|
|
+ */
|
|
|
|
|
+ public DynamicExcelUtil<T> setExportFieldsByString(String fieldNamesStr) {
|
|
|
|
|
+ if (StringUtils.isNotEmpty(fieldNamesStr)) {
|
|
|
|
|
+ String[] fieldArray = fieldNamesStr.split(",");
|
|
|
|
|
+ for (String field : fieldArray) {
|
|
|
|
|
+ String trimmedField = field.trim();
|
|
|
|
|
+ if (StringUtils.isNotEmpty(trimmedField)) {
|
|
|
|
|
+ exportFields.add(trimmedField);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置数据列表
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param dataList 数据列表
|
|
|
|
|
+ * @return 当前实例
|
|
|
|
|
+ */
|
|
|
|
|
+ public DynamicExcelUtil<T> setData(List<T> dataList) {
|
|
|
|
|
+ this.dataList = dataList;
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置标题
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param title 标题
|
|
|
|
|
+ * @return 当前实例
|
|
|
|
|
+ */
|
|
|
|
|
+ public DynamicExcelUtil<T> setTitle(String title) {
|
|
|
|
|
+ this.title = title;
|
|
|
|
|
+ this.includeTitle = StringUtils.isNotEmpty(title);
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置工作表名称
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param sheetName 工作表名称
|
|
|
|
|
+ * @return 当前实例
|
|
|
|
|
+ */
|
|
|
|
|
+ public DynamicExcelUtil<T> setSheetName(String sheetName) {
|
|
|
|
|
+ this.sheetName = sheetName;
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取所有可导出的字段名称(按注解sort排序)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return 字段名称列表
|
|
|
|
|
+ */
|
|
|
|
|
+ public List<String> getAllExportableFields() {
|
|
|
|
|
+ List<String> fields = new ArrayList<>(excelAnnotationMap.keySet());
|
|
|
|
|
+ // 按注解的sort属性排序
|
|
|
|
|
+ fields.sort(Comparator.comparingInt(fieldName -> {
|
|
|
|
|
+ Excel excel = excelAnnotationMap.get(fieldName);
|
|
|
|
|
+ return excel != null ? excel.sort() : 0;
|
|
|
|
|
+ }));
|
|
|
|
|
+ return fields;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 准备导出数据
|
|
|
|
|
+ */
|
|
|
|
|
+ private void prepareExport() {
|
|
|
|
|
+ // 如果没有指定导出字段,导出所有有注解的字段(按sort排序)
|
|
|
|
|
+ if (exportFields.isEmpty()) {
|
|
|
|
|
+ // 获取所有字段并按sort排序
|
|
|
|
|
+ List<Map.Entry<String, Excel>> sortedEntries = excelAnnotationMap.entrySet()
|
|
|
|
|
+ .stream()
|
|
|
|
|
+ .sorted(Comparator.comparingInt(entry -> entry.getValue().sort()))
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ for (Map.Entry<String, Excel> entry : sortedEntries) {
|
|
|
|
|
+ exportFields.add(entry.getKey());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 过滤掉不存在的字段
|
|
|
|
|
+ exportFields.removeIf(fieldName -> !excelAnnotationMap.containsKey(fieldName));
|
|
|
|
|
+
|
|
|
|
|
+ // 获取字段对象和注解
|
|
|
|
|
+ sortedFieldList.clear();
|
|
|
|
|
+ for (String fieldName : exportFields) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Field field = getField(clazz, fieldName);
|
|
|
|
|
+ if (field != null) {
|
|
|
|
|
+ Excel excel = excelAnnotationMap.get(fieldName);
|
|
|
|
|
+ sortedFieldList.add(new Object[]{field, excel});
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (NoSuchFieldException e) {
|
|
|
|
|
+ log.warn("字段 {} 在类 {} 中不存在", fieldName, clazz.getName());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果没有指定顺序,按注解的sort排序
|
|
|
|
|
+ if (sortedFieldList.size() > 1) {
|
|
|
|
|
+ sortedFieldList.sort(Comparator.comparingInt(o -> ((Excel) o[1]).sort()));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 递归获取字段(包括父类)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return clazz.getDeclaredField(fieldName);
|
|
|
|
|
+ } catch (NoSuchFieldException e) {
|
|
|
|
|
+ Class<?> superClass = clazz.getSuperclass();
|
|
|
|
|
+ if (superClass != null && superClass != Object.class) {
|
|
|
|
|
+ return getField(superClass, fieldName);
|
|
|
|
|
+ }
|
|
|
|
|
+ throw e;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建工作簿和样式
|
|
|
|
|
+ */
|
|
|
|
|
+ private void createWorkbookAndStyles() {
|
|
|
|
|
+ // 创建SXSSFWorkbook,支持大数据量
|
|
|
|
|
+ this.workbook = new SXSSFWorkbook(500);
|
|
|
|
|
+ this.sheet = workbook.createSheet(StringUtils.isNotEmpty(sheetName) ? sheetName : "Sheet1");
|
|
|
|
|
+
|
|
|
|
|
+ // 创建样式
|
|
|
|
|
+ createStyles();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建样式
|
|
|
|
|
+ */
|
|
|
|
|
+ private void createStyles() {
|
|
|
|
|
+ this.styles = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 标题样式
|
|
|
|
|
+ CellStyle titleStyle = workbook.createCellStyle();
|
|
|
|
|
+ titleStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
+ titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
|
+ Font titleFont = workbook.createFont();
|
|
|
|
|
+ titleFont.setFontName("宋体");
|
|
|
|
|
+ titleFont.setFontHeightInPoints((short) 16);
|
|
|
|
|
+ titleFont.setBold(true);
|
|
|
|
|
+ titleStyle.setFont(titleFont);
|
|
|
|
|
+ styles.put("title", titleStyle);
|
|
|
|
|
+
|
|
|
|
|
+ // 表头样式 - 默认样式
|
|
|
|
|
+ CellStyle headerStyle = workbook.createCellStyle();
|
|
|
|
|
+ headerStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
+ headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
|
+ headerStyle.setBorderTop(BorderStyle.THIN);
|
|
|
|
|
+ headerStyle.setBorderBottom(BorderStyle.THIN);
|
|
|
|
|
+ headerStyle.setBorderLeft(BorderStyle.THIN);
|
|
|
|
|
+ headerStyle.setBorderRight(BorderStyle.THIN);
|
|
|
|
|
+ headerStyle.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
|
|
+ headerStyle.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
|
|
+ headerStyle.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
|
|
+ headerStyle.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
|
|
+ headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
|
|
|
|
|
+ headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
+ Font headerFont = workbook.createFont();
|
|
|
|
|
+ headerFont.setFontName("宋体");
|
|
|
|
|
+ headerFont.setFontHeightInPoints((short) 11);
|
|
|
|
|
+ headerFont.setBold(true);
|
|
|
|
|
+ headerStyle.setFont(headerFont);
|
|
|
|
|
+ styles.put("header", headerStyle);
|
|
|
|
|
+
|
|
|
|
|
+ // 数据样式 - 默认样式
|
|
|
|
|
+ CellStyle dataStyle = workbook.createCellStyle();
|
|
|
|
|
+ dataStyle.setAlignment(HorizontalAlignment.LEFT);
|
|
|
|
|
+ dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
|
+ dataStyle.setBorderTop(BorderStyle.THIN);
|
|
|
|
|
+ dataStyle.setBorderBottom(BorderStyle.THIN);
|
|
|
|
|
+ dataStyle.setBorderLeft(BorderStyle.THIN);
|
|
|
|
|
+ dataStyle.setBorderRight(BorderStyle.THIN);
|
|
|
|
|
+ dataStyle.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
|
|
+ dataStyle.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
|
|
+ dataStyle.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
|
|
+ dataStyle.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
|
|
|
|
|
+ Font dataFont = workbook.createFont();
|
|
|
|
|
+ dataFont.setFontName("宋体");
|
|
|
|
|
+ dataFont.setFontHeightInPoints((short) 10);
|
|
|
|
|
+ dataStyle.setFont(dataFont);
|
|
|
|
|
+ styles.put("data", dataStyle);
|
|
|
|
|
+
|
|
|
|
|
+ // 创建数字样式
|
|
|
|
|
+ createNumberStyles();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建日期样式
|
|
|
|
|
+ createDateStyles();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建文本样式(防止科学计数法)
|
|
|
|
|
+ CellStyle textStyle = workbook.createCellStyle();
|
|
|
|
|
+ textStyle.cloneStyleFrom(dataStyle);
|
|
|
|
|
+ DataFormat textFormat = workbook.createDataFormat();
|
|
|
|
|
+ textStyle.setDataFormat(textFormat.getFormat("@"));
|
|
|
|
|
+ styles.put("text", textStyle);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建数字样式
|
|
|
|
|
+ */
|
|
|
|
|
+ private void createNumberStyles() {
|
|
|
|
|
+ // 普通数字样式
|
|
|
|
|
+ CellStyle numberStyle = workbook.createCellStyle();
|
|
|
|
|
+ numberStyle.cloneStyleFrom(styles.get("data"));
|
|
|
|
|
+ numberStyle.setAlignment(HorizontalAlignment.RIGHT);
|
|
|
|
|
+ DataFormat numberFormat = workbook.createDataFormat();
|
|
|
|
|
+ numberStyle.setDataFormat(numberFormat.getFormat("#,##0"));
|
|
|
|
|
+ styles.put("number", numberStyle);
|
|
|
|
|
+
|
|
|
|
|
+ // 两位小数样式
|
|
|
|
|
+ CellStyle decimalStyle = workbook.createCellStyle();
|
|
|
|
|
+ decimalStyle.cloneStyleFrom(styles.get("data"));
|
|
|
|
|
+ decimalStyle.setAlignment(HorizontalAlignment.RIGHT);
|
|
|
|
|
+ DataFormat decimalFormat = workbook.createDataFormat();
|
|
|
|
|
+ decimalStyle.setDataFormat(decimalFormat.getFormat("#,##0.00"));
|
|
|
|
|
+ styles.put("decimal", decimalStyle);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建日期样式
|
|
|
|
|
+ */
|
|
|
|
|
+ private void createDateStyles() {
|
|
|
|
|
+ // 日期样式
|
|
|
|
|
+ CellStyle dateStyle = workbook.createCellStyle();
|
|
|
|
|
+ dateStyle.cloneStyleFrom(styles.get("data"));
|
|
|
|
|
+ dateStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
+ DataFormat dateFormat = workbook.createDataFormat();
|
|
|
|
|
+ dateStyle.setDataFormat(dateFormat.getFormat("yyyy-mm-dd"));
|
|
|
|
|
+ styles.put("date", dateStyle);
|
|
|
|
|
+
|
|
|
|
|
+ // 日期时间样式
|
|
|
|
|
+ CellStyle datetimeStyle = workbook.createCellStyle();
|
|
|
|
|
+ datetimeStyle.cloneStyleFrom(styles.get("data"));
|
|
|
|
|
+ datetimeStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
+ DataFormat datetimeFormat = workbook.createDataFormat();
|
|
|
|
|
+ datetimeStyle.setDataFormat(datetimeFormat.getFormat("yyyy-mm-dd hh:mm:ss"));
|
|
|
|
|
+ styles.put("datetime", datetimeStyle);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建标题行
|
|
|
|
|
+ */
|
|
|
|
|
+ private void createTitleRow() {
|
|
|
|
|
+ if (includeTitle && StringUtils.isNotEmpty(title)) {
|
|
|
|
|
+ Row titleRow = sheet.createRow(currentRowNum++);
|
|
|
|
|
+ titleRow.setHeightInPoints(30);
|
|
|
|
|
+
|
|
|
|
|
+ // 创建标题单元格
|
|
|
|
|
+ Cell titleCell = titleRow.createCell(0);
|
|
|
|
|
+ titleCell.setCellValue(title);
|
|
|
|
|
+ titleCell.setCellStyle(styles.get("title"));
|
|
|
|
|
+
|
|
|
|
|
+ // 合并单元格
|
|
|
|
|
+ int lastCol = Math.max(0, sortedFieldList.size() - 1);
|
|
|
|
|
+ if (lastCol > 0) {
|
|
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 0, lastCol));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建表头行
|
|
|
|
|
+ */
|
|
|
|
|
+ private void createHeaderRow() {
|
|
|
|
|
+ Row headerRow = sheet.createRow(currentRowNum++);
|
|
|
|
|
+ headerRow.setHeightInPoints(25);
|
|
|
|
|
+
|
|
|
|
|
+ int colIndex = 0;
|
|
|
|
|
+ for (Object[] fieldInfo : sortedFieldList) {
|
|
|
|
|
+ Excel excel = (Excel) fieldInfo[1];
|
|
|
|
|
+ Cell cell = headerRow.createCell(colIndex);
|
|
|
|
|
+ cell.setCellValue(excel.name());
|
|
|
|
|
+ cell.setCellStyle(styles.get("header"));
|
|
|
|
|
+
|
|
|
|
|
+ // 设置列宽,width()返回double,需要转换为int
|
|
|
|
|
+ setColumnWidth(colIndex, excel);
|
|
|
|
|
+
|
|
|
|
|
+ colIndex++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置列宽
|
|
|
|
|
+ */
|
|
|
|
|
+ private void setColumnWidth(int colIndex, Excel excel) {
|
|
|
|
|
+ double widthValue = excel.width();
|
|
|
|
|
+ int width;
|
|
|
|
|
+
|
|
|
|
|
+ if (widthValue > 0) {
|
|
|
|
|
+ // width()返回double,转换为int,公式为(width + 0.72) * 256
|
|
|
|
|
+ width = (int) ((widthValue + 0.72) * 256);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 根据列名长度自动设置宽度
|
|
|
|
|
+ int nameLength = excel.name().getBytes().length;
|
|
|
|
|
+ width = Math.max(nameLength * 256 + 512, 2000);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置最大最小限制
|
|
|
|
|
+ width = Math.min(width, 255 * 256); // 最大255个字符
|
|
|
|
|
+ width = Math.max(width, 1500); // 最小约6个字符
|
|
|
|
|
+
|
|
|
|
|
+ sheet.setColumnWidth(colIndex, width);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 填充数据行
|
|
|
|
|
+ */
|
|
|
|
|
+ private void fillDataRows() {
|
|
|
|
|
+ if (dataList == null || dataList.isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (T item : dataList) {
|
|
|
|
|
+ Row dataRow = sheet.createRow(currentRowNum++);
|
|
|
|
|
+ dataRow.setHeightInPoints(20);
|
|
|
|
|
+
|
|
|
|
|
+ int colIndex = 0;
|
|
|
|
|
+ for (Object[] fieldInfo : sortedFieldList) {
|
|
|
|
|
+ Field field = (Field) fieldInfo[0];
|
|
|
|
|
+ Excel excel = (Excel) fieldInfo[1];
|
|
|
|
|
+
|
|
|
|
|
+ Cell cell = dataRow.createCell(colIndex);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ field.setAccessible(true);
|
|
|
|
|
+ Object value = field.get(item);
|
|
|
|
|
+
|
|
|
|
|
+ // 应用单元格样式
|
|
|
|
|
+ applyCellStyle(cell, excel);
|
|
|
|
|
+
|
|
|
|
|
+ // 设置单元格值
|
|
|
|
|
+ setCellValue(cell, value, excel);
|
|
|
|
|
+
|
|
|
|
|
+ // 统计
|
|
|
|
|
+ addStatistics(colIndex, value, excel);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("设置单元格值失败,字段:{}", field.getName(), e);
|
|
|
|
|
+ cell.setCellValue("");
|
|
|
|
|
+ cell.setCellStyle(styles.get("data"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ colIndex++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 应用单元格样式
|
|
|
|
|
+ */
|
|
|
|
|
+ private void applyCellStyle(Cell cell, Excel excel) {
|
|
|
|
|
+ CellStyle style = getCellStyle(excel);
|
|
|
|
|
+ cell.setCellStyle(style);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据注解获取单元格样式
|
|
|
|
|
+ */
|
|
|
|
|
+ private CellStyle getCellStyle(Excel excel) {
|
|
|
|
|
+ String styleKey;
|
|
|
|
|
+
|
|
|
|
|
+ // 根据单元格类型确定样式
|
|
|
|
|
+ switch (excel.cellType()) {
|
|
|
|
|
+ case NUMERIC:
|
|
|
|
|
+ // 根据scale判断小数位数
|
|
|
|
|
+ if (excel.scale() > 0) {
|
|
|
|
|
+ styleKey = "decimal";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ styleKey = "number";
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ case TEXT:
|
|
|
|
|
+ styleKey = "text";
|
|
|
|
|
+ break;
|
|
|
|
|
+ // 在若依框架的Excel.ColumnType中,没有DATE和DATETIME枚举值
|
|
|
|
|
+ // 日期格式化是通过dateFormat属性实现的
|
|
|
|
|
+ default:
|
|
|
|
|
+ styleKey = "data";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ CellStyle baseStyle = styles.get(styleKey);
|
|
|
|
|
+
|
|
|
|
|
+ // 克隆样式以避免修改全局样式
|
|
|
|
|
+ CellStyle cellStyle = workbook.createCellStyle();
|
|
|
|
|
+ cellStyle.cloneStyleFrom(baseStyle);
|
|
|
|
|
+
|
|
|
|
|
+ // 设置水平对齐方式
|
|
|
|
|
+ switch (excel.align()) {
|
|
|
|
|
+ case LEFT:
|
|
|
|
|
+ cellStyle.setAlignment(HorizontalAlignment.LEFT);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case CENTER:
|
|
|
|
|
+ cellStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case RIGHT:
|
|
|
|
|
+ cellStyle.setAlignment(HorizontalAlignment.RIGHT);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return cellStyle;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置单元格值
|
|
|
|
|
+ */
|
|
|
|
|
+ private void setCellValue(Cell cell, Object value, Excel excel) {
|
|
|
|
|
+ if (value == null) {
|
|
|
|
|
+ cell.setCellValue(excel.defaultValue());
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 首先转换单元格值
|
|
|
|
|
+ String cellValue = convertCellValue(value, excel);
|
|
|
|
|
+
|
|
|
|
|
+ // 防止CSV注入
|
|
|
|
|
+ if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) {
|
|
|
|
|
+ cellValue = "\t" + cellValue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 根据单元格类型设置值
|
|
|
|
|
+ switch (excel.cellType()) {
|
|
|
|
|
+ case NUMERIC:
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (value instanceof Number) {
|
|
|
|
|
+ double numValue = ((Number) value).doubleValue();
|
|
|
|
|
+ // 如果有scale设置,进行格式化
|
|
|
|
|
+ if (excel.scale() >= 0) {
|
|
|
|
|
+ BigDecimal bd = BigDecimal.valueOf(numValue);
|
|
|
|
|
+ bd = bd.setScale(excel.scale(), excel.roundingMode());
|
|
|
|
|
+ cell.setCellValue(bd.doubleValue());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ cell.setCellValue(numValue);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ cell.setCellValue(Double.parseDouble(cellValue));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ cell.setCellValue(cellValue);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ // 对于日期类型,如果设置了dateFormat,这里cellValue已经是格式化后的字符串
|
|
|
|
|
+ cell.setCellValue(StringUtils.isNull(cellValue) ? excel.defaultValue() : cellValue);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 转换单元格值
|
|
|
|
|
+ */
|
|
|
|
|
+ private String convertCellValue(Object value, Excel excel) {
|
|
|
|
|
+ if (value == null) {
|
|
|
|
|
+ return excel.defaultValue();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String strValue = value.toString();
|
|
|
|
|
+
|
|
|
|
|
+ // 日期格式化 - 如果有dateFormat属性,优先使用
|
|
|
|
|
+ if (StringUtils.isNotEmpty(excel.dateFormat())) {
|
|
|
|
|
+ return parseDateToStr(excel.dateFormat(), value);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 字典转换
|
|
|
|
|
+ if (StringUtils.isNotEmpty(excel.dictType())) {
|
|
|
|
|
+ String cacheKey = excel.dictType() + "_" + strValue;
|
|
|
|
|
+ if (!dictCache.containsKey(cacheKey)) {
|
|
|
|
|
+ String dictLabel = DictUtils.getDictLabel(excel.dictType(), strValue, excel.separator());
|
|
|
|
|
+ dictCache.put(cacheKey, dictLabel != null ? dictLabel : strValue);
|
|
|
|
|
+ }
|
|
|
|
|
+ return dictCache.get(cacheKey);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 读取转换器(例如:0=男,1=女)
|
|
|
|
|
+ if (StringUtils.isNotEmpty(excel.readConverterExp())) {
|
|
|
|
|
+ return convertByExp(strValue, excel.readConverterExp(), excel.separator());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return strValue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 添加统计信息
|
|
|
|
|
+ */
|
|
|
|
|
+ private void addStatistics(int columnIndex, Object value, Excel excel) {
|
|
|
|
|
+ if (excel.isStatistics() && value != null) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ double numValue = 0;
|
|
|
|
|
+ if (value instanceof Number) {
|
|
|
|
|
+ numValue = ((Number) value).doubleValue();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ numValue = Double.parseDouble(value.toString());
|
|
|
|
|
+ }
|
|
|
|
|
+ statistics.merge(columnIndex, numValue, Double::sum);
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ // 忽略非数字值
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建统计行
|
|
|
|
|
+ */
|
|
|
|
|
+ private void createStatisticsRow() {
|
|
|
|
|
+ if (!statistics.isEmpty()) {
|
|
|
|
|
+ Row statRow = sheet.createRow(currentRowNum++);
|
|
|
|
|
+ Cell totalCell = statRow.createCell(0);
|
|
|
|
|
+ totalCell.setCellValue("合计");
|
|
|
|
|
+ totalCell.setCellStyle(styles.get("header"));
|
|
|
|
|
+
|
|
|
|
|
+ for (Map.Entry<Integer, Double> entry : statistics.entrySet()) {
|
|
|
|
|
+ Cell cell = statRow.createCell(entry.getKey());
|
|
|
|
|
+ cell.setCellValue(entry.getValue());
|
|
|
|
|
+
|
|
|
|
|
+ // 应用数字样式
|
|
|
|
|
+ CellStyle numberStyle = workbook.createCellStyle();
|
|
|
|
|
+ numberStyle.cloneStyleFrom(styles.get("decimal"));
|
|
|
|
|
+ cell.setCellStyle(numberStyle);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 日期格式化
|
|
|
|
|
+ */
|
|
|
|
|
+ private String parseDateToStr(String dateFormat, Object value) {
|
|
|
|
|
+ if (value == null) {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value instanceof Date) {
|
|
|
|
|
+ return DateUtils.parseDateToStr(dateFormat, (Date) value);
|
|
|
|
|
+ }
|
|
|
|
|
+ return value.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析导出值 0=男,1=女,2=未知
|
|
|
|
|
+ */
|
|
|
|
|
+ private String convertByExp(String propertyValue, String converterExp, String separator) {
|
|
|
|
|
+ if (StringUtils.isEmpty(propertyValue) || StringUtils.isEmpty(converterExp)) {
|
|
|
|
|
+ return propertyValue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ StringBuilder result = new StringBuilder();
|
|
|
|
|
+ String[] convertSource = converterExp.split(",");
|
|
|
|
|
+
|
|
|
|
|
+ // 处理多个值的情况(用分隔符分隔)
|
|
|
|
|
+ String[] propertyValues = propertyValue.split(separator);
|
|
|
|
|
+
|
|
|
|
|
+ for (String propVal : propertyValues) {
|
|
|
|
|
+ boolean found = false;
|
|
|
|
|
+ for (String item : convertSource) {
|
|
|
|
|
+ String[] itemArray = item.split("=");
|
|
|
|
|
+ if (itemArray.length == 2 && propVal.trim().equals(itemArray[0].trim())) {
|
|
|
|
|
+ if (result.length() > 0) {
|
|
|
|
|
+ result.append(separator);
|
|
|
|
|
+ }
|
|
|
|
|
+ result.append(itemArray[1].trim());
|
|
|
|
|
+ found = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果没有找到匹配,使用原值
|
|
|
|
|
+ if (!found) {
|
|
|
|
|
+ if (result.length() > 0) {
|
|
|
|
|
+ result.append(separator);
|
|
|
|
|
+ }
|
|
|
|
|
+ result.append(propVal.trim());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 导出Excel到HttpServletResponse
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param response HttpServletResponse
|
|
|
|
|
+ * @param fileName 文件名(不需要后缀)
|
|
|
|
|
+ * @throws IOException 异常
|
|
|
|
|
+ */
|
|
|
|
|
+ public void exportToResponse(HttpServletResponse response, String fileName) throws IOException {
|
|
|
|
|
+ // 准备数据
|
|
|
|
|
+ prepareExport();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建工作簿和样式
|
|
|
|
|
+ createWorkbookAndStyles();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建标题行
|
|
|
|
|
+ createTitleRow();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建表头行
|
|
|
|
|
+ createHeaderRow();
|
|
|
|
|
+
|
|
|
|
|
+ // 填充数据
|
|
|
|
|
+ fillDataRows();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建统计行
|
|
|
|
|
+ createStatisticsRow();
|
|
|
|
|
+
|
|
|
|
|
+ // 设置响应头
|
|
|
|
|
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
|
|
|
+ response.setCharacterEncoding("UTF-8");
|
|
|
|
|
+
|
|
|
|
|
+ // 对文件名进行URL编码
|
|
|
|
|
+ String encodedFileName = URLEncoder.encode(
|
|
|
|
|
+ fileName + ".xlsx", StandardCharsets.UTF_8.toString())
|
|
|
|
|
+ .replaceAll("\\+", "%20");
|
|
|
|
|
+ response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
|
|
|
|
|
+
|
|
|
|
|
+ // 写入响应流
|
|
|
|
|
+ workbook.write(response.getOutputStream());
|
|
|
|
|
+
|
|
|
|
|
+ // 清理临时文件(SXSSFWorkbook会生成临时文件)
|
|
|
|
|
+ if (workbook instanceof SXSSFWorkbook) {
|
|
|
|
|
+ ((SXSSFWorkbook) workbook).dispose();
|
|
|
|
|
+ }
|
|
|
|
|
+ workbook.close();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 导出Excel(简化方法)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param response HttpServletResponse
|
|
|
|
|
+ * @param dataList 数据列表
|
|
|
|
|
+ * @param sheetName 工作表名
|
|
|
|
|
+ * @throws IOException 异常
|
|
|
|
|
+ */
|
|
|
|
|
+ public void exportExcel(HttpServletResponse response, List<T> dataList, String sheetName) throws IOException {
|
|
|
|
|
+ setData(dataList)
|
|
|
|
|
+ .setSheetName(sheetName)
|
|
|
|
|
+ .exportToResponse(response, sheetName);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 导出Excel(完整方法)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param response HttpServletResponse
|
|
|
|
|
+ * @param dataList 数据列表
|
|
|
|
|
+ * @param sheetName 工作表名
|
|
|
|
|
+ * @param title 标题
|
|
|
|
|
+ * @param exportFields 导出的字段(逗号分隔的字符串)
|
|
|
|
|
+ * @throws IOException 异常
|
|
|
|
|
+ */
|
|
|
|
|
+ public void exportExcel(HttpServletResponse response, List<T> dataList,
|
|
|
|
|
+ String sheetName, String title, String exportFields) throws IOException {
|
|
|
|
|
+ setData(dataList)
|
|
|
|
|
+ .setSheetName(sheetName)
|
|
|
|
|
+ .setTitle(title)
|
|
|
|
|
+ .setExportFieldsByString(exportFields)
|
|
|
|
|
+ .exportToResponse(response, sheetName);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 导出Excel(完整方法,数组参数)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param response HttpServletResponse
|
|
|
|
|
+ * @param dataList 数据列表
|
|
|
|
|
+ * @param sheetName 工作表名
|
|
|
|
|
+ * @param title 标题
|
|
|
|
|
+ * @param exportFields 导出的字段数组
|
|
|
|
|
+ * @throws IOException 异常
|
|
|
|
|
+ */
|
|
|
|
|
+ public void exportExcel(HttpServletResponse response, List<T> dataList,
|
|
|
|
|
+ String sheetName, String title, String... exportFields) throws IOException {
|
|
|
|
|
+ setData(dataList)
|
|
|
|
|
+ .setSheetName(sheetName)
|
|
|
|
|
+ .setTitle(title)
|
|
|
|
|
+ .setExportFields(exportFields)
|
|
|
|
|
+ .exportToResponse(response, sheetName);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 导出到文件
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param filePath 文件路径
|
|
|
|
|
+ * @throws IOException 异常
|
|
|
|
|
+ */
|
|
|
|
|
+ public void exportToFile(String filePath) throws IOException {
|
|
|
|
|
+ prepareExport();
|
|
|
|
|
+ createWorkbookAndStyles();
|
|
|
|
|
+ createTitleRow();
|
|
|
|
|
+ createHeaderRow();
|
|
|
|
|
+ fillDataRows();
|
|
|
|
|
+ createStatisticsRow();
|
|
|
|
|
+
|
|
|
|
|
+ try (OutputStream fos = new java.io.FileOutputStream(filePath)) {
|
|
|
|
|
+ workbook.write(fos);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (workbook instanceof SXSSFWorkbook) {
|
|
|
|
|
+ ((SXSSFWorkbook) workbook).dispose();
|
|
|
|
|
+ }
|
|
|
|
|
+ workbook.close();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 导出到字节数组
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return 字节数组
|
|
|
|
|
+ * @throws IOException 异常
|
|
|
|
|
+ */
|
|
|
|
|
+ public byte[] exportToBytes() throws IOException {
|
|
|
|
|
+ prepareExport();
|
|
|
|
|
+ createWorkbookAndStyles();
|
|
|
|
|
+ createTitleRow();
|
|
|
|
|
+ createHeaderRow();
|
|
|
|
|
+ fillDataRows();
|
|
|
|
|
+ createStatisticsRow();
|
|
|
|
|
+
|
|
|
|
|
+ try (java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream()) {
|
|
|
|
|
+ workbook.write(baos);
|
|
|
|
|
+ return baos.toByteArray();
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (workbook instanceof SXSSFWorkbook) {
|
|
|
|
|
+ ((SXSSFWorkbook) workbook).dispose();
|
|
|
|
|
+ }
|
|
|
|
|
+ workbook.close();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 清空缓存和状态
|
|
|
|
|
+ */
|
|
|
|
|
+ public void clear() {
|
|
|
|
|
+ this.exportFields.clear();
|
|
|
|
|
+ this.dictCache.clear();
|
|
|
|
|
+ this.statistics.clear();
|
|
|
|
|
+ this.sortedFieldList.clear();
|
|
|
|
|
+ this.title = null;
|
|
|
|
|
+ this.sheetName = null;
|
|
|
|
|
+ this.includeTitle = false;
|
|
|
|
|
+ this.currentRowNum = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取导出字段列表(用于调试)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return 导出字段列表
|
|
|
|
|
+ */
|
|
|
|
|
+ public List<String> getExportFieldList() {
|
|
|
|
|
+ return new ArrayList<>(exportFields);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|