一、引言
作者最近的平台项目需要生成excel,excel的导入导出是常用的功能,但是作者想做成动态的,不要固定模板,那就看看怎么实现。
二、后端
先捋一下原理,前后端的交互看起来是制定好的接口,其实根本上是数据键值对的映射,后端可以直接用Map进行接收,只不过接收回来的数据如果是对象嵌套对象或者集合嵌套,那么就要用object接收之后再解析。
而对于excel的导入导出来说,基本上都是转字符串再去填入文件,也不会有什么嵌套,所以可以直接用Map接收。
1、pom
先引入工具包
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
<exclusions>
<exclusion>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
</exclusion>
</exclusions>
</dependency>
2、导入
导入按功能不同,步骤也不一样,如果是为了业务处理,那就是把excel数据解析之后处理完,前端再去查
作者这边是解析完excel之后把数据直接给前端,一个意思,主要是解析excel
首先要把excel给下载下来
@Service
public class ExcelWDownloadUtil {
private static final LoggerService LOG = LoggerServiceFactory.getLoggerService(FileWsHelper.class);
private static final String TITLE = "ExcelWDownloadUtil";
/**
* Function - 下载文件
*
* @param fileUrl 文件路径
* @return 文件内容
*/
public byte[] downloadBytes(String fileUrl) {
HttpGet httpGet = new HttpGet(fileUrl);
return HttpClientHelper.getInstance().getBytes(httpGet);
}
/**
* excel文件后缀
*/
private static final String EXCEL_FIX = "xlsx";
private static final String EXCEL_FIX_OLD = "xls";
/**
* 文件后缀分隔符
*/
private final static String FILE_SPLIT = ".";
public List<List<String>> downloadExcel(String excelDownloadUrl) {
// 1. 通过http下载文件,并转为bytes
byte[] fileBytes = downloadBytes(excelDownloadUrl);
// 2. 将byte数组转为流
ByteArrayInputStream byteInputStream = new ByteArrayInputStream(fileBytes);
// 3. 将流转为excel工作薄
String fileType = getFileType(excelDownloadUrl);
if (StringUtilsExt.equals(fileType, EXCEL_FIX)) {
return convertXlsxExcel(byteInputStream);
} else if (StringUtilsExt.equals(fileType, EXCEL_FIX_OLD)) {
return convertXlsExcel(byteInputStream);
}
LOG.error(TITLE, "file is not excel");
return null;
}
public List<List<String>> convertXlsxExcel(ByteArrayInputStream byteInputStream) {
List<List<String>> res = new ArrayList<>();
XSSFWorkbook sheets = null;
try {
// 1. 转为工作薄
sheets = new XSSFWorkbook(byteInputStream);
// 2. 取第一个Sheet
XSSFSheet sheet = sheets.getSheetAt(0);
// 3. 循环行列,转为String返回
DataFormatter formatter = new DataFormatter();
for (int i = 0; i <= sheet.getLastRowNum(); i++) {
List<String> rowString = getStringFormRow(sheet.getRow(i), formatter);
if (CollectionUtilsExt.isNotBlank(rowString)) {
res.add(rowString);
}
}
} catch (IOException e) {
LOG.error(TITLE, e);
throw new FileExecuteException("excel file io exception");
} finally {
// 关闭文件流
if (sheets != null) {
try {
sheets.close();
} catch (IOException e) {
LOG.error(TITLE, e);
}
}
}
return res;
}
public List<List<String>> convertXlsExcel(ByteArrayInputStream byteInputStream) {
List<List<String>> res = new ArrayList<>();
HSSFWorkbook sheets = null;
try {
// 1. 转为工作薄
sheets = new HSSFWorkbook(byteInputStream);
// 2. 取第一个Sheet
HSSFSheet sheet = sheets.getSheetAt(0);
// 3. 循环行列,转为String返回
DataFormatter formatter = new DataFormatter();
for (int i = 0; i < sheet.getLastRowNum(); i++) {
List<String> rowString = getStringFormRow(sheet.getRow(i), formatter);
if (CollectionUtilsExt.isNotBlank(rowString)) {
res.add(rowString);
}
}
} catch (IOException e) {
LOG.error(TITLE, e);
throw new FileExecuteException("excel file io exception");
} finally {
// 关闭文件流
if (sheets != null) {
try {
sheets.close();
} catch (IOException e) {
LOG.error(TITLE, e);
}
}
}
return res;
}
private List<String> getStringFormRow(Row row, DataFormatter formatter) {
if (Objects.isNull(row)) {
return null;
}
List<String> rowString = new ArrayList<>();
for (int j = 0; j < row.getLastCellNum(); j++) {
rowString.add(getStringFromCell(row.getCell(j), formatter));
}
return rowString;
}
private String getStringFromCell(Cell cell, DataFormatter formatter) {
if (Objects.isNull(cell)) {
return null;
}
if (CellType.NUMERIC == cell.getCellTypeEnum()) {
BigDecimal num = BigDecimal.valueOf(cell.getNumericCellValue());
// 判断是否有小数,防止1变成了1.0,下游会报错
if (new BigDecimal(num.intValue()).compareTo(num) == 0) {
return String.valueOf(num.intValue());
}
// 这里是防止出现科学计数法
return NumberToTextConverter.toText(cell.getNumericCellValue());
} else {
return formatter.formatCellValue(cell);
}
}
public static String getFileType(String fileName) {
if (StringUtilsExt.isBlank(fileName) || !fileName.contains(FILE_SPLIT)) {
return null;
}
return fileName.substring(fileName.lastIndexOf(FILE_SPLIT) + 1);
}
}
解析成键值对,说白了解析excel得到的List<List<String>>,第一行是列名作为键,下面行数据都作为值
if (CollectionUtilsExt.isBlank(fileList) || fileList.size() <= 1) {
throw new OrderException("EXCEL_NO_DATA");
}
List<Map<String, String>> res = new ArrayList<>();
List<String> cellName = fileList.get(0);
for (int i = 1; i < fileList.size(); i++) {
List<String> row = fileList.get(i);
Map<String, String> rowMap = new HashMap<>();
for (int j = 0; j < row.size(); j++) {
rowMap.put(cellName.get(j), row.get(j));
}
res.add(rowMap);
}
return res;
3、导出
导出的话就是把数据生成excel,第一把前端传的数据或者数据库查出来的数据生成excel,第二步把excel上传内部服务器,第三步把生成文件的地址给前端打开
生成excel
@Service
public class GenerateExcelUtil {
private static final int SHEET_ROW = 1000;
private static final short FONT_SIZE = 11;
private int getCellWidth(String cellName) {
// 根据列名获取配置的列宽度,不配置也行,默认宽度
Map<String, String> cellWidthMap = Config.getMap(CELL_WIDTH_MAP);
if (cellWidthMap == null || !cellWidthMap.containsKey(cellName)) {
return 4000;
}
return Integer.parseInt(cellWidthMap.get(cellName));
}
private short getCellColor(String cellName) {
// 根据列名获取配置的列颜色,不配置也行,默认颜色
Map<String, String> cellColorMap = Config.getMap(CELL_COLOR_MAP);
if (cellColorMap == null || !cellColorMap.containsKey(cellName)) {
return 0;
}
return Short.parseShort(cellColorMap.get(cellName));
}
public SXSSFWorkbook generateExcel(String sheetName, List<Map<String, String>> excelBoList,
List<String> cellNameList) {
// 生成excel文件
SXSSFWorkbook workbook = new SXSSFWorkbook(SHEET_ROW);
// 建表
SXSSFSheet sheet = workbook.createSheet(sheetName);
// 设置每列宽度
for (int i = 0; i < cellNameList.size(); i++) {
sheet.setColumnWidth(i, getCellWidth(cellNameList.get(i)));
}
// 构建表头
SXSSFRow rowHead = sheet.createRow(0);
for (int i = 0; i < cellNameList.size(); i++) {
createCell(rowHead, i, cellNameList.get(i), createTitleStyle(workbook, getCellColor(cellNameList.get(i))));
}
// 构建内容
CellStyle contentStyle = createContentStyle(workbook);
for (int i = 0; i < excelBoList.size(); i++) {
Map<String, String> excelBo = excelBoList.get(i);
createRow(sheet, i + 1, excelBo, contentStyle, cellNameList);
}
return workbook;
}
private CellStyle createTitleStyle(SXSSFWorkbook workbook, short color) {
Font boldFont = workbook.createFont();
boldFont.setFontHeightInPoints(FONT_SIZE);
boldFont.setBold(true);
boldFont.setColor(color);
CellStyle style = workbook.createCellStyle();
style.setFont(boldFont);
style.setWrapText(true);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
style.setBorderTop(BorderStyle.THIN);
return style;
}
private CellStyle createContentStyle(SXSSFWorkbook workbook) {
Font boldFont = workbook.createFont();
boldFont.setFontHeightInPoints(FONT_SIZE);
CellStyle style = workbook.createCellStyle();
style.setFont(boldFont);
style.setWrapText(true);
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
return style;
}
private void createCell(SXSSFRow row, int column, Object value, CellStyle style) {
SXSSFCell cell = row.createCell(column);
cell.setCellType(CellType.STRING);
cell.setCellValue(Null.or(value, Object::toString, null));
cell.setCellStyle(style);
}
private void createRow(SXSSFSheet sheet, int rowIndex, Map<String, String> map, CellStyle style,
List<String> cellName) {
SXSSFRow row = sheet.createRow(rowIndex);
for (int i = 0; i < cellName.size(); i++) {
createCell(row, i, map.get(cellName.get(i)), style);
}
}
}
上传服务器
@Service
public class ExcelUploadUtil {
private static final LoggerService LOG = LoggerServiceFactory.getLoggerService(ExcelUploadUtil.class);
private static final String TITLE = "ExcelUploadUtil";
/**
* 上传文件的地址
*/
private static final String UPLOAD_URL = "fileUploadUrl";
/**
* 上传文件的contentType
*/
private static final String EXCEL_CONTENT_TYPE =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
/**
* 创建文件的后缀
*/
public static final String FILE_SUFFIX = ".xlsx";
/**
* 请求头
*/
private static final String CONTENT_TYPE = "Content-Type";
public FileResponseBo uploadExcel(SXSSFWorkbook workbook, String filePrefix) {
File file = convertFile(workbook, filePrefix);
LOG.info(TITLE, "convertFile");
if (file == null) {
return null;
}
return uploadFile(file, EXCEL_CONTENT_TYPE);
}
private File convertFile(SXSSFWorkbook workbook, String filePrefix) {
File file = null;
FileOutputStream fos = null;
try {
file = File.createTempFile(filePrefix, FILE_SUFFIX);
fos = new FileOutputStream(file);
workbook.write(fos);
} catch (IOException e) {
// 这里上传文件有io异常无需处理,后续返回空,会对空处理
LOG.error(TITLE, e);
} finally {
// 关闭文件流
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
LOG.error(TITLE, e);
}
}
// 删除临时xml文件
workbook.dispose();
}
return file;
}
/**
* 上传文件
*
* @param file
* @param contentType
* @return
*/
private FileResponseBo uploadFile(File file, String contentType) {
String uploadUrl = Config.get(UPLOAD_URL);
LOG.info(TITLE, "uploadUrl:{}", uploadUrl);
HttpPost httpPost = new HttpPost(uploadUrl);
httpPost.setHeader(CONTENT_TYPE, contentType);
FileEntity fileEntity = new FileEntity(file);
httpPost.setEntity(fileEntity);
String res = HttpClientHelper.getInstance().doPost(httpPost);
LOG.info(TITLE, "res:{}", res);
return JSONUtil.parse(res, FileResponseBo.class);
}
}
前端使用Window.open就可以打开下载了
三、前端
前端可以参考前端(一)Vue+Java实现动态表格展示_java+vue显示数据库数据-CSDN博客
四、效果
只要导入导出的数据变一下,表格就会自动展示不同的列和数据
五、总结
很多东西做成通用的会比较方便,但是比较适合内部项目,减少人力