前言
利用Poi進行Excel導入導出,在SpringBoot框架下進行測試,前端小小的使用了下Vue,並使用了Bootstrap-fileinput作爲前端上傳組件作爲參考,下載採用原生ajax下載形式,因爲一直忘記前端下載怎麼寫還有後端怎麼設置Request-Header,所以提供一下一整個前後端的思路,以後方便進行查閱,只是個人學習用,所以僅供參考。
引入POI依賴
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
ExcelUtil工具類
excel的工具類,這裏基本上把導入導出的功能大致做了一遍,但還有值得改進的地方,不過想說的是,基本能用!
package com.podago.demo.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
/**
* Excel工具類 - 提供導入導出功能
* @author chenxiaotao
*/
public class ExcelUtil {
private static final Logger logger = LoggerFactory.getLogger(ExcelUtil.class);
private static final String XLS = "xls";
private static final String XLSX = "xlsx";
/**
* 讀取Excel文件數據,並轉換爲二維數組形式輸出
* @param file 文件流
* @return Excel數據
*/
public static List<List<Object>> readExcel(MultipartFile file) {
Workbook workbook = null;
String fileName = file.getOriginalFilename();
InputStream in = null;
try {
in = file.getInputStream();
if (fileName != null && fileName.endsWith(XLS)) {
// 2003
workbook = readExcel2003(in);
} else if (fileName != null && fileName.endsWith(XLSX)) {
// 2007
workbook = readExcel2007(in);
}
} catch (IOException e) {
logger.error("excel輸入流讀取錯誤,返回空列表", e);
return Collections.emptyList();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
logger.error("excel輸入流關閉錯誤", e);
}
}
}
if (workbook == null) {
return Collections.emptyList();
}
return readExcel(workbook);
}
/**
* 讀取2007+版本的excel文件,即後綴爲xlsx
*/
private static Workbook readExcel2007(InputStream in) {
Workbook workbook = null;
try {
workbook = new XSSFWorkbook(in);
} catch (IOException e) {
e.printStackTrace();
}
return workbook;
}
/**
* 讀取2003版本的excel文件,即後綴爲xls
*/
private static Workbook readExcel2003(InputStream in) {
Workbook workbook = null;
try {
workbook = new HSSFWorkbook(in);
} catch (IOException e) {
e.printStackTrace();
}
return workbook;
}
/**
* 根據Workbook,讀取Excel數據
* @param workbook excel工作簿
* @return excel中數據
*/
private static List<List<Object>> readExcel(Workbook workbook) {
List<List<Object>> res = new ArrayList<>();
Sheet sheet = workbook.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
for (int i = 0; i <= lastRowNum; i++) {
Row row = sheet.getRow(i);
int lastCellNum = row.getLastCellNum();
List<Object> rdata = new ArrayList<>(lastCellNum);
for (int j = 0 ; j < lastCellNum; j++) {
Cell cell = row.getCell(j);
rdata.add(getCell(cell));
}
res.add(rdata);
}
return res;
}
/**
* 獲取單元格數據
* @param cell 單元格對象
* @return 單元格中數據
*/
private static Object getCell(Cell cell) {
switch (cell.getCellTypeEnum()) {
case NUMERIC:
return cell.getNumericCellValue();
case STRING:
return cell.getStringCellValue();
case BOOLEAN:
return cell.getBooleanCellValue();
case FORMULA:
return cell.getCellFormula();
case BLANK:
return StringUtils.EMPTY;
case ERROR:
return StringUtils.EMPTY;
case _NONE:
return StringUtils.EMPTY;
default:
return null;
}
}
/**
* 輸出Excel數據流,並最終關閉流
* @param dataLists 傳入數據
* @param out 輸出流
*/
public static void createExcel(List<List<Object>> dataLists, OutputStream out) {
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet();
HSSFCellStyle titleCellStyle = workbook.createCellStyle();
//設置單元標題樣式
titleCellStyle.setAlignment(HorizontalAlignment.CENTER);
titleCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
titleCellStyle.setFillForegroundColor(HSSFColorPredefined.SKY_BLUE.getIndex());
titleCellStyle.setWrapText(true);
//設置單元標題字體
HSSFFont titleFont = workbook.createFont();
titleFont.setFontHeightInPoints((short) 13);
titleCellStyle.setFont(titleFont);
//設置表格內容單元樣式
HSSFCellStyle valueCellStyle = workbook.createCellStyle();
valueCellStyle.setAlignment(HorizontalAlignment.CENTER);
valueCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
HSSFFont cellFont = workbook.createFont();
cellFont.setFontHeightInPoints((short) 12);
valueCellStyle.setFont(cellFont);
// 二維形式
for (int i = 0; i < dataLists.size(); i++) {
HSSFRow row = sheet.createRow(i);
List<Object> objects = dataLists.get(i);
for (int j = 0; j < objects.size(); j++) {
HSSFCell valueCell = row.createCell(j);
valueCell.setCellStyle(valueCellStyle);
valueCell.setCellValue(objects.get(j).toString());
}
}
try {
workbook.write(out);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//導出成功!
}
}
分解讀取Excel文件的思路
- 獲取MultiPartFile文件,因爲是用的SpringBoot,所以直接傳入這個就可以獲取到文件名以及文件流,如果覺得和Spring有耦合的話可以將方法寫成 readExcel(String fileName, InputStream in)
- 判斷文件類型,簡單判斷文件後綴名,然後根據文件類型(因爲Excel2003和Excel2007+的讀取方式不同),調用不同的讀取方法,Excel2003是用的 HSSFWorkbook,Excel2007用的是XSSFWorkbook,獲取後賦值給Workbook引用,這裏利用了多態的思想。
- 獲取到Workbook後,我們便可以開始讀取數據
/** * 根據Workbook,讀取Excel數據 * @param workbook excel工作簿 * @return excel中數據 */ private static List<List<Object>> readExcel(Workbook workbook) { List<List<Object>> res = new ArrayList<>(); Sheet sheet = workbook.getSheetAt(0); int lastRowNum = sheet.getLastRowNum(); for (int i = 0; i <= lastRowNum; i++) { Row row = sheet.getRow(i); int lastCellNum = row.getLastCellNum(); List<Object> rdata = new ArrayList<>(lastCellNum); for (int j = 0 ; j < lastCellNum; j++) { Cell cell = row.getCell(j); rdata.add(getCell(cell)); } res.add(rdata); } return res; }
解釋下各個方法:
- workbook.getSheetAt(index): 獲取指定下標下的sheet,sheet就是你打開Excel後右下角就可以看到這樣一個標識
主要是用於打印時區分紙張,一個Sheet表示一張紙,一般我們讀取第一個也就是下標爲0的一個,當然你可以自行對方法進行擴展,寫死總是不好的。
- sheet.getLastRowNum(): 獲取該Sheet行數
- row.getLastCellNum(): 獲取該行有多少單元格,即多少列
瞭解這些方法後,我們就可以用遍歷二維數組的思想去遍歷每個單元格就行了,需要注意的是要根據單元格類型調用不同方法獲取單元格數據
/**
* 獲取單元格數據
* @param cell 單元格對象
* @return 單元格中數據
*/
private static Object getCell(Cell cell) {
switch (cell.getCellTypeEnum()) {
case NUMERIC:
return cell.getNumericCellValue();
case STRING:
return cell.getStringCellValue();
case BOOLEAN:
return cell.getBooleanCellValue();
case FORMULA:
return cell.getCellFormula();
case BLANK:
return StringUtils.EMPTY;
case ERROR:
return StringUtils.EMPTY;
case _NONE:
return StringUtils.EMPTY;
default:
return null;
}
}
分解導出Excel的思路
- 創建一個空的Workbook (工作簿)
- 創建一個空的Sheet
- 設置樣式
- 根據數據(依舊是二維數組的形式),來創建行,創建單元格
- 將工作簿內容寫入輸出流進行輸出,最後關閉流
/**
* 輸出Excel數據流,並最終關閉流
* @param dataLists 傳入數據
* @param out 輸出流
*/
public static void createExcel(List<List<Object>> dataLists, OutputStream out) {
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet();
HSSFCellStyle titleCellStyle = workbook.createCellStyle();
//設置單元標題樣式
titleCellStyle.setAlignment(HorizontalAlignment.CENTER);
titleCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
titleCellStyle.setFillForegroundColor(HSSFColorPredefined.SKY_BLUE.getIndex());
titleCellStyle.setWrapText(true);
//設置單元標題字體
HSSFFont titleFont = workbook.createFont();
titleFont.setFontHeightInPoints((short) 13);
titleCellStyle.setFont(titleFont);
//設置表格內容單元樣式
HSSFCellStyle valueCellStyle = workbook.createCellStyle();
valueCellStyle.setAlignment(HorizontalAlignment.CENTER);
valueCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
HSSFFont cellFont = workbook.createFont();
cellFont.setFontHeightInPoints((short) 12);
valueCellStyle.setFont(cellFont);
// 二維形式
for (int i = 0; i < dataLists.size(); i++) {
HSSFRow row = sheet.createRow(i);
List<Object> objects = dataLists.get(i);
for (int j = 0; j < objects.size(); j++) {
HSSFCell valueCell = row.createCell(j);
valueCell.setCellStyle(valueCellStyle);
valueCell.setCellValue(objects.get(j).toString());
}
}
try {
workbook.write(out);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//導出成功!
}
ExcelController
控制層,主要記憶一下SpringMVC的上傳下載流程,還有下載文件時ResponseHeader的設置
package com.podago.demo.controller;
import com.podago.demo.utils.ExcelUtil;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* @author chenxiaotao
* @version 1.0
* @date 2019/3/27 9:57
**/
@RestController
@RequestMapping("/excel")
public class ExcelController {
private static final String EXCEL2003_CONTENT_TYPE = "application/vnd.ms-excel";
private static final String EXCEL2007_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public List<List<Object>> upload(@RequestPart MultipartFile excel) {
String contentType = excel.getContentType();
if (contentType.equals(EXCEL2003_CONTENT_TYPE) || contentType.equals(EXCEL2007_CONTENT_TYPE)) {
return ExcelUtil.readExcel(excel);
} else {
return new ArrayList<>(0);
}
}
@RequestMapping(value = "/download", method = RequestMethod.POST, produces = {"application/vnd.ms-excel;charset=UTF-8"})
public void download(@RequestBody List<List<Object>> data, HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setHeader("Content-Disposition", "attachment;filename=excel.xls");
ServletOutputStream outputStream = response.getOutputStream();
ExcelUtil.createExcel(data, outputStream);
}
}
前端上傳下載
前端使用了boostrap-fileinput組件,這個組件是可以中文化的,並且感覺很強大,關於這個組件的使用這裏不過多描述,可自行查閱資料。
前端實現的就是,把Excel導入後轉換爲JSON數據返回回來,把這段JSON數據傳回後端能夠導出爲Excel文件
excel.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- Bootstrap-FileInput CSS -->
<link rel="stylesheet" href="dict/plugins/bootstrap-fileinput/css/fileinput.css">
</head>
<body>
<div id="app" class="panel col-md-8">
<input id="txt_file" name="excel" type="file" class="file" multiple
data-show-upload="true" data-show-caption="true" data-show-preview="false" data-upload-url="/excel/upload" data-allowed-file-extensions='["xlsx","xls"]'>
<h5>excel文件內容:</h5>
<textarea class="form-control" disabled rows="5">{{excel_data}}</textarea>
<a @click="download" class="btn btn-primary">導出Excel</a>
</div>
</body>
<script src="dict/plugins/jquery-3.3.1.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<!-- Bootstrap-FileInput JS -->
<script src="dict/plugins/bootstrap-fileinput/js/fileinput.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="dict/excel/excel.js"></script>
</html>
excel.js
這裏的原生ajax異步下載可以多注意下,它異步獲取後 response 的 blob 後利用a標籤進行下載,我覺得是個挺好用的東西。
var app = new Vue({
el: '#app',
data: {
excel_data: ''
},methods:{
download:function() {
var url = '/excel/download';
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true); // 也可以使用POST方式,根據接口
xhr.responseType = "blob"; // 返回類型blob
xhr.setRequestHeader("Content-Type", "application/json;charset=utf-8");
// 定義請求完成的處理函數,請求前也可以增加加載框/禁用下載按鈕邏輯
xhr.onload = function () {
// 請求完成
if (this.status === 200) {
// 返回200
var blob = this.response;
var reader = new FileReader();
var filename = this.getResponseHeader("Content-Disposition").substring(20);
reader.readAsDataURL(blob); // 轉換爲base64,可以直接放入a表情href
reader.onload = function (e) {
// 轉換完成,創建一個a標籤用於下載
var a = document.createElement('a');
a.download = filename;
a.href = e.target.result;
$("body").append(a); // 修復firefox中無法觸發click
a.click();
$(a).remove();
}
}
};
// 發送ajax請求
xhr.send(JSON.stringify(app.excel_data))
}
}, mounted: function() {
//導入文件上傳完成之後的事件
$("#txt_file").on("fileuploaded", function (event, data, previewId, index) {
app.excel_data = data.response;
});
}
});
最後展示的頁面:
測試
測試用Excel:
在前端頁面選擇文件後上傳:
上傳成功
測試一下 下載 功能:
點擊導出Excel按鈕進行導出,彈出下載框下載,打開Excel內容進行對比可知下載成功!