2. 创建工具类com.alibaba easyexcel 2.2.7
2.1 ExcelUtil.java(设置头背景,浅绿,宋体,对齐方式)
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;
public class ExcelUtil {
public static HorizontalCellStyleStrategy getStyleStrategy() {
// 头的策略 样式调整
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 头背景 浅绿
headWriteCellStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
WriteFont headWriteFont = new WriteFont();
// 头字号
headWriteFont.setFontHeightInPoints((short) 14);
// 字体样式
headWriteFont.setFontName("宋体");
headWriteCellStyle.setWriteFont(headWriteFont);
// 自动换行
headWriteCellStyle.setWrapped(true);
// 设置细边框
headWriteCellStyle.setBorderBottom(BorderStyle.THIN);
headWriteCellStyle.setBorderLeft(BorderStyle.THIN);
headWriteCellStyle.setBorderRight(BorderStyle.THIN);
headWriteCellStyle.setBorderTop(BorderStyle.THIN);
// 设置边框颜色 25灰度
headWriteCellStyle.setBottomBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
headWriteCellStyle.setTopBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
headWriteCellStyle.setLeftBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
headWriteCellStyle.setRightBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
// 水平对齐方式
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 垂直对齐方式
headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 内容的策略 宋体
WriteCellStyle contentStyle = new WriteCellStyle();
// 设置垂直居中
contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// 设置 水平居中
contentStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
WriteFont contentWriteFont = new WriteFont();
// 内容字号
contentWriteFont.setFontHeightInPoints((short) 12);
// 字体样式
contentWriteFont.setFontName("宋体");
contentStyle.setWriteFont(contentWriteFont);
// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
return new HorizontalCellStyleStrategy(headWriteCellStyle, contentStyle);
}
}
2.2 CustomCellWriteUtil.java(自适应列宽)
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.Cell;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CustomCellWriteUtil extends AbstractColumnWidthStyleStrategy {
private static final int MAX_COLUMN_WIDTH = 255;
private Map> CACHE = new HashMap(8);
public CustomCellWriteUtil() {
}
protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
if (needSetWidth) {
Map maxColumnWidthMap = (Map) CACHE.get(writeSheetHolder.getSheetNo());
if (maxColumnWidthMap == null) {
maxColumnWidthMap = new HashMap(16);
CACHE.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
}
Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
if (columnWidth >= 0) {
if (columnWidth > 255) {
columnWidth = 255;
}
Integer maxColumnWidth = (Integer) ((Map) maxColumnWidthMap).get(cell.getColumnIndex());
if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
((Map) maxColumnWidthMap).put(cell.getColumnIndex(), columnWidth);
writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
}
}
}
}
private Integer dataLength(List cellDataList, Cell cell, Boolean isHead) {
if (isHead) {
return cell.getStringCellValue().getBytes().length;
} else {
CellData cellData = (CellData) cellDataList.get(0);
CellDataTypeEnum type = cellData.getType();
if (type == null) {
return -1;
} else {
switch (type) {
case STRING:
return cellData.getStringValue().getBytes().length;
case BOOLEAN:
return cellData.getBooleanValue().toString().getBytes().length;
case NUMBER:
return cellData.getNumberValue().toString().getBytes().length;
default:
return -1;
}
}
}
}
}
2.3 DropDownSetField.java(自定义标记导出excel的下拉框注解)
import java.lang.annotation.*;
@documented
// 作用在字段上
@Target(ElementType.FIELD)
// 运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface DropDownSetField {
// 固定下拉内容
String[] source() default {};
// 注解内的名称,解析时要注意对应
String name() default "";
}
2.4 ResolveDropAnnotationUtil.java
import java.util.Map;
import java.util.Optional;
public class ResolveDropAnnotationUtil {
public static String[] resove(DropDownSetField dropDownSetField, String[] strings) {
if (!Optional.ofNullable(dropDownSetField).isPresent()) {
return null;
}
// 获取固定下拉信息
String[] source = dropDownSetField.source();
if (null != source && source.length > 0) {
return source;
}
if (null != strings && strings.length > 0) {
try {
String[] dynamicSource = strings;
if (null != dynamicSource && dynamicSource.length > 0) {
return dynamicSource;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
//插入到map中
private void insertMap(Map map, String[] params, DropDownSetField dropDownSetField, int i) {
String[] sources = ResolveDropAnnotationUtil.resove(dropDownSetField, params);
if (null != sources && sources.length > 0) {
map.put(i, sources);
}
}
}
3. 具体用法
TestController.java
package com.example.demo;
public class TestController {
@RequestMapping("/upload")
@ResponseBody
public ResultData upload(MultipartFile file) {
try {
// XxxSaveDataListener 导入监听器 在下面
EasyExcel.read(file.getInputStream(), Xxximport.class, new XxxSaveDataListener(XxxService)).sheet().doRead();
return ResultData.ok("上传成功");
} catch (IOException e) {
return ResultData.ok(e.getMessage());
}
}
@PostMapping("/export")
@ResponseBody
public void exportXxx(HttpServletResponse response,
@RequestBody List xxximports) {
String fileName = "ExportData.xlsx";
OutputStream fileOutputStream = null;
List exportDataList = new ArrayList<>();
try {
for (Xxximport xxximport : xxximports) {
Xxximport xxximport1 = new Xxximport();
xxximport1.setParentLibraryId(xxximport.getParentLibraryId());
xxximport1.setCode(xxximport.getCode());
xxximport1.setName(xxximport.getName());
//转换指标类型;
xxximport1.setTargetTypeId(getByTypeIdReturnName(xxximport.getTargetTypeId()));
xxximport1.setOrigin(xxximport.getOrigin());
//转换 定性 定量
Long category = Long.parseLong(xxximport.getCategory());
xxximport1.setCategory(category == 0 ? "定性" : "定量");
xxximport1.setDefinition(xxximport.getDefinition());
xxximport1.setStatisticalcaliber(xxximport.getStatisticalcaliber());
xxximport1.setStandard(xxximport.getStandard());
xxximport1.setStatisticUnit(xxximport.getStatisticUnit());
xxximport1.setPrecisions(xxximport.getPrecisions());
xxximport1.setRule(xxximport.getRule());
xxximport1.setEffectDate(xxximport.getEffectDate());
xxximport1.setExpireDate(xxximport.getExpireDate());
exportDataList.add(xxximport1);
}
//文件以流形式返回前端下载
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setContentType("application/x-download");
response.setCharacterEncoding("UTF-8");
response.addHeader("Pargam", "no-cache");
response.addHeader("Cache-Control", "no-cache");
response.flushBuffer();
fileOutputStream = response.getOutputStream();
ExcelWriter excelWriter = EasyExcel.write(fileOutputStream, Xxximport.class)
.registerWriteHandler(new CustomCellWriteUtil())
.registerWriteHandler(ExcelUtil.getStyleStrategy())
.build();
WriteSheet sheet = EasyExcel.writerSheet(0, "指标库导出数据").build();
excelWriter.write(exportDataList, sheet);
excelWriter.finish();
fileOutputStream.flush();
fileOutputStream.close();
} catch (Exception e) {
log.error("导出出错,原因:{}", e);
}
}
@GetMapping("/download/dropDownTemplate")
public void downTemplate(HttpServletResponse response) {
try {
// 获取该类声明的所有字段
Field[] fields = Xxximport.class.getDeclaredFields();
// 响应字段对应的下拉集合
Map map = new HashMap<>();
Field field = null;
// 循环判断哪些字段有下拉数据集,并获取
for (int i = 0; i < fields.length; i++) {
field = fields[i];
// 解析注解信息
DropDownSetField dropDownSetField = field.getAnnotation(DropDownSetField.class);
if (null != dropDownSetField) {
String name = dropDownSetField.name();
if (!StringUtils.isEmpty(name)) {
// DropDownNameEnum 对应下拉框注解里面的字段名称,要和导出实体类中的name属性对应上
if (name.equals(DropDownNameEnum.TARGET_CATEGORY.getName())) {
List list = new ArrayList<>();
list.add("定性");
list.add("定量");
String[] params = list.toArray(new String[list.size()]);
insertMap(map, params, dropDownSetField, i);
}
} else {
insertMap(map, null, dropDownSetField, i);
}
}
}
//文件以流形式返回前端下载
String fileName = "importTemplate.xlsx";
OutputStream fileOutputStream = null;
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setContentType("application/x-download");
response.setCharacterEncoding("UTF-8");
response.addHeader("Pargam", "no-cache");
response.addHeader("Cache-Control", "no-cache");
response.flushBuffer();
fileOutputStream = response.getOutputStream();
ExcelWriter excelWriter = EasyExcel.write(fileOutputStream, Xxximport.class)
.registerWriteHandler(new XxximportHandler(map))
.registerWriteHandler(new CustomCellWriteUtil())
.registerWriteHandler(ExcelUtil.getStyleStrategy()).build();
WriteSheet sheet = EasyExcel.writerSheet(0, "指标库导入模板").build();
excelWriter.write(null, sheet);
excelWriter.finish();
fileOutputStream.flush();
fileOutputStream.close();
} catch (Exception e) {
log.info("下载指标库导入模板出错,原因:{}", e);
}
}
private void insertMap(Map map, String[] params, DropDownSetField dropDownSetField, int i) {
String[] sources = ResolveDropAnnotationUtil.resove(dropDownSetField, params);
if (null != sources && sources.length > 0) {
map.put(i, sources);
}
}
}
4. 创建下拉框枚举类,导入导出实体类及监听器
4.1 下拉框枚举类
DropDownNameEnum .java
public enum DropDownNameEnum {
TARGET_CATEGORY("Drop001","类别"),
PERIOD_SET("Drop002", "周期");
private String code;
private String name;
private DropDownNameEnum(String code, String name){
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public void setCode(String code) {
this.code = code;
}
public void setName(String name) {
this.name = name;
}
}
4.2 导入实体类
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.gaiaworks.cn.opm.biz.util.excel.DropDownSetField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Xxximport {
@ExcelProperty(value = "父指标")
private String parentLibraryId;
@ExcelProperty(value = "代码")
private String code;
@ExcelProperty(value = "名称")
private String name;
@ExcelProperty(value = "类型")
private String targetTypeId;
@ExcelProperty(value = "来源")
private String origin;
@ExcelProperty(value = "类别")
@DropDownSetField(name = "类别")
private String category;
@ExcelProperty(value = "定义")
private String definition;
@ExcelProperty(value = "口径")
private String statisticalcaliber;
@ExcelProperty(value = "评分标准")
private String standard;
@ExcelProperty(value = "统计单位")
private String statisticUnit;
@ExcelProperty(value = "精度")
private String precisions;
@ExcelProperty(value = "计算规则")
private String rule;
@ExcelProperty(value = "有效开始日期",index = 12)
private String effectDate;
@ExcelProperty(value = "有效结束日期",index = 13)
private String expireDate;
}
4.3 导入模板的读取类及实现批量入库监听器
XxxSaveDataListener.java
import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.exception.ExcelDataConvertException; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Objects; // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 @Slf4j public class XxxSaveDataListener extends AnalysisEventListener{ private static final int BATCH_COUNT = 30; List list = new ArrayList (); private XxxService xxxService; public XxxSaveDataListener(XxxService xxxService) { this.xxxService = xxxService; } @Override public void invoke(Xxximport data, AnalysisContext context) { list.add(data); // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (list.size() >= BATCH_COUNT) { saveData(); // 存储完成清理 list list.clear(); } } @Override public void onException(Exception exception, AnalysisContext context) { log.error("解析失败,但是继续解析下一行:" + exception.getMessage()); // 如果是某一个单元格的转换异常 能获取到具体行号 // 如果要获取头的信息 配合invokeHeadMap使用 if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception; log.info("第" + excelDataConvertException.getRowIndex() + "行,第" + excelDataConvertException.getColumnIndex() + "列解析异常"); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { // 这里也要保存数据,确保最后遗留的数据也存储到数据库 saveData(); log.info("所有数据解析完成!"); } @Transactional public void saveData() { // service->mapper->batchInsert(list) 实现批量入库 } }
4.4 下拉框校验及日期校验
XxximportHandler.java
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class XxximportHandler implements SheetWriteHandler {
// 这里的map对应步骤3中的insertMap插入的下拉框自定义数据集合
private Map map = null;
public XxximportHandler(Map map) {
this.map = map;
}
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
// 这里可以对cell进行任何操作
Sheet sheet = writeSheetHolder.getSheet();
DataValidationHelper helper = sheet.getDataValidationHelper();
// k 为存在下拉数据集的单元格下表 v为下拉数据集
map.forEach((k, v) -> {
// 下拉列表约束数据
DataValidationConstraint constraint = helper.createExplicitListConstraint(v);
// 设置下拉单元格的首行 末行 首列 末列
CellRangeAddressList rangeList = new CellRangeAddressList(1, 65536, k, k);
// 设置约束
DataValidation validation = helper.createvalidation(constraint, rangeList);
// 阻止输入非下拉选项的值
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
validation.setShowErrorBox(true);
validation.setSuppressDropDownArrow(true);
validation.createErrorBox("提示", "请选择下拉框内的数据");
sheet.addValidationData(validation);
});
//设置验证生效的范围(excel起始行,结束行,起始列,结束列)
CellRangeAddressList addressList = new CellRangeAddressList(1, 65536, 12, 13);
//设置验证方式(Date(1990, 1, 1)是excel的日期函数,能成功解析,写成"1990-01-01"解析失败)
//需要其他日期格式,修改第四个参数"yyyy-MM-dd",eg:"yyyy-MM-dd HH:mm:ss"
DataValidationConstraint constraint = helper.createDateConstraint(DataValidationConstraint.OperatorType.BETWEEN, "Date(1990, 1, 1)", "Date(9999, 12, 31)", "yyyy-MM-dd");
//创建验证对象
DataValidation dataValidation = helper.createvalidation(constraint, addressList);
//错误提示信息
dataValidation.createErrorBox("提示", "请输入[yyyy-MM-dd]格式日期,范围:[1990-01-01,9999-12-31]");
dataValidation.setShowErrorBox(true);
//验证和工作簿绑定
sheet.addValidationData(dataValidation);
}
}
5. 实现效果
下拉框校验,仅能选择下拉框内的数据,不按规则输入效果如下:
日期校验,仅能输入yyyy-MM-dd格式(需要其他格式,在4.4中修改),不按规则输入效果如下:
如果需要校验其他类型,修改4.4中的helper后的构造方法即可
DataValidationConstraint constraint = helper.createDateConstraint(DataValidationConstraint.OperatorType.BETWEEN, "Date(1990, 1, 1)", "Date(9999, 12, 31)", "yyyy-MM-dd");
package org.apache.poi.ss.usermodel;
import org.apache.poi.ss.util.CellRangeAddressList;
public interface DataValidationHelper {
// 创建公式列表约束
DataValidationConstraint createFormulaListConstraint(String listFormula);
// 创建显式列表约束
DataValidationConstraint createExplicitListConstraint(String[] listOfValues);
// 创建数字约束
DataValidationConstraint createNumericConstraint(int validationType,int operatorType, String formula1, String formula2);
// 创建文本长度约束
DataValidationConstraint createTextLengthConstraint(int operatorType, String formula1, String formula2);
// 创建小数约束
DataValidationConstraint createDecimalConstraint(int operatorType, String formula1, String formula2);
// 创建整数约束
DataValidationConstraint createIntegerConstraint(int operatorType, String formula1, String formula2);
// 创建日期约束
DataValidationConstraint createDateConstraint(int operatorType, String formula1, String formula2,String dateFormat);
// 创建时间约束
DataValidationConstraint createTimeConstraint(int operatorType, String formula1, String formula2);
// 创建自定义约束
DataValidationConstraint createCustomConstraint(String formula);
// 创建验证
DataValidation createvalidation(DataValidationConstraint constraint,CellRangeAddressList cellRangeAddressList);
}



