作者最近的平台项目需要生成excel,excel的导入导出是常用的功能,但是作者想做成动态的,不要固定模板,那就看看怎么实现。
先捋一下原理,前后端的交互看起来是制定好的接口,其实根本上是数据键值对的映射,后端可以直接用Map进行接收,只不过接收回来的数据如果是对象嵌套对象或者集合嵌套,那么就要用object接收之后再解析。
而对于excel的导入导出来说,基本上都是转字符串再去填入文件,也不会有什么嵌套,所以可以直接用Map接收。
先引入工具包
org.apache.poi poi-ooxml3.17 org.apache.xmlbeans xmlbeans
导入按功能不同,步骤也不一样,如果是为了业务处理,那就是把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> 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
> convertXlsxExcel(ByteArrayInputStream byteInputStream) { List
> 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
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 > convertXlsExcel(ByteArrayInputStream byteInputStream) { List
> 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
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 getStringFormRow(Row row, DataFormatter formatter) { if (Objects.isNull(row)) { return null; } List 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>,第一行是列名作为键,下面行数据都作为值
if (CollectionUtilsExt.isBlank(fileList) || fileList.size() <= 1) { throw new OrderException("EXCEL_NO_DATA"); } List
导出的话就是把数据生成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) { // 根据列名获取配置的列宽度,不配置也行,默认宽度 MapcellWidthMap = 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 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
上传服务器
@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博客
只要导入导出的数据变一下,表格就会自动展示不同的列和数据
很多东西做成通用的会比较方便,但是比较适合内部项目,减少人力