<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>2.2.7version>
dependency>
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 {
/**
* 设置excel样式
*
* @return
*/
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;
/**
* excel自适应列宽
*/
public class CustomCellWriteUtil extends AbstractColumnWidthStyleStrategy {
private static final int MAX_COLUMN_WIDTH = 255;
private Map<Integer, Map<Integer, Integer>> CACHE = new HashMap(8);
public CustomCellWriteUtil() {
}
protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
if (needSetWidth) {
Map<Integer, Integer> 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<CellData> 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.*;
/**
* 注解:自定义标记导出excel的下拉数据集
*/
@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;
/**
* 处理导入excel下拉框注解的工具类
*/
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<Integer, String[]> map, String[] params, DropDownSetField dropDownSetField, int i) {
String[] sources = ResolveDropAnnotationUtil.resove(dropDownSetField, params);
if (null != sources && sources.length > 0) {
map.put(i, sources);
}
}
}
TestController.java
package com.example.demo;
public class TestController {
/**
* 文件上传
*
* 1. 创建excel对应的实体对象
*
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器
*
* 3. 直接读即可
*/
@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());
}
}
/**
* 导出指标库记录
*
* @param response
* @param XxxImports 导出模板实体类
*/
@PostMapping("/export")
@ResponseBody
public void exportXxx(HttpServletResponse response,
@RequestBody List<XxxImport> xxxImports) {
String fileName = "ExportData.xlsx";
OutputStream fileOutputStream = null;
List<XxxImport> 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<Integer, String[]> 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<String> 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);
}
}
/**
* excel导入下拉框 数据放入map集合
*
* @param map
* @param params
* @param dropDownSetField
* @param i
*/
private void insertMap(Map<Integer, String[]> map, String[] params, DropDownSetField dropDownSetField, int i) {
String[] sources = ResolveDropAnnotationUtil.resove(dropDownSetField, params);
if (null != sources && sources.length > 0) {
map.put(i, sources);
}
}
}
4.1 下拉框枚举类
DropDownNameEnum .java
/**
* excel模板下拉框名称枚举
*/
public enum DropDownNameEnum {
TARGET_CATEGORY("Drop001","类别"),
PERIOD_SET("Drop002", "周期");
/**
* 指标类型id
*/
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;
/**
* 0:定性,1:定量
* 这里的name 要和上面下拉框枚举类的名称对应起来
*/
@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;
/**
* 生效日期(这里加index是用在导入模板的日期校验)
*/
@ExcelProperty(value = "有效开始日期",index = 12)
private String effectDate;
/**
* 失效日期(这里加index是用在导入模板的日期校验)
*/
@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<XxxImport> {
/**
* 每隔30条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 30;
List<XxxImport> list = new ArrayList<XxxImport>();
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private XxxService xxxService;
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*/
public XxxSaveDataListener(XxxService xxxService) {
this.xxxService = xxxService;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(XxxImport data, AnalysisContext context) {
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
/**
* 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
*
* @param exception
* @param context
* @throws Exception
*/
@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() + "列解析异常");
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@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<Integer, String[]> map = null;
public XxxImportHandler(Map<Integer, String[]> 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);
}
}
下拉框校验,仅能选择下拉框内的数据,不按规则输入效果如下:
日期校验,仅能输入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;
/**
* @author Radhakrishnan J
*
*/
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);
}