!52 feat(excel): 新增EasyExcel分批导入,支持字典校验和Hibernate Validator
* feat(excel): 新增EasyExcel分批导入,支持字典校验和Hibernate Validator * feat(excel): 新增EasyExcel导出工具类,基于@Excel注解解析并支持模板表头下载 * chore: 添加easyexcel依赖
This commit is contained in:
@@ -327,5 +327,14 @@
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-starter-chatgpt</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+95
-21
@@ -18,7 +18,9 @@ import org.jeecgframework.poi.excel.entity.ExportParams;
|
||||
import org.jeecgframework.poi.excel.entity.ImportParams;
|
||||
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
|
||||
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
|
||||
import org.jeecg.common.util.excel.ExcelAnnotationUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
@@ -27,6 +29,7 @@ import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -37,12 +40,16 @@ import java.util.*;
|
||||
*/
|
||||
@Slf4j
|
||||
public class JeecgController<T, S extends IService<T>> {
|
||||
/**issues/2933 JeecgController注入service时改用protected修饰,能避免重复引用service*/
|
||||
/**
|
||||
* issues/2933 JeecgController注入service时改用protected修饰,能避免重复引用service
|
||||
*/
|
||||
@Autowired
|
||||
protected S service;
|
||||
@Resource
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
|
||||
|
||||
private final int BATCH_NUM = 500;
|
||||
|
||||
/**
|
||||
* 导出excel
|
||||
*
|
||||
@@ -57,7 +64,7 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
String selections = request.getParameter("selections");
|
||||
if (oConvertUtils.isNotEmpty(selections)) {
|
||||
List<String> selectionList = Arrays.asList(selections.split(","));
|
||||
queryWrapper.in("id",selectionList);
|
||||
queryWrapper.in("id", selectionList);
|
||||
}
|
||||
// Step.2 获取导出数据
|
||||
List<T> exportList = service.list(queryWrapper);
|
||||
@@ -68,53 +75,54 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
mv.addObject(NormalExcelConstants.FILE_NAME, title);
|
||||
mv.addObject(NormalExcelConstants.CLASS, clazz);
|
||||
//update-begin--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置--------------------
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
|
||||
ExportParams exportParams = new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title);
|
||||
exportParams.setImageBasePath(jeecgBaseConfig.getPath().getUpload());
|
||||
//update-end--Author:liusq Date:20210126 for:图片导出报错,ImageBasePath未设置----------------------
|
||||
mv.addObject(NormalExcelConstants.PARAMS,exportParams);
|
||||
mv.addObject(NormalExcelConstants.PARAMS, exportParams);
|
||||
mv.addObject(NormalExcelConstants.DATA_LIST, exportList);
|
||||
return mv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据每页sheet数量导出多sheet
|
||||
*
|
||||
* @param request
|
||||
* @param object 实体类
|
||||
* @param clazz 实体类class
|
||||
* @param title 标题
|
||||
* @param object 实体类
|
||||
* @param clazz 实体类class
|
||||
* @param title 标题
|
||||
* @param exportFields 导出字段自定义
|
||||
* @param pageNum 每个sheet的数据条数
|
||||
* @param pageNum 每个sheet的数据条数
|
||||
* @param request
|
||||
*/
|
||||
protected ModelAndView exportXlsSheet(HttpServletRequest request, T object, Class<T> clazz, String title,String exportFields,Integer pageNum) {
|
||||
protected ModelAndView exportXlsSheet(HttpServletRequest request, T object, Class<T> clazz, String title, String exportFields, Integer pageNum) {
|
||||
// Step.1 组装查询条件
|
||||
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
// Step.2 计算分页sheet数据
|
||||
double total = service.count();
|
||||
int count = (int)Math.ceil(total/pageNum);
|
||||
int count = (int) Math.ceil(total / pageNum);
|
||||
//update-begin-author:liusq---date:20220629--for: 多sheet导出根据选择导出写法调整 ---
|
||||
// Step.3 过滤选中数据
|
||||
String selections = request.getParameter("selections");
|
||||
if (oConvertUtils.isNotEmpty(selections)) {
|
||||
List<String> selectionList = Arrays.asList(selections.split(","));
|
||||
queryWrapper.in("id",selectionList);
|
||||
queryWrapper.in("id", selectionList);
|
||||
}
|
||||
//update-end-author:liusq---date:20220629--for: 多sheet导出根据选择导出写法调整 ---
|
||||
// Step.4 多sheet处理
|
||||
List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
|
||||
for (int i = 1; i <=count ; i++) {
|
||||
for (int i = 1; i <= count; i++) {
|
||||
Page<T> page = new Page<T>(i, pageNum);
|
||||
IPage<T> pageList = service.page(page, queryWrapper);
|
||||
List<T> exportList = pageList.getRecords();
|
||||
Map<String, Object> map = new HashMap<>(5);
|
||||
ExportParams exportParams=new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title+i,jeecgBaseConfig.getPath().getUpload());
|
||||
ExportParams exportParams = new ExportParams(title + "报表", "导出人:" + sysUser.getRealname(), title + i, jeecgBaseConfig.getPath().getUpload());
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
//map.put("title",exportParams);
|
||||
//表格Title
|
||||
map.put(NormalExcelConstants.PARAMS,exportParams);
|
||||
map.put(NormalExcelConstants.PARAMS, exportParams);
|
||||
//表格对应实体
|
||||
map.put(NormalExcelConstants.CLASS,clazz);
|
||||
map.put(NormalExcelConstants.CLASS, clazz);
|
||||
//数据集合
|
||||
map.put(NormalExcelConstants.DATA_LIST, exportList);
|
||||
listMap.add(map);
|
||||
@@ -133,12 +141,42 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
protected ModelAndView exportXls(HttpServletRequest request, T object, Class<T> clazz, String title,String exportFields) {
|
||||
ModelAndView mv = this.exportXls(request,object,clazz,title);
|
||||
mv.addObject(NormalExcelConstants.EXPORT_FIELDS,exportFields);
|
||||
protected ModelAndView exportXls(HttpServletRequest request, T object, Class<T> clazz, String title, String exportFields) {
|
||||
ModelAndView mv = this.exportXls(request, object, clazz, title);
|
||||
mv.addObject(NormalExcelConstants.EXPORT_FIELDS, exportFields);
|
||||
return mv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过EasyExcel导出excel,基于 @ExcelProperty 注解读取列配置
|
||||
*/
|
||||
protected void exportXlsByEasyExcel(HttpServletRequest request, HttpServletResponse response,
|
||||
T object, Class<T> clazz, String title) throws IOException {
|
||||
QueryWrapper<T> queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
|
||||
|
||||
String selections = request.getParameter("selections");
|
||||
if (oConvertUtils.isNotEmpty(selections)) {
|
||||
List<String> selectionList = Arrays.asList(selections.split(","));
|
||||
queryWrapper.in("id", selectionList);
|
||||
}
|
||||
|
||||
List<T> exportList = service.list(queryWrapper);
|
||||
writeExcel(response, clazz, title, exportList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅导出Excel表头(不含数据),基于 @ExcelProperty 注解读取列配置
|
||||
*/
|
||||
protected void exportXlsHeadersByEasyExcel(HttpServletResponse response,
|
||||
Class<T> clazz, String title) throws IOException {
|
||||
writeExcel(response, clazz, title, Collections.emptyList());
|
||||
}
|
||||
|
||||
private void writeExcel(HttpServletResponse response, Class<T> clazz,
|
||||
String title, List<T> data) throws IOException {
|
||||
ExcelAnnotationUtils.write(response, clazz, title, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象ID
|
||||
*
|
||||
@@ -153,6 +191,42 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过EasyExcel导入数据,基于 @Excel 注解读取列配置
|
||||
*/
|
||||
protected Result<?> importExcelByEasyExcel(HttpServletRequest request, Class<T> clazz) {
|
||||
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
|
||||
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
|
||||
MultipartFile file = fileMap.values().stream().findFirst().orElse(null);
|
||||
if (file == null) {
|
||||
return Result.error("未找到上传文件!");
|
||||
}
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
long start = System.currentTimeMillis();
|
||||
List<ExcelAnnotationUtils.ValidationError> errors = new ArrayList<>();
|
||||
int total = ExcelAnnotationUtils.read(
|
||||
inputStream,
|
||||
clazz,
|
||||
batch -> service.saveBatch(batch),
|
||||
BATCH_NUM,
|
||||
errors::addAll);
|
||||
log.info("【EasyExcel导入】导入完成, 数据行数: {}, 耗时: {}毫秒", total, System.currentTimeMillis() - start);
|
||||
if (!errors.isEmpty()) {
|
||||
for (ExcelAnnotationUtils.ValidationError err : errors) {
|
||||
log.error("【EasyExcel导入】校验失败, 行号: {}, 错误: {}", err.row(), err.messages());
|
||||
}
|
||||
return Result.error("文件导入失败: 存在" + errors.size() + "行数据校验不通过,请修改后重新导入");
|
||||
}
|
||||
return Result.ok("文件导入成功!数据行数:" + total);
|
||||
} catch (DuplicateKeyException e) {
|
||||
log.error("【EasyExcel导入】存在重复数据", e);
|
||||
return Result.error("文件导入失败:有重复数据!");
|
||||
} catch (IOException e) {
|
||||
log.error("【EasyExcel导入】文件读取失败", e);
|
||||
return Result.error("文件导入失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过excel导入数据
|
||||
*
|
||||
@@ -184,9 +258,9 @@ public class JeecgController<T, S extends IService<T>> {
|
||||
//update-begin-author:taoyan date:20211124 for: 导入数据重复增加提示
|
||||
String msg = e.getMessage();
|
||||
log.error(msg, e);
|
||||
if(msg!=null && msg.indexOf("Duplicate entry")>=0){
|
||||
if (msg != null && msg.indexOf("Duplicate entry") >= 0) {
|
||||
return Result.error("文件导入失败:有重复数据!");
|
||||
}else{
|
||||
} else {
|
||||
return Result.error("文件导入失败:" + e.getMessage());
|
||||
}
|
||||
//update-end-author:taoyan date:20211124 for: 导入数据重复增加提示
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.jeecg.common.util.excel;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* @Excel 注解解析后的列元数据
|
||||
*/
|
||||
public class ColumnMeta {
|
||||
private final Field field;
|
||||
private final String name;
|
||||
private final double width;
|
||||
private final String format;
|
||||
private final String[] replace;
|
||||
private final boolean multiReplace;
|
||||
private final String numFormat;
|
||||
private final String groupName;
|
||||
private final int orderNum;
|
||||
private final String suffix;
|
||||
private final String databaseFormat;
|
||||
private final boolean columnHidden;
|
||||
|
||||
public ColumnMeta(Field field, String name, double width, String format, String[] replace,
|
||||
boolean multiReplace, String numFormat, String groupName, int orderNum,
|
||||
String suffix, String databaseFormat, boolean columnHidden) {
|
||||
this.field = field;
|
||||
this.name = name;
|
||||
this.width = width;
|
||||
this.format = format;
|
||||
this.replace = replace;
|
||||
this.multiReplace = multiReplace;
|
||||
this.numFormat = numFormat;
|
||||
this.groupName = groupName;
|
||||
this.orderNum = orderNum;
|
||||
this.suffix = suffix;
|
||||
this.databaseFormat = databaseFormat;
|
||||
this.columnHidden = columnHidden;
|
||||
}
|
||||
|
||||
public Field getField() { return field; }
|
||||
public String getName() { return name; }
|
||||
public double getWidth() { return width; }
|
||||
public String getFormat() { return format; }
|
||||
public String[] getReplace() { return replace; }
|
||||
public boolean isMultiReplace() { return multiReplace; }
|
||||
public String getNumFormat() { return numFormat; }
|
||||
public String getGroupName() { return groupName; }
|
||||
public int getOrderNum() { return orderNum; }
|
||||
public String getSuffix() { return suffix; }
|
||||
public String getDatabaseFormat() { return databaseFormat; }
|
||||
public boolean isColumnHidden() { return columnHidden; }
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package org.jeecg.common.util.excel;
|
||||
|
||||
import com.alibaba.excel.metadata.Head;
|
||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
||||
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* EasyExcel 列宽处理器,读取 ColumnMeta 中的 width 设置列宽
|
||||
*/
|
||||
public class ColumnWidthHandler extends AbstractColumnWidthStyleStrategy {
|
||||
|
||||
private final List<ColumnMeta> columns;
|
||||
|
||||
public ColumnWidthHandler(List<ColumnMeta> columns) {
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setColumnWidth(WriteSheetHolder writeSheetHolder,
|
||||
List<WriteCellData<?>> cellDataList,
|
||||
Cell cell, Head head,
|
||||
Integer relativeRowIndex, Boolean isHead) {
|
||||
if (isHead == null || !isHead || columns == null || columns.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int columnIndex = cell.getColumnIndex();
|
||||
if (columnIndex >= columns.size()) {
|
||||
return;
|
||||
}
|
||||
ColumnMeta meta = columns.get(columnIndex);
|
||||
Sheet sheet = cell.getSheet();
|
||||
if (meta.isColumnHidden()) {
|
||||
sheet.setColumnHidden(columnIndex, true);
|
||||
} else {
|
||||
sheet.setColumnWidth(columnIndex, (int) ((meta.getWidth() + 2) * 256));
|
||||
}
|
||||
}
|
||||
}
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
package org.jeecg.common.util.excel;
|
||||
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.context.AnalysisContext;
|
||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
||||
import com.alibaba.excel.read.listener.ReadListener;
|
||||
import com.alibaba.excel.write.handler.WriteHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecgframework.dict.service.AutoPoiDictServiceI;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @Excel 注解工具类,用于 EasyExcel 导入导出。
|
||||
* 从实体类字段上读取 @Excel 注解,构建 EasyExcel 表头和数据,无需 @ExcelProperty。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public final class ExcelAnnotationUtils {
|
||||
|
||||
private static volatile ExcelAnnotationUtils instance;
|
||||
private final Validator validator;
|
||||
|
||||
/**
|
||||
* 校验错误信息载体:行号 + 错误消息列表(现代 Java Record,1行搞定)
|
||||
*/
|
||||
public record ValidationError(int row, List<String> messages) {}
|
||||
|
||||
@Autowired
|
||||
public ExcelAnnotationUtils(Validator validator) {
|
||||
this.validator = validator;
|
||||
ExcelAnnotationUtils.instance = this;
|
||||
log.info("【Excel导入】Spring Validator 成功通过构造器桥接到静态上下文");
|
||||
}
|
||||
|
||||
private ExcelAnnotationUtils(Validator validator, boolean isBackup) {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
public static Validator getValidator() {
|
||||
if (instance != null && instance.validator != null) {
|
||||
return instance.validator;
|
||||
}
|
||||
|
||||
synchronized (ExcelAnnotationUtils.class) {
|
||||
if (instance == null) {
|
||||
log.warn("【Excel导入】未检测到已注入的 Spring 实例,正在启用原生纯 Java Validator 作为安全保底!");
|
||||
Validator tempValidator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
instance = new ExcelAnnotationUtils(tempValidator, true);
|
||||
return tempValidator;
|
||||
}
|
||||
}
|
||||
return instance.validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Class 上带 @Excel 注解的字段,按 orderNum 排序返回列元数据
|
||||
*/
|
||||
public static List<ColumnMeta> parseColumns(Class<?> clazz) {
|
||||
List<ColumnMeta> columns = new ArrayList<>();
|
||||
AutoPoiDictServiceI dictService = getDictService();
|
||||
|
||||
for (Field field : getAllFields(clazz)) {
|
||||
Excel excel = field.getAnnotation(Excel.class);
|
||||
if (excel == null) {
|
||||
continue;
|
||||
}
|
||||
field.setAccessible(true);
|
||||
|
||||
String[] replace = excel.replace();
|
||||
if (dictService != null && (StringUtils.isNotEmpty(excel.dictTable()) || StringUtils.isNotEmpty(excel.dicCode()))) {
|
||||
String[] dictReplace = dictService.queryDict(excel.dictTable(), excel.dicCode(), excel.dicText());
|
||||
if (dictReplace != null && dictReplace.length > 0) {
|
||||
replace = mergeReplace(replace, dictReplace);
|
||||
}
|
||||
}
|
||||
|
||||
columns.add(new ColumnMeta(
|
||||
field,
|
||||
excel.name(),
|
||||
excel.width(),
|
||||
StringUtils.isNotEmpty(excel.exportFormat()) ? excel.exportFormat() : excel.format(),
|
||||
replace,
|
||||
excel.multiReplace(),
|
||||
excel.numFormat(),
|
||||
excel.groupName(),
|
||||
parseOrderNum(excel.orderNum()),
|
||||
excel.suffix(),
|
||||
excel.databaseFormat(),
|
||||
excel.isColumnHidden()
|
||||
));
|
||||
}
|
||||
columns.sort(Comparator.comparingInt(ColumnMeta::getOrderNum));
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 EasyExcel 多级表头,支持 @Excel.groupName()
|
||||
*/
|
||||
public static List<List<String>> buildHead(List<ColumnMeta> columns) {
|
||||
boolean hasGroup = columns.stream().anyMatch(c -> StringUtils.isNotEmpty(c.getGroupName()));
|
||||
List<List<String>> head = new ArrayList<>();
|
||||
for (ColumnMeta col : columns) {
|
||||
if (hasGroup && StringUtils.isNotEmpty(col.getGroupName())) {
|
||||
head.add(Arrays.asList(col.getGroupName(), col.getName()));
|
||||
} else {
|
||||
head.add(Collections.singletonList(col.getName()));
|
||||
}
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据列表按 ColumnMeta 转换为 EasyExcel 的行数据,应用字典翻译、日期格式化、值替换等
|
||||
*/
|
||||
public static List<List<Object>> convertData(List<ColumnMeta> columns, List<?> dataList) {
|
||||
List<List<Object>> rows = new ArrayList<>(dataList.size());
|
||||
for (Object row : dataList) {
|
||||
List<Object> rowData = new ArrayList<>(columns.size());
|
||||
for (ColumnMeta col : columns) {
|
||||
rowData.add(getCellValue(col, row));
|
||||
}
|
||||
rows.add(rowData);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出 Excel 的公共入口:设置响应头 + 构建表头 + 写数据
|
||||
*/
|
||||
public static void write(HttpServletResponse response, Class<?> clazz,
|
||||
String title, List<?> data) throws IOException {
|
||||
List<ColumnMeta> columns = parseColumns(clazz);
|
||||
List<List<String>> head = buildHead(columns);
|
||||
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
String fileName = URLEncoder.encode(title, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||
response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
|
||||
|
||||
EasyExcel.write(response.getOutputStream())
|
||||
.head(head)
|
||||
.registerWriteHandler(new ColumnWidthHandler(columns)) // 这里保持自适应宽度
|
||||
.sheet(title)
|
||||
.doWrite(convertData(columns, data));
|
||||
}
|
||||
|
||||
// ---- 私有辅助方法 ----
|
||||
|
||||
private static Object getCellValue(ColumnMeta col, Object obj) {
|
||||
try {
|
||||
Object value = col.getField().get(obj);
|
||||
if (value == null || "".equals(value)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(col.getFormat())) {
|
||||
value = formatDate(value, col);
|
||||
}
|
||||
|
||||
String strValue = value.toString();
|
||||
|
||||
if (col.getReplace() != null && col.getReplace().length > 0) {
|
||||
strValue = col.isMultiReplace()
|
||||
? multiReplaceValue(col.getReplace(), strValue)
|
||||
: replaceValue(col.getReplace(), strValue);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(col.getNumFormat()) && value instanceof Number) {
|
||||
strValue = new DecimalFormat(col.getNumFormat()).format(value);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(col.getSuffix())) {
|
||||
strValue = strValue + col.getSuffix();
|
||||
}
|
||||
|
||||
return strValue;
|
||||
} catch (Exception e) {
|
||||
log.error("获取单元格值失败, field: {}, row: {}", col.getField().getName(), obj, e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static Object formatDate(Object value, ColumnMeta col) throws Exception {
|
||||
if (value instanceof Date) {
|
||||
return new SimpleDateFormat(col.getFormat()).format((Date) value);
|
||||
}
|
||||
if (value instanceof LocalDateTime) {
|
||||
return ((LocalDateTime) value).format(DateTimeFormatter.ofPattern(col.getFormat()));
|
||||
}
|
||||
if (value instanceof LocalDate) {
|
||||
return ((LocalDate) value).format(DateTimeFormatter.ofPattern(col.getFormat()));
|
||||
}
|
||||
if (value instanceof String && StringUtils.isNotEmpty(col.getDatabaseFormat())) {
|
||||
Date parsed = new SimpleDateFormat(col.getDatabaseFormat()).parse((String) value);
|
||||
return new SimpleDateFormat(col.getFormat()).format(parsed);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String replaceValue(String[] replace, String value) {
|
||||
for (String s : replace) {
|
||||
String[] arr = s.split("_");
|
||||
if (arr.length == 2 && value.equals(arr[1])) {
|
||||
return arr[0];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String multiReplaceValue(String[] replace, String value) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String v : value.split(",")) {
|
||||
String replaced = replaceValue(replace, v.trim());
|
||||
if (sb.length() > 0) sb.append(",");
|
||||
sb.append(replaced);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String[] mergeReplace(String[] original, String[] dictReplace) {
|
||||
if (original == null || original.length == 0) return dictReplace;
|
||||
if (dictReplace == null || dictReplace.length == 0) return original;
|
||||
String[] merged = Arrays.copyOf(original, original.length + dictReplace.length);
|
||||
System.arraycopy(dictReplace, 0, merged, original.length, dictReplace.length);
|
||||
return merged;
|
||||
}
|
||||
|
||||
private static int parseOrderNum(String orderNum) {
|
||||
try {
|
||||
return Integer.parseInt(orderNum);
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static AutoPoiDictServiceI getDictService() {
|
||||
try {
|
||||
return SpringContextUtils.getBean(AutoPoiDictServiceI.class);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Excel 输入流分批导入数据,基于 @Excel 注解读取列配置。
|
||||
*/
|
||||
public static <T> int read(InputStream inputStream, Class<T> clazz,
|
||||
Consumer<List<T>> batchHandler, int batchSize,
|
||||
Consumer<List<ValidationError>> errorHandler) {
|
||||
|
||||
// 1. 职责一:解析注解元数据,准备基础映射表
|
||||
List<ColumnMeta> columns = parseColumns(clazz);
|
||||
|
||||
Map<String, ColumnMeta> nameToCol = new LinkedHashMap<>();
|
||||
Map<String, String> displayToCode = new LinkedHashMap<>();
|
||||
|
||||
for (ColumnMeta col : columns) {
|
||||
nameToCol.put(col.getName(), col);
|
||||
buildReverseReplace(col.getReplace(), displayToCode);
|
||||
}
|
||||
|
||||
// 2. 职责二:实例化专属的流式解析校验监听器
|
||||
ExcelImportListener<T> listener = new ExcelImportListener<>(
|
||||
clazz, batchSize, nameToCol, displayToCode, batchHandler, errorHandler
|
||||
);
|
||||
|
||||
// 3. 职责三:交由 EasyExcel 引擎启动
|
||||
EasyExcel.read(inputStream, listener).sheet().doRead();
|
||||
|
||||
// 4. 返回结果
|
||||
return listener.getSuccessCount();
|
||||
}
|
||||
|
||||
static boolean isValidDictDisplay(String[] replace, String value) {
|
||||
for (String s : replace) {
|
||||
String[] arr = s.split("_");
|
||||
if (arr.length >= 2 && arr[0].equals(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void buildReverseReplace(String[] replace, Map<String, String> displayToCode) {
|
||||
if (replace == null || replace.length == 0) {
|
||||
return;
|
||||
}
|
||||
for (String s : replace) {
|
||||
String[] arr = s.split("_");
|
||||
if (arr.length >= 2) {
|
||||
displayToCode.put(arr[0], arr[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void setFieldValue(ColumnMeta col, Object obj, String value,
|
||||
Map<String, String> displayToCode) throws Exception {
|
||||
Field field = col.getField();
|
||||
Class<?> type = field.getType();
|
||||
|
||||
String resolved = displayToCode.getOrDefault(value, value);
|
||||
|
||||
if (type == String.class) {
|
||||
field.set(obj, resolved);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (type == Integer.class || type == int.class) {
|
||||
field.set(obj, Integer.valueOf(resolved));
|
||||
} else if (type == Long.class || type == long.class) {
|
||||
field.set(obj, Long.valueOf(resolved));
|
||||
} else if (type == Double.class || type == double.class) {
|
||||
field.set(obj, Double.valueOf(resolved));
|
||||
} else if (type == BigDecimal.class) {
|
||||
field.set(obj, new BigDecimal(resolved));
|
||||
} else if (type == Boolean.class || type == boolean.class) {
|
||||
field.set(obj, Boolean.valueOf(resolved));
|
||||
} else if (type == Date.class) {
|
||||
String format = StringUtils.isNotEmpty(col.getDatabaseFormat()) ? col.getDatabaseFormat() : col.getFormat();
|
||||
if (StringUtils.isNotEmpty(format)) {
|
||||
field.set(obj, new SimpleDateFormat(format).parse(resolved));
|
||||
}
|
||||
} else if (type == LocalDateTime.class) {
|
||||
String format = col.getFormat();
|
||||
if (StringUtils.isNotEmpty(format)) {
|
||||
field.set(obj, LocalDateTime.parse(resolved, DateTimeFormatter.ofPattern(format)));
|
||||
}
|
||||
} else if (type == LocalDate.class) {
|
||||
String format = col.getFormat();
|
||||
if (StringUtils.isNotEmpty(format)) {
|
||||
field.set(obj, LocalDate.parse(resolved, DateTimeFormatter.ofPattern(format)));
|
||||
}
|
||||
} else if (type == Float.class || type == float.class) {
|
||||
field.set(obj, Float.valueOf(resolved));
|
||||
} else if (type == Short.class || type == short.class) {
|
||||
field.set(obj, Short.valueOf(resolved));
|
||||
} else {
|
||||
field.set(obj, resolved);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("【Excel导入】字段值类型转换失败, field: {}, value: {}", field.getName(), resolved);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Field> getAllFields(Class<?> clazz) {
|
||||
List<Field> fields = new ArrayList<>();
|
||||
Class<?> current = clazz;
|
||||
while (current != null && current != Object.class) {
|
||||
Collections.addAll(fields, current.getDeclaredFields());
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.jeecg.common.util.excel; // 换成你的包名
|
||||
|
||||
import com.alibaba.excel.context.AnalysisContext;
|
||||
import com.alibaba.excel.event.AnalysisEventListener;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ExcelDataListener<T> extends AnalysisEventListener<T> {
|
||||
private static final int BATCH_COUNT = 1000;
|
||||
private final List<T> cachedDataList = new ArrayList<>(BATCH_COUNT);
|
||||
private final IService<T> baseService;
|
||||
private int totalCount = 0;
|
||||
|
||||
public ExcelDataListener(IService<T> baseService) {
|
||||
this.baseService = baseService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(T data, AnalysisContext context) {
|
||||
cachedDataList.add(data);
|
||||
totalCount++;
|
||||
if (cachedDataList.size() >= BATCH_COUNT) {
|
||||
saveData();
|
||||
cachedDataList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAfterAllAnalysed(AnalysisContext context) {
|
||||
if (!cachedDataList.isEmpty()) {
|
||||
saveData();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveData() {
|
||||
baseService.saveBatch(cachedDataList);
|
||||
}
|
||||
|
||||
public int getTotalCount() {
|
||||
return totalCount;
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
package org.jeecg.common.util.excel;
|
||||
|
||||
import com.alibaba.excel.context.AnalysisContext;
|
||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
||||
import com.alibaba.excel.read.listener.ReadListener;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.util.excel.ExcelAnnotationUtils.ValidationError;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validator;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 工业级 Excel 导入流式解析与全防御校验监听器
|
||||
*/
|
||||
@Slf4j
|
||||
public class ExcelImportListener<T> implements ReadListener<Map<Integer, String>> {
|
||||
|
||||
private final Class<T> clazz;
|
||||
private final int batchSize;
|
||||
private final Consumer<List<T>> batchHandler;
|
||||
private final Consumer<List<ValidationError>> errorHandler;
|
||||
|
||||
// 外部传入的数据支撑
|
||||
private final Map<String, ColumnMeta> nameToCol;
|
||||
private final Map<String, String> displayToCode;
|
||||
|
||||
// 内部状态机
|
||||
private final List<T> batch;
|
||||
private final List<ValidationError> errorBatch;
|
||||
private final Map<Integer, ColumnMeta> columnOrder = new LinkedHashMap<>();
|
||||
private final Map<String, String> fieldToColName = new HashMap<>();
|
||||
|
||||
private final int[] headRowIndex = {-1};
|
||||
private final int[] successCount = {0};
|
||||
private final int[] skipCount = {0};
|
||||
private final int[] validationFailCount = {0};
|
||||
|
||||
public ExcelImportListener(Class<T> clazz, int batchSize,
|
||||
Map<String, ColumnMeta> nameToCol, Map<String, String> displayToCode,
|
||||
Consumer<List<T>> batchHandler, Consumer<List<ValidationError>> errorHandler) {
|
||||
this.clazz = clazz;
|
||||
this.batchSize = batchSize;
|
||||
this.nameToCol = nameToCol;
|
||||
this.displayToCode = displayToCode;
|
||||
this.batchHandler = batchHandler;
|
||||
this.errorHandler = errorHandler;
|
||||
this.batch = new ArrayList<>(batchSize);
|
||||
this.errorBatch = new ArrayList<>(batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
|
||||
if (headRowIndex[0] >= 0) return;
|
||||
|
||||
for (Map.Entry<Integer, ReadCellData<?>> entry : headMap.entrySet()) {
|
||||
String headName = entry.getValue() != null ? entry.getValue().getStringValue() : null;
|
||||
if (headName != null) {
|
||||
ColumnMeta col = nameToCol.get(headName);
|
||||
if (col != null) {
|
||||
columnOrder.put(entry.getKey(), col);
|
||||
}
|
||||
}
|
||||
}
|
||||
headRowIndex[0] = context.readRowHolder().getRowIndex();
|
||||
|
||||
// 缓存 Java 属性名 -> Excel 表头列名的转换映射
|
||||
for (ColumnMeta cm : columnOrder.values()) {
|
||||
if (cm.getField() != null) {
|
||||
fieldToColName.put(cm.getField().getName(), cm.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(Map<Integer, String> data, AnalysisContext context) {
|
||||
if (context.readRowHolder().getRowIndex() == headRowIndex[0]) return;
|
||||
int rowIndex = context.readRowHolder().getRowIndex();
|
||||
|
||||
try {
|
||||
T obj = clazz.getDeclaredConstructor().newInstance();
|
||||
List<String> rowErrors = new ArrayList<>();
|
||||
|
||||
// 1. 字典校验 + 安全转换赋值
|
||||
for (Map.Entry<Integer, String> entry : data.entrySet()) {
|
||||
ColumnMeta col = columnOrder.get(entry.getKey());
|
||||
String cellValue = entry.getValue();
|
||||
|
||||
if (col == null || StringUtils.isEmpty(cellValue)) continue;
|
||||
|
||||
String[] replace = col.getReplace();
|
||||
if (replace != null && replace.length > 0) {
|
||||
if (!ExcelAnnotationUtils.isValidDictDisplay(replace, cellValue)) {
|
||||
rowErrors.add("【" + col.getName() + "】的值'" + cellValue + "'不在字典范围内");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ExcelAnnotationUtils.setFieldValue(col, obj, cellValue, displayToCode);
|
||||
}
|
||||
|
||||
// 2. Hibernate JSR-303 进阶校验
|
||||
Validator currentValidator = ExcelAnnotationUtils.getValidator();
|
||||
if (currentValidator != null && errorHandler != null) {
|
||||
Set<ConstraintViolation<T>> violations = currentValidator.validate(obj);
|
||||
if (!violations.isEmpty()) {
|
||||
for (ConstraintViolation<T> v : violations) {
|
||||
String propertyName = v.getPropertyPath().toString();
|
||||
String label = fieldToColName.getOrDefault(propertyName, propertyName);
|
||||
rowErrors.add("【" + label + "】" + v.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 错误统一拦截清算
|
||||
if (!rowErrors.isEmpty()) {
|
||||
handleError(rowIndex + 1, rowErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 成功加入批次
|
||||
batch.add(obj);
|
||||
successCount[0]++;
|
||||
if (batch.size() >= batchSize) {
|
||||
batchHandler.accept(new ArrayList<>(batch));
|
||||
batch.clear();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
skipCount[0]++;
|
||||
log.error("【Excel导入】行数据转换发生系统异常, row: {}", rowIndex, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleError(int rowNum, List<String> errors) {
|
||||
validationFailCount[0]++;
|
||||
errorBatch.add(new ValidationError(rowNum, errors));
|
||||
if (errorBatch.size() >= batchSize) {
|
||||
errorHandler.accept(new ArrayList<>(errorBatch));
|
||||
errorBatch.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAfterAllAnalysed(AnalysisContext context) {
|
||||
if (!batch.isEmpty()) {
|
||||
batchHandler.accept(new ArrayList<>(batch));
|
||||
batch.clear();
|
||||
}
|
||||
if (!errorBatch.isEmpty()) {
|
||||
errorHandler.accept(new ArrayList<>(errorBatch));
|
||||
errorBatch.clear();
|
||||
}
|
||||
if (skipCount[0] > 0 || validationFailCount[0] > 0) {
|
||||
log.warn("【Excel导入】导入完成, 成功: {} 行, 转换失败: {} 行, 校验失败: {} 行",
|
||||
successCount[0], skipCount[0], validationFailCount[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public int getSuccessCount() {
|
||||
return successCount[0];
|
||||
}
|
||||
}
|
||||
+20
@@ -373,6 +373,26 @@ public class BgXiSpeakController extends JeecgController<BgXiSpeak, IBgXiSpeakSe
|
||||
return super.importExcel(request, response, BgXiSpeak.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过EasyExcel导入数据
|
||||
*/
|
||||
@RequiresPermissions("bg.xispeak:bg_xi_speak:importExcel")
|
||||
@RequestMapping(value = "/importExcelByEasyExcel", method = RequestMethod.POST)
|
||||
public Result<?> importExcelByEasyExcel(HttpServletRequest request) {
|
||||
return super.importExcelByEasyExcel(request, BgXiSpeak.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 仅导出Excel表头(EasyExcel)
|
||||
*/
|
||||
@RequiresPermissions("bg.xispeak:bg_xi_speak:exportXls")
|
||||
@GetMapping(value = "/exportXlsHeaders")
|
||||
public void exportXlsHeaders(HttpServletResponse response) throws IOException {
|
||||
super.exportXlsHeadersByEasyExcel(response, BgXiSpeak.class, "习总书记重要讲话指示批示情况");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询多个 xispeak 节点下的反馈统计数量
|
||||
*
|
||||
|
||||
+9
-6
@@ -7,7 +7,6 @@ import java.math.BigDecimal;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import org.jeecg.common.constant.ProvinceCityArea;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import lombok.Data;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
@@ -15,6 +14,8 @@ import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -83,9 +84,10 @@ public class BgXiSpeak implements Serializable {
|
||||
@Schema(description = "学习传达研究部署情况")
|
||||
private java.lang.String status;
|
||||
/**密级*/
|
||||
@Excel(name = "密级", width = 15, dicCode = "secret_level")
|
||||
@Excel(name = "密级(请填入非秘/秘密/机密)-必填", width = 15, dicCode = "secret_level")
|
||||
@Dict(dicCode = "secret_level")
|
||||
@Schema(description = "密级")
|
||||
@NotBlank
|
||||
private java.lang.String secretLevel;
|
||||
/**责任人*/
|
||||
@Excel(name = "责任人", width = 15, dictTable = "sys_user", dicText = "realname", dicCode = "username")
|
||||
@@ -102,8 +104,9 @@ public class BgXiSpeak implements Serializable {
|
||||
@Schema(description = "牵头部门")
|
||||
private java.lang.String leadDepartment;
|
||||
/**贯彻落实措施*/
|
||||
@Excel(name = "贯彻落实措施", width = 15)
|
||||
@Excel(name = "贯彻落实措施-必填", width = 15)
|
||||
@Schema(description = "贯彻落实措施")
|
||||
@NotBlank
|
||||
private java.lang.String implMeasures;
|
||||
/**会议决策数*/
|
||||
@Excel(name = "会议决策数", width = 15)
|
||||
@@ -126,7 +129,7 @@ public class BgXiSpeak implements Serializable {
|
||||
@Schema(description = "报告反馈情况")
|
||||
private java.lang.String reportFeedbackStatus;
|
||||
/**是否存在完成风险(0不存在,1存在)*/
|
||||
@Excel(name = "是否存在完成风险(0不存在,1存在)", width = 15, dicCode = "true_or_flase")
|
||||
@Excel(name = "是否存在完成风险(请填入是/否)", width = 15, dicCode = "true_or_flase")
|
||||
@Schema(description = "是否存在完成风险(0不存在,1存在)")
|
||||
private java.lang.Integer isCompletionRisk;
|
||||
/**拖期风险应对措施*/
|
||||
@@ -134,7 +137,7 @@ public class BgXiSpeak implements Serializable {
|
||||
@Schema(description = "拖期风险应对措施")
|
||||
private java.lang.String delayRiskMitigation;
|
||||
/**完成状态(0推进中,1已完成)*/
|
||||
@Excel(name = "完成状态(0推进中,1已完成)", width = 15, dicCode = "db_status")
|
||||
@Excel(name = "完成状态(请填入推进中/已完成)-必填", width = 15, dicCode = "db_status")
|
||||
@Schema(description = "完成状态(0推进中,1已完成)")
|
||||
@Dict( dicCode = "db_status")
|
||||
private java.lang.Integer completionStatus;
|
||||
@@ -170,7 +173,7 @@ public class BgXiSpeak implements Serializable {
|
||||
@Schema(description = "领导userId字段")
|
||||
private java.lang.String sdwLeaderList;
|
||||
/**dw领导userId字段*/
|
||||
@Excel(name = "是否需要sdw审批", width = 15)
|
||||
@Excel(name = "是否需要sdw审批(请填入是/否)", width = 15,dicCode = "true_or_flase")
|
||||
@Schema(description = "是否需要sdw审批")
|
||||
private java.lang.Integer needSdwApproval;
|
||||
|
||||
|
||||
+9
@@ -282,6 +282,15 @@ public class DjInspectImproveController extends JeecgController<DjInspectImprove
|
||||
return super.exportXls(request, djInspectImprove, DjInspectImprove.class, "巡视整改台账");
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅导出Excel表头(EasyExcel)
|
||||
*/
|
||||
@RequiresPermissions("dj.inspectimprove:dj_inspect_improve:exportXls")
|
||||
@GetMapping(value = "/exportXlsHeaders")
|
||||
public void exportXlsHeaders(HttpServletResponse response) throws IOException {
|
||||
super.exportXlsHeadersByEasyExcel(response, DjInspectImprove.class, "巡视整改台账");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过excel导入数据
|
||||
*
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<qiniu-uapp-sdk.version>7.4.0</qiniu-uapp-sdk.version>
|
||||
|
||||
<jsoup.version>1.11.3</jsoup.version>
|
||||
<esayexcel.version>4.0.3</esayexcel.version>
|
||||
|
||||
|
||||
</properties>
|
||||
@@ -346,6 +347,13 @@
|
||||
<version>${dom4j.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<version>${esayexcel.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- hutool工具类
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
|
||||
Reference in New Issue
Block a user