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中
@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);
}
貼出導出導入工具類代碼
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;
}
校驗導入
新建導入導出實體類
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