一、引言

作者最近的平台项目需要生成excel,excel的导入导出是常用的功能,但是作者想做成动态的,不要固定模板,那就看看怎么实现。

二、后端

先捋一下原理,前后端的交互看起来是制定好的接口,其实根本上是数据键值对的映射,后端可以直接用Map进行接收,只不过接收回来的数据如果是对象嵌套对象或者集合嵌套,那么就要用object接收之后再解析。

而对于excel的导入导出来说,基本上都是转字符串再去填入文件,也不会有什么嵌套,所以可以直接用Map接收。

1、pom

先引入工具包

org.apache.poipoi-ooxml3.17org.apache.xmlbeansxmlbeans

2、导入

导入按功能不同,步骤也不一样,如果是为了业务处理,那就是把excel数据解析之后处理完,前端再去查

作者这边是解析完excel之后把数据直接给前端,一个意思,主要是解析excel

首先要把excel给下载下来

@Servicepublic 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> downloadExcel(String excelDownloadUrl) {// 1. 通过http下载文件,并转为bytesbyte[] 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> convertXlsxExcel(ByteArrayInputStream byteInputStream) {List<List> res = new ArrayList();XSSFWorkbook sheets = null;try {// 1. 转为工作薄sheets = new XSSFWorkbook(byteInputStream);// 2. 取第一个SheetXSSFSheet 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<List> convertXlsExcel(ByteArrayInputStream byteInputStream) {List<List> res = new ArrayList();HSSFWorkbook sheets = null;try {// 1. 转为工作薄sheets = new HSSFWorkbook(byteInputStream);// 2. 取第一个SheetHSSFSheet 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<List>,第一行是列名作为键,下面行数据都作为值

if (CollectionUtilsExt.isBlank(fileList) || fileList.size() <= 1) {throw new OrderException("EXCEL_NO_DATA");}List<Map> res = new ArrayList();List cellName = fileList.get(0);for (int i = 1; i < fileList.size(); i++) {List row = fileList.get(i);Map 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

@Servicepublic class GenerateExcelUtil {private static final int SHEET_ROW = 1000;private static final short FONT_SIZE = 11;private int getCellWidth(String cellName) {// 根据列名获取配置的列宽度,不配置也行,默认宽度Map 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 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> excelBoList,List 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 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 map, CellStyle style,List cellName) {SXSSFRow row = sheet.createRow(rowIndex);for (int i = 0; i < cellName.size(); i++) {createCell(row, i, map.get(cellName.get(i)), style);}}}

上传服务器

@Servicepublic 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博客

四、效果

只要导入导出的数据变一下,表格就会自动展示不同的列和数据

五、总结

很多东西做成通用的会比较方便,但是比较适合内部项目,减少人力