一、简单说明
本次封装引入阿里开源框架EasyExcel,EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。 github地址:GitHub - alibaba/easyexcel: 快速、简洁、解决大文件内存溢出的java处理Excel工具 。64M内存20秒读取75M(46W行25列)的Excel(3.0.2+版本)
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
</dependency>
结构图如下:
1.1结构说明:
1.annotation:注解
@ExcelPropertyCheck(自己写的注解用作导入数据校验)
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ExcelPropertyCheck {
boolean required() default true; ----是否为空,默认不为空。
boolean checkFormat() default false; ----是否进行格式检验,默认不进行。
int type() default -1; ----格式检验类型,int 已经支持的类型有 0->ip、1->端口、2->时间日期格式
int length() default -1; ----长度校验, int 字符串的长度,-1不进行校验
}
@ExcelProperty(框架自带的,用于标记excel传输类和一些通用的导入导出配置)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelProperty {
String[] value() default {""}; ----导出时对应字段的表头名称
int index() default -1;----排列顺序,最好不要配默认按字段顺序。
int order() default Integer.MAX_VALUE; ----同上
Class<? extends Converter<?>> converter() default AutoConverter.class; ----转换器
String format() default ""; ----格式划输出
}
特别提醒:!!!
@ExcelPropertyCheck该注解作用类上时只支持required,其余属性无效。字段上的注解配置会覆盖类上的配置。
2.constant:常量类,格式校验的类型定义,如电话号码、日期、IP地址等。
/**
* excel导入字段类型常量
*/
public class ExcelPropertyType {
//时间日期格式校验
public static final int DATE_TIME = 0;
}
3.converter:转换器
读写均可使用,实现Converter重写方法即可。
如将excel 中的日期转为 LocalDateTime 或者将LocalDateTime 转为excel表中的日期
或者将数据库的枚举值0,1,2,3导入到excel文件变成对应的中文汉字
/**
* String and string converter
*
*/
public class CustomStringStringConverter implements Converter<String> {
/**
* 这里读的时候会调用
*
* @param context
* @return
*/
@Override
public String convertToJavaData(ReadConverterContext<?> context) {
return "自定义:" + context.getReadCellData().getStringValue();
}
/**
* 这里是写的时候会调用 不用管
*
* @return
*/
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
return new WriteCellData<>(context.getValue());
}
}
上述代码中泛型为String 表示此字段经过处理后最后返回的类型为String
4.listener:监听器
ReadListener<T>(EasyExcel提供的)
ReadListener<T>读监听器,框架提供。提供读取excel不同时期的监听方法。
分别为:
onException()-------------->“读取发生异常时监听”、
invokeHead()----------------->“读取表头信息监听”、
invoke()--------------------->“读取每行数据监听”、
doAfterAllAnalysed()----------->“所有数据读取完毕监听”。
BaseListener<T>我们自己封装的读监听器,在里面结合注解配置完成数据校验。
BaseListener<T>我们自己封装的读监听器,在里面结合注解配置完成数据校验。
对外提供的字段:
private final List<Map<String, Object>> mapData = new ArrayList<>();
private final List<T> data = new ArrayList<>();
private final Map<Integer, String> errorMessageMap = new HashMap<>();
//非空校验map
private final Map<String, Boolean> nullAbleFieldMap = new HashMap<>();
//格式校验map
private final Map<String, String> checkFormatFieldMap = new HashMap<>();
//长度校验map
private final Map<String, Integer> checkLengthFieldMap = new HashMap<>();
//枚举值校验map
private final Map<String, String[]> checkEnumFieldMap = new HashMap<>();
字段 | 描述 | 备注 |
mapData | excel读取完毕的Map数据 | Map<String,Object> |
data | excel读取完毕的对于实体List | List<T> |
errorMessageMap | 错误信息map | Map<Integer,String>;key为错误行号,value为描述。 |
nullAbleFieldMap |
非空校验map | Map<String, Boolean> |
checkFormatFieldMap |
格式校验map:电话号码,ip,日期 | Map<String, String> |
checkLengthFieldMap |
长度校验map | Map<String, Integer> |
checkEnumFieldMap |
枚举值检验map | Map<String, String[]> |
结合注解做参数校验或者格式校验的实现思路(反射加泛型)
步骤一:在表头读取的监听方法里利用反射判断检验注解ExcelPropertyCheck加在了那些ExcelDTO哪些属性上,并且ExcelPropertyCheck注解的具体属性是什么将这些存入分门别类的map里面。
步骤二:在读取每行数据的时候,结合步骤一的map,判断数据是否需要校验,并且从map中取出校验的类型是什么,再去完成校验,如果校验失败则将错误信息放入errorMessageMap,当前读取的excel这一行数据全部校验通过则数据放入data 里面
完整的BaseListener<T>代码如下:
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.holder.ReadSheetHolder;
import com.baomidou.mybatisplus.core.toolkit.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.util.*;
public class BaseListener<T> implements ReadListener<T> {
private final Logger logger = LoggerFactory.getLogger(BaseListener.class);
private Class<?> headClazz;
//读取数据map形式
private final List<Map<String, Object>> mapData = new ArrayList<>();
//读取数据实体类泛型形式
private final List<T> data = new ArrayList<>();
//非空校验map
private final Map<String, Boolean> nullAbleFieldMap = new HashMap<>();
//格式校验map
private final Map<String, String> checkFormatFieldMap = new HashMap<>();
//长度校验map
private final Map<String, Integer> checkLengthFieldMap = new HashMap<>();
//枚举值校验map
private final Map<String, String[]> checkEnumFieldMap = new HashMap<>();
//数据校验错误信息map key:错误的行号 value:错误信息描述
private final Map<Integer, String> errorMessageMap = new HashMap<>();
public List<Map<String, Object>> getMapData() {
return mapData;
}
public List<T> getData() {
return data;
}
public Map<Integer, String> getErrorMessageMap() {
return errorMessageMap;
}
public BaseListener() {
}
/**
* @param headClazz excel model 类对象
*/
public BaseListener(Class<?> headClazz) {
this.headClazz = headClazz;
}
/**
* 读取发生异常时的方法
*
* @param exception
* @param context
* @throws Exception
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
logger.debug("发生了异常");
}
/**
* 读取头信息
*
* @param headMap
* @param context
*/
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
ExcelPropertyCheck clazzHeadAnno = this.headClazz.getAnnotation(ExcelPropertyCheck.class);
Field[] declaredFields = headClazz.getDeclaredFields();
if (clazzHeadAnno != null && clazzHeadAnno.required()) {
for (Field declaredField : declaredFields) {
nullAbleFieldMap.put(declaredField.getName(), true);
}
}
for (Field declaredField : declaredFields) {
ExcelPropertyCheck annotation = declaredField.getAnnotation(ExcelPropertyCheck.class);
if (annotation != null) {
if (annotation.checkFormat()) {
checkFormatFieldMap.put(declaredField.getName(), annotation.type() + "");
}
if (annotation.required()) {
nullAbleFieldMap.put(declaredField.getName(), true);
} else {
nullAbleFieldMap.remove(declaredField.getName());
}
if (annotation.required() && annotation.length() != -1) {
checkLengthFieldMap.put(declaredField.getName(), annotation.length());
}
if (annotation.required() && annotation.value().length != 0) {
checkEnumFieldMap.put(declaredField.getName(), annotation.value());
}
}
}
}
/**
* 读取每一行数据
*
* @param t
* @param analysisContext
*/
@Override
public void invoke(T t, AnalysisContext analysisContext) {
int rowIndex = ((ReadSheetHolder) analysisContext.currentReadHolder()).getRowIndex() + 1;
StringBuilder error = new StringBuilder();
Field[] declaredFields = t.getClass().getDeclaredFields();
//必填校验和格式校验
for (Field declaredField : declaredFields) {
try {
declaredField.setAccessible(true);
if (nullAbleFieldMap.get(declaredField.getName()) != null) {
if (nullAbleFieldMap.get(declaredField.getName())) {
Object o = declaredField.get(t);
if (!Objects.nonNull(o)) {
error.append(declaredField.getName()).append("为空;");
} else {
//字段不为空进行长度校验
if (checkLengthFieldMap.get(declaredField.getName()) != null) {
if (String.valueOf(o).length() > checkLengthFieldMap.get(declaredField.getName())) {
error.append(declaredField.getName()).append("长度错误;");
}
}
if (checkEnumFieldMap.get(declaredField.getName()) != null) {
if (Integer.parseInt(String.valueOf(o)) == -1) {
error.append(declaredField.getName()).append("枚举值错误;");
}
}
}
}
}
//是否需要进行格式校验
if (checkFormatFieldMap.get(declaredField.getName()) != null) {
String res = check(String.valueOf(declaredField.get(t)), Integer.valueOf(checkFormatFieldMap.get(declaredField.getName())));
if (StringUtils.hasText(res)) {
error.append(res);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (StringUtils.hasText(error.toString())) {
errorMessageMap.put(rowIndex, error.toString());
}
mapData.add(BeanUtils.beanToMap(t));
data.add(t);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
logger.info("excel解析完毕,共解析{}条数据,错误数据{}条,错误详情{}", data.size(), errorMessageMap.size(), errorMessageMap);
}
private String check(String str, Integer checkType) {
switch (checkType) {
case 0:
return DateUtil.isDateTimeFormat(str) ? "" : "日期格式错误;";
default:
return "";
}
}
}
如果导入时除了简单校验还需要特色校验如数据库去重,业务判断等,请另外写一个xxxReadListener 来继承BaseListener<T> 泛型为对应excel传输对象。重写监听方法时请务必super()一下,否则基本校验就没了!
5.model:excel传输对象
注解配置
为了不污染实体类和字段冗余请在这里定义excel传输对象加注解。
@Data
@EqualsAndHashCode
@ExcelPropertyCheck
public class DemoExcelDto {
@ExcelProperty("id")
@ExcelPropertyCheck(required = false)
private Long id; //记录标识
@ExcelProperty("导入时间")
@ExcelPropertyCheck(required = true, checkFormat = true, type = ExcelPropertyType.DATE_TIME)
private LocalDateTime importTime;
}
二、使用说明
1.读Excel
EasyExcelUtils.importExcel(file, IpDepartsExcelDto.class, listener)
@RequestMapping("importExcel")
@ResponseBody
public Result importIps(MultipartFile file) {
//XXXReadListener listener = new XXXReadListener (XXXExcelDto.class);
//没有复杂的校验就用BaseListener
BaseListener<DemoExcelDto> listener = new BaseListener<>();
//获取校验错误信息
Map errorMap = EasyExcelUtils.importExcel(file, IpDepartsExcelDto.class, listener);
//获取读取完毕的数据,即使发生校验错误也会继续读取
List<IpDeparts> ipDeparts = EasyExcelUtils.dto2model(listener.getData() ,new IpDeparts());
if (errorMap.size() != 0) {
//errorMap校验数据不为空就是excel数据校验有不通过的数据。
return Result.failure(CommonResultStatus.IMPORT_FAIL, errorMap.toString());
} else {
//mapper保存数据库。
mapper.saveAll(ipDeparts);
return Result.success(CommonResultStatus.OK);
}
}
2.写Excel
EasyExcelUtils.downloadExcel(response, fileName, ipDepartsExcelDtoList, IpDepartsExcelDto.class)
@GetMapping("download")
public void download(HttpServletResponse response) throws UnsupportedEncodingException {
String fileName = URLEncoder.encode("test", "utf-8");
List<DemoExcelDto> demoExcelDtos = new ArrayList<>();
for (int i = 0; i < 5; i++) {
DemoExcelDto demoExcelDto = new DemoExcelDto(i, "2020-08-01");
demoExcelDtos.add(demoExcelDto);
}
try {
EasyExcelUtils.downloadExcel(response, fileName, demoExcelDtos, DemoExcelDto.class);
} catch (IOException e) {
e.printStackTrace();
}
}
三、EasyExcelUtils(简单的导入导出工具类)
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.BeanUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class EasyExcelUtils {
/**
* 导出excel
*
* @param response
* @param fileName 文件名
* @param data List<数据集合>
* @throws IOException
*/
public static <T> void downloadExcel(HttpServletResponse response, String fileName, List<T> data, Class<?> clazz) throws IOException {
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
// 这里需要设置不关闭流
EasyExcel.write(response.getOutputStream(), clazz).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).autoCloseStream(Boolean.FALSE).sheet("sheet1")
.doWrite(data);
} catch (Exception e) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = MapUtils.newHashMap();
map.put("status", "failure");
map.put("message", "下载文件失败" + e.getMessage());
response.getWriter().println(JSON.toJSONString(map));
}
}
/**
* 读取excel 文件
*
* @param file 文件
* @param cl 类对象
* @param listener 参数校验监听器
*/
public static <T> Map importExcel(MultipartFile file, Class<T> cl, BaseListener<T> listener) {
try {
EasyExcel.read(file.getInputStream(), cl, listener).sheet().doRead();
return listener.getErrorMessageMap();
} catch (IOException e) {
return null;
}
}
/**
* excleDto 转为对应model实体类
*
* @param data
* @param t
* @param <T>
* @return
*/
public static <T> List<T> dto2model(Collection<?> data, T t) {
return data.stream().map(e -> {
T t1 = null;
try {
t1 = (T) t.getClass().newInstance();
} catch (Exception exception) {
exception.printStackTrace();
}
BeanUtils.copyProperties(e, t1);
return t1;
}).collect(Collectors.toList());
}
}
四、总结:
经过简单的封装完成了一个带参数校验的简单使用案例,直接引入项目后在controller直接调用EasyExcelUtils的 "importExcel"方法和"downloadExcel"就能实现excel的导入导出,是不是很方便呢。文章来源:https://www.toymoban.com/news/detail-430294.html
有问题留言大家一起交流学习。文章来源地址https://www.toymoban.com/news/detail-430294.html
到了这里,关于java实现excel的导入导出(带参数校验:非空校验、数据格式校验)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!