架构(十二)动态Excel

一、引言

        作者最近的平台项目需要生成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下载文件,并转为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<String>> convertXlsxExcel(ByteArrayInputStream byteInputStream) {List<List<String>> 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<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. 取第一个SheetHSSFSheet 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博客

四、效果

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

002b3a272e1244638e4bb87423ebc406.png

五、总结

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

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/464237.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构和算法】--- 基于c语言排序算法的实现(2)

目录 一、交换排序1.1 冒泡排序1.2 快速排序1.2.1 hoare法1.2.2 挖坑法1.2.3 前后指针法 1.3 快速排序优化1.3.1 三数取中法选key1.3.2 递归到小的子区间使用插入排序 1.4 快排非递归版 二、归并排序2.1 归并排序2.1.1 递归版2.1.2 非递归版 一、交换排序 基本思想&#xff1a…

【java】Hibernate访问数据库

一、Hibernate访问数据库案例 Hibernate 是一个在 Java 社区广泛使用的对象关系映射&#xff08;ORM&#xff09;工具。它简化了 Java 应用程序中数据库操作的复杂性&#xff0c;并提供了一个框架&#xff0c;用于将对象模型数据映射到传统的关系型数据库。下面是一个简单的使…

Redisson分布式锁 原理 + 运用 记录

Redisson 分布式锁 简单入门 pom <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version></dependency>配置类 package com.hmdp.config;import org.redisson.Redisson;…

无人机飞控算法原理基础研究,多旋翼无人机的飞行控制算法理论详解,无人机飞控软件架构设计

多旋翼无人机的飞行控制算法主要涉及到自动控制器、捷联式惯性导航系统、卡尔曼滤波算法和飞行控制PID算法等部分。 自动控制器是无人机飞行控制的核心部分&#xff0c;它负责接收来自无人机传感器和其他系统的信息&#xff0c;并根据预设的算法和逻辑&#xff0c;对无人机的姿…

M1 Mac使用SquareLine-Studio进行LVGL开发

背景 使用Gui-Guider开发遇到一些问题&#xff0c;比如组件不全。使用LVGL官方的设计软件开发 延续上一篇使用的基本环境。 LVGL项目 新建项目 选择Arduino的项目&#xff0c;设定好分辨率及颜色。 设计UI 导出代码 Export -> Create Template Project 导出文件如图…

【AI大模型应用开发】【LangChain系列】5. 实战LangChain的智能体Agents模块

大家好&#xff0c;我是【同学小张】。持续学习&#xff0c;持续干货输出&#xff0c;关注我&#xff0c;跟我一起学AI大模型技能。 在我前面的MetaGPT系列文章中&#xff0c;已经对智能体有了一个认知&#xff0c;重温一下&#xff1a; 智能体 LLM观察思考行动记忆 将大语言模…

P2196 [NOIP1996 提高组] 挖地雷

网址如下&#xff1a; P2196 [NOIP1996 提高组] 挖地雷 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 早上看二进制下标树看到一半被高中同学要求看看这一题 他只说看看&#xff0c;也没问什么东西&#xff0c;怪 就做了一下 思路还算是简单的 dp值代表在这个地窖的最大炸…

hexo 博客搭建以及踩雷总结

搭建时的坑 文章置顶 安装一下这个依赖 npm install hexo-generator-topindex --save然后再文章的上面设置 top: number&#xff0c;数字越大&#xff0c;权重越大&#xff0c;也就是越靠顶部 hexo 每次推送 nginx 都访问不到 宝塔自带的 nginx 的 config 里默认的角色是 …

02 数据库管理 数据表管理

文章目录 数据库管理数据表管理基础数据类型表的基本操作 数据库管理 查看已有库 show databases; 创建库 create database 库名 [character set utf8]; e.g. 创建stu数据库&#xff0c;编码为utf8 create database stu character set utf8; create database stu charsetutf8;…

Java图形化界面编程——菜单组件 笔记

2.7 菜单组件 ​ 前面讲解了如果构建GUI界面&#xff0c;其实就是把一些GUI的组件&#xff0c;按照一定的布局放入到容器中展示就可以了。在实际开发中&#xff0c;除了主界面&#xff0c;还有一类比较重要的内容就是菜单相关组件&#xff0c;可以通过菜单相关组件很方便的使用…

随机应变——Sleep()和_sleep()

Sleep()的困窘困o(╯□╰)o 最近在写程序时&#xff1a; 函数的颜色是紫色的。 可如果你的Sleep()是粉色的&#xff1a; Sleep()在一些情况下是粉色的&#xff1a; 那么&#xff1a; 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。…

廖雪峰Python教程实战Day 2 - 编写Web App骨架,运行后不显示网页如何解决

教程代码如下&#xff1a; import logging; logging.basicConfig(levellogging.INFO)import asyncio, os, json, time from datetime import datetimefrom aiohttp import webdef index(request):return web.Response(bodyb<h1>Awesome</h1>)asyncio.coroutine de…