文章目录
SpringBoot整合EasyPoi 实现Excel 导入与导出 导入数据校验
本文SpringBoot学习小营地是SpringBoot与EasyPoi的整合,通过此文章你可以做到基本数据的导入与导出以及导入数据校验
参考官网: easypoi
(1)EasyPoi整合前准备
1.项目依赖
<!--easypoi与SpringBoot的整合包-->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-spring-boot-starter</artifactId>
<version>4.1.2</version>
</dependency>
也可以替换为:
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>3.0.3</version>
</dependency>
excel操作
2.YML配置
由于我使用Google浏览器开始未配置时,导入文件报错:上传文件大小超出
所以本项目中目前定义了上传以及请求文件大小限制(具体可根据业务需求自行修改)
#HTML页面配置
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
#设置文件单次上传与请求无大小限制
servlet:
multipart:
max-file-size: -1
max-request-size: -1
本项目导入导出均可在PostMan软件中测试 当然 本文也编写了导入的HTML页面。
项目结构如下
(2)导入导出项目实体类
本文中做了深入导出(关联对象属性导出)具体实体类类注解用法请还是参考官网 easypoi
/**
* @author : leilei
* @date : 11:29 2020/2/22
* @desc : name为在excel 名字 width:表格宽度 orderNum:处于excel第列 默认为0 replace:替换 例如我的 true 替换为男 suffix:后缀 男生 女生
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
@Excel(name = "用户名", width = 15)
private String username;
@Excel(name = "密码", width = 15, orderNum = "4")
private String password;
@Excel(name = "性别", replace = {"男_true", "女_false"}, suffix = "生", width = 15, orderNum = "1")
private Boolean sex;
@Excel(name = "年龄")
private Integer age;
@Excel(name = "头像", width = 15, height = 15, type = 2, orderNum = "2")
private String headImg;
@Excel(name = "创建时间", exportFormat = "yyyy-MM-dd HH:mm:ss", importFormat ="yyyy-MM-dd HH:mm:ss",width = 25, orderNum = "3")
private LocalDateTime createTime;
/**连表查询时 需要深入导出关联对象属相*/
@ExcelEntity
private DepartMent departMent;
}
须深入导出 关联的对象
/**
* @author : leilei
* @date : 14:36 2020/2/22
* @desc :
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DepartMent {
@Excel(name = "部门")
private String name;
}
(3)导入导出相关代码编写
(1)导出
由于本文并未连接数据库,所以 导入导出只能通过new 对象存集合的方式进行展示
创建查询用户列表接口
public interface IEasyPoiService {
/**
* 查询用户列表
* @return
*/
List<User> findUserList();
}
接口实现
public static String HeadImgUrl = "/static/head.jpg";
@Override
public List<User> findUserList() {
List<User> users = new ArrayList<>();
for (int i = 0; i < 15; i++) {
User user = new User();
user.setUsername("leilei" + i);
if (i % 2 == 0) {
user.setSex(true);
user.setDepartMent(new DepartMent("电商部门"));
} else {
user.setSex(false);
user.setDepartMent(new DepartMent("传统部门"));
}
user.setPassword("leilei" + i);
int age = new Random().nextInt(70 - 35) + 35;
user.setAge(age);
user.setHeadimg(HeadImgUrl);
user.setCreateTime(LocalDateTime.now());
users.add(user);
}
return users;
}
因为本文是SpringBoot 项目,所有的静态资源都是放在src/main/java/resources/static下 直接导入时读取不到文件的(本文中,用户头像存放于本地static下),所以需要自定义一个文件读取类
自定义一个文件读取类
**
* @author leilei
* @date 2020/02/22
* @desc 文件加载读取类, 根据路径加载指定文件 参照官网修改
*/
@Slf4j
public class FileLoaderImpl implements IFileLoader {
@Override
public byte[] getFile(String url) {
InputStream fileis = null;
ByteArrayOutputStream baos = null;
try {
//判断是否是网络地址
if (url.startsWith("http")) {
URL urlObj = new URL(url);
URLConnection urlConnection = urlObj.openConnection();
urlConnection.setConnectTimeout(30);
urlConnection.setReadTimeout(60);
urlConnection.setDoInput(true);
fileis = urlConnection.getInputStream();
} else {
//先用绝对路径查询,再查询相对路径
try {
fileis = new FileInputStream(url);
} catch (FileNotFoundException e) {
//获取项目文件
fileis = FileLoaderImpl.class.getClassLoader().getResourceAsStream(url);
if (fileis == null) {
fileis = FileLoaderImpl.class.getResourceAsStream(url);
}
}
}
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = fileis.read(buffer)) > -1) {
baos.write(buffer, 0, len);
}
baos.flush();
return baos.toByteArray();
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
IOUtils.closeQuietly(fileis);
IOUtils.closeQuietly(baos);
}
log.error(fileis + "这个路径文件没有找到,请查询");
return null;
}
}
文件监听读取类
/**
* @author leilei
* @date 2020/02/22
* @desc 使自定义的文件加载类生效 (本地图片)
*/
@Component
public class ExcelListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
POICacheManager.setFileLoader(new FileLoaderImpl());
}
}
导出网络图片:
@Excel注解中 type=2则表示,该字段在Excel中类型为图片
导出已下载图片:
type =2 该字段类型为图片,imageType=2 ,表示从数据库或者已经读取完毕,字段类型是个字节数组 直接使用
编写导出控制层 导出用户列表到Excel中
@Autowired
private IEasyPoiService easyPoiService;
/**
* 导出1 使用本身代码 导出须校验的数据集
*/
@RequestMapping("/outexcel")
public String download(ModelMap map) {
List<UserVo> list = easyPoiService.findUserVoList();
//导出基本信息的配置
ExportParams params = new ExportParams("用户列表", "用户", ExcelType.XSSF);
// params.setFreezeCol(2);//冻结的列
/**excel文件要导出的数据*/
map.put(NormalExcelConstants.DATA_LIST, list);
/**excel文件导出的实体*/
map.put(NormalExcelConstants.CLASS, UserVo.class);
/**excel文件参数*/
map.put(NormalExcelConstants.PARAMS, params);
/**excel文件名称*/
map.put(NormalExcelConstants.FILE_NAME, "用户列表");
return NormalExcelConstants.EASYPOI_EXCEL_VIEW;
}
/**
* 导出2 使用网上excel导出导入工具类 无需校验的导入导出
*
* @param response
*/
@RequestMapping(value = "/export")
public void exportExcel(HttpServletResponse response) throws IOException {
List<User> users = easyPoiService.findUserList();
ExcelUtils.exportExcel(users, "用户信息表", "用户信息", User.class, "用户信息", response);
}
自定义数据处理(导入导出字典使用)
导入导出总有一些自定义格式转换,EasyPoi虽然定义了很多服务,但是也无法满足所有客户的需求,这个时候就需要咱们自己定义数据处理,我们如果有自己的字典码值需要处理也很简单,只需要实现easypoi提供的IExcelDictHandler接口即可
ex:
public class IDictHandlerImpl implements IExcelDictHandler
public class IDictHandlerImpl implements IExcelDictHandler {
public static final String POSITION = "position";
/**
* 从值翻译到名称 一般是导出用(数据库存的值,导出到excel需要转换为名字)
*
* @param dict 字典Key
* @param obj 对象
* @param name 属性名称
* @param value 属性值
* @return
*/
@Override
public String toName(String dict, Object obj, String name, Object value) {
// 职位字典转换
if (Objects.equals(dict, POSITION)) {
//todo 查询字典表或缓存
int positionInt = Integer.parseInt(String.valueOf(value));
// 模拟字典值转换
if (positionInt==1) {
return "老师";
}
if (positionInt==2) {
return "校长";
}
if (positionInt==3) {
return "班主任";
}
return null;
}
// 如果有其他字典值需要转换,继续进行以上操作
return null;
}
/**
* 从名称翻译到值 一般是导入使用,excel中为具体的字典说明,导入时转换为数字进行最后入库
*
* @param dict 字典Key
* @param obj 对象
* @param name 属性名称
* @param value 属性值
* @return
*/
@Override
public String toValue(String dict, Object obj, String name, Object value) {
return null;
}
}
贴出导出导入工具类代码
package com.leilei.util;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
/**
* excel 工具类
*
* @author easypoi
* @date 2020/02/22
*/
public class ExcelUtils {
/**
* 导出excel到
*
* @param list 数据
* @param title 表头
* @param sheetName sheetName
* @param pojoClass 解析的对象类型
* @param fileName 文件名称
* @param isCreateHeader 是否创建表头
* @return 文件路径
*/
public static String exportExcelToFile(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean isCreateHeader) {
OutputStream out = null;
Workbook workbook = null;
try {
ExportParams exportParams = new ExportParams(title, sheetName, ExcelType.XSSF);
exportParams.setCreateHeadRows(isCreateHeader);
fileName = encodingFilename(fileName);
out = new FileOutputStream(getAbsoluteFile(fileName));
workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
workbook.write(out);
return fileName;
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
} finally {
if (workbook != null) {
try {
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* excel 导出到文件
*
* @param list 数据
* @param title 表头
* @param sheetName sheet名称
* @param pojoClass pojo类型
* @param fileName 文件名
* @return 文件路径
*/
public static String exportExcelToFile(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName) {
return exportExcelToFile(list, title, sheetName, pojoClass, fileName, true);
}
/**
* excel 导出到文件
*
* @param list 数据
* @param title 表头
* @param sheetName sheet名称
* @param pojoClass pojo类型
* @return 文件路径
*/
public static String exportExcelToFile(List<?> list, String title, String sheetName, Class<?> pojoClass) {
return exportExcelToFile(list, title, sheetName, pojoClass, title, true);
}
/**
* excel 导出到文件
*
* @param list 数据
* @param fileName 文件名
* @param pojoClass pojo类型
* @return 文件路径
*/
public static String exportExcelToFile(List<?> list, String fileName, Class<?> pojoClass) {
return exportExcelToFile(list, fileName, fileName, pojoClass, fileName, true);
}
/**
* excel 导出
*
* @param list 数据
* @param title 标题
* @param sheetName sheet名称
* @param pojoClass pojo类型
* @param fileName 文件名称
* @param isCreateHeader 是否创建表头
* @param response
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean isCreateHeader, HttpServletResponse response) throws IOException {
ExportParams exportParams = new ExportParams(title, sheetName, ExcelType.XSSF);
exportParams.setCreateHeadRows(isCreateHeader);
defaultExport(list, pojoClass, fileName, response, exportParams);
}
/**
* excel 导出
*
* @param list 数据
* @param title 标题
* @param sheetName sheet名称
* @param pojoClass pojo类型
* @param fileName 文件名称
* @param response
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) throws IOException {
defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName, ExcelType.XSSF));
}
/**
* excel 导出
*
* @param list 数据
* @param pojoClass pojo类型
* @param fileName 文件名称
* @param response
* @param exportParams 导出参数
*/
public static void exportExcel(List<?> list, Class<?> pojoClass, String fileName, ExportParams exportParams, HttpServletResponse response) throws IOException {
defaultExport(list, pojoClass, fileName, response, exportParams);
}
/**
* excel 导出
*
* @param list 数据
* @param fileName 文件名称
* @param response
*/
public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException {
defaultExport(list, fileName, response);
}
/**
* 默认的 excel 导出
*
* @param list 数据
* @param pojoClass pojo类型
* @param fileName 文件名称
* @param response
* @param exportParams 导出参数
*/
private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) throws IOException {
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
downLoadExcel(fileName, response, workbook);
}
/**
* 默认的 excel 导出
*
* @param list 数据
* @param fileName 文件名称
* @param response
*/
private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException {
Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF);
downLoadExcel(fileName, response, workbook);
}
/**
* 下载
*
* @param fileName 文件名称
* @param response
* @param workbook excel数据
*/
private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) throws IOException {
try {
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type", "application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + "." + ExcelTypeEnum.XLSX.getValue(), "UTF-8"));
workbook.write(response.getOutputStream());
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* excel 导入
*
* @param filePath excel文件路径
* @param titleRows 标题行
* @param headerRows 表头行
* @param pojoClass pojo类型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws IOException {
if (StringUtils.isBlank(filePath)) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
params.setNeedSave(true);
params.setSaveUrl("/excel/");
try {
return ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
} catch (NoSuchElementException e) {
throw new IOException("模板不能为空");
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* excel 导入
*
* @param file excel文件
* @param pojoClass pojo类型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(MultipartFile file, Class<T> pojoClass) throws IOException {
return importExcel(file, 1, 1, pojoClass);
}
/**
* excel 导入
*
* @param file excel文件
* @param titleRows 标题行
* @param headerRows 表头行
* @param pojoClass pojo类型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws IOException {
return importExcel(file, titleRows, headerRows, false, pojoClass);
}
/**
* excel 导入
*
* @param file 上传的文件
* @param titleRows 标题行
* @param headerRows 表头行
* @param needVerify 是否检验excel内容
* @param pojoClass pojo类型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, boolean needVerify, Class<T> pojoClass) throws IOException {
if (file == null) {
return null;
}
try {
return importExcel(file.getInputStream(), titleRows, headerRows, needVerify, pojoClass);
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* excel 导入
*
* @param inputStream 文件输入流
* @param titleRows 标题行
* @param headerRows 表头行
* @param needVerify 是否检验excel内容
* @param pojoClass pojo类型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(InputStream inputStream, Integer titleRows, Integer headerRows, boolean needVerify, Class<T> pojoClass) throws IOException {
if (inputStream == null) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
params.setSaveUrl("/excel/");
params.setNeedSave(true);
params.setNeedVerify(needVerify);
try {
return ExcelImportUtil.importExcel(inputStream, pojoClass, params);
} catch (NoSuchElementException e) {
throw new IOException("excel文件不能为空");
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* 获取下载路径
*
* @param downloadPath 文件名称
*/
private static String getAbsoluteFile(String downloadPath) {
downloadPath = "/excel/" + downloadPath;
File desc = new File(downloadPath);
if (!desc.getParentFile().exists()) {
desc.getParentFile().mkdirs();
}
return downloadPath;
}
/**
* 编码文件名
*/
private static String encodingFilename(String filename) {
filename = UUID.randomUUID().toString() + "_" + filename + "." + ExcelTypeEnum.XLSX.getValue();
return filename;
}
/**
* Excel 类型枚举
*/
enum ExcelTypeEnum {
XLS("xls"), XLSX("xlsx");
private String value;
ExcelTypeEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
}
在浏览器中访问localhost:8080/export 或者 localhost:8080/outexcel 即可将自定义的十多条用户数据导出到Excel表格中 打开即可查看
(2)导入
导入HTML页面简单编写
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>poi导入测试页面</title>
</head>
<body>
<h2><p> poi导入测试页面</p></h2>
<div>
<form method="POST" enctype="multipart/form-data" action="/import/import1">
<p>
文件:<input type="file" name="file"/>
<input type="submit" value="导入1 普通导入"/>
</p>
</form>
</div>
<div>
<form method="POST" enctype="multipart/form-data" action="/import/import2">
<p>
文件:<input type="file" name="file"/>
<input type="submit" value="导入2 文件内容校验"/>
</p>
</form>
</div>
</body>
</html>
普通导入
使用本文提供的Excel工具类进行导入
/**
* 普通导入
*
* @param file
*/
@PostMapping("/import1")
public Object importExcel(MultipartFile file) throws IOException {
List<User> users = ExcelUtils.importExcel(file, User.class);
users.forEach(e -> System.out.println(e));
return users;
}
字典使用
public class IDictHandlerImpl implements IExcelDictHandler
校验导入
新建导入导出实体类
Easypoi 中的一些注解 校验
@NotNull 表示导入的此字段值不能为空
@Max 表示此字段值 最大值
@Min 表示此字段值 最小值
还可以正则校验等
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserVo {
@Excel(name = "用户名", width = 15)
@NotNull(message = "用户名不能为空哦!!")
private String username;
@Excel(name = "密码", width = 15, orderNum = "4")
@NotNull(message = "密码不能为空!!")
private String password;
@Excel(name = "性别", replace = {"男_true", "女_false"}, suffix = "生", width = 15, orderNum = "1")
private Boolean sex;
@Excel(name = "年龄")
@Max(value = 65,message = "年龄太大了,不适合工作哦")
private Integer age;
@Excel(name = "头像", width = 15, height = 15, type = 2, orderNum = "2")
private String headimg;
@Excel(name = "创建时间", exportFormat = "yyyy-MM-dd HH:mm:ss", importFormat ="yyyy-MM-dd HH:mm:ss",width = 25, orderNum = "3")
private LocalDateTime createTime;
/**连表查询时 需要深入导出关联对象属相*/
@ExcelEntity
private DepartMent departMent;
自定义校验规则
本文自定义的校验规则 是校验用户名不可重复(其他校验规则可根据自己的业务场景来定) 须编写一个类 实现IExcelVerifyHandler 并覆写其中的方法
/**
* @author : leilei
* @date : 15:36 2020/2/22
* @desc :自定义excel校验名字是否重复 实现IExcelVerifyHandler接口
*/
@Component
public class MyExcelverifiyName implements IExcelVerifyHandler<UserVo> {
@Autowired
private ICheckUserVoService checkUserVoService;
@Override
public ExcelVerifyHandlerResult verifyHandler(UserVo userVo) {
//设置默认验证为true
ExcelVerifyHandlerResult excelVerifyHandlerResult = new ExcelVerifyHandlerResult(true);
if (StringUtils.isNotBlank(userVo.getUsername())) {
UserVo one = checkUserVoService.findOneByname(userVo.getUsername());
//查询不为空则说明数据库中用户已存在,此次录入为重复录入
if (one != null) {
excelVerifyHandlerResult.setSuccess(false);
excelVerifyHandlerResult.setMsg("对不起,此用户已存在,请不要重复提交");
}
}
return excelVerifyHandlerResult;
}
}
校验规则实现类与接口编写
/**
* @author : leilei
* @date : 15:51 2020/2/22
* @desc : 自定义校验规则接口
*/
public interface ICheckUserVoService {
UserVo findOneByname(String username);
}
/**
* @author : leilei
* @date : 15:51 2020/2/22
* @desc : 校验用户是否重复
*/
@Service
public class CheckUserVoServiceImpl implements ICheckUserVoService {
@Override
public UserVo findOneByname(String username) {
/**
* 虚假数据 由于我这里是根据名字查 那么我模拟一个对象返回即可 (实际开发可根据身份证去数据库查询)
*/
if (username .equals("leilei0") ) {
return new UserVo("leilei0", "1", true, 22, "aaa", LocalDateTime.now(), new DepartMent("dianshan"));
}
return null;
}
}
控制层
/**
* 文件校验导入
*
* @return
*/
@RequestMapping("/import2")
public Object upload(MultipartFile file,HttpServletResponse resp) throws Exception {
//导入的基本配置
ImportParams params = new ImportParams();
//表头一行
params.setHeadRows(1);
//标题一行
params.setTitleRows(1);
//代表导入这里是需要验证的(根据字段上的注解校验)
params.setNeedVerify(true);
//设及一个自定义校验 (自定义校验名字不可重复)
params.setVerifyHandler(excelVerifyHandler);
//使用框架自身导入工具
ExcelImportResult<UserVo> result = ExcelImportUtil.importExcelMore(file.getInputStream(), UserVo.class, params);
//导入成功的数据
List<UserVo> list = result.getList();
//失败结果集
List<UserVo> failList = result.getFailList();
//拿到导出失败的工作簿
Workbook failWorkbook = result.getFailWorkbook();
//验证是否有失败的数据
if (result.isVerifyFail()) {
ServletOutputStream fos = resp.getOutputStream();
//mime类型
resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
resp.setHeader("Content-disposition", "attachment;filename=error.xlsx");
result.getFailWorkbook().write(fos);
fos.close();
}
return failList;
}
导入后 会下载一个Excel 文件 文件中会包含导入失败的数据以及失败原由
结语
SpringBoot 与EasyPoi 导入导出Excel 就到这里了 附上我的源码地址: springboot-easypoi