架构(十二)动态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下载文件,并转为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博客

四、效果

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

002b3a272e1244638e4bb87423ebc406.png

五、总结

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

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法学习——华为机考题库9(HJ56 - HJ63)

算法学习——华为机考题库9&#xff08;HJ56 - HJ63&#xff09; HJ56 完全数计算 描述 完全数&#xff08;Perfect number&#xff09;&#xff0c;又称完美数或完备数&#xff0c;是一些特殊的自然数。 它所有的真因子&#xff08;即除了自身以外的约数&#xff09;的和&…

基于idea解决springweb项目的Java文件无法执行问题

前言 上一篇文章的话介绍了spring以及创建spring项目&#xff0c;但是因为有宝子私聊我说创建的项目那个JAVA文件显示灰色还有一个红点&#xff0c;问我怎么解决下面我来简答的写一下怎么修改配置让他正常的运行 配置 原因好像是因为基于maven的JAVA项目构架&#xff0c;对应…

主干网络篇 | YOLOv5/v7 更换主干网络为 VGG13 / VGG16 / VGG19 | 对比实验必备

论文地址:https://arxiv.org/pdf/1409.1556.pdf 在这项工作中,我们研究了卷积网络深度对其在大规模图像识别环境中准确性的影响。我们的主要贡献是对使用非常小(33)卷积滤波器的架构的不断增加深度的网络进行了彻底评估,这表明通过将深度推进到16-19个权重层,可以在先前…

【漏洞复现】狮子鱼CMS某SQL注入漏洞

Nx01 产品简介 狮子鱼CMS&#xff08;Content Management System&#xff09;是一种网站管理系统&#xff0c;它旨在帮助用户更轻松地创建和管理网站。该系统拥有用户友好的界面和丰富的功能&#xff0c;包括页面管理、博客、新闻、产品展示等。通过简单直观的管理界面&#xf…

怎么把视频音乐提取成mp3?分享详细工具和方法!

在数字媒体时代&#xff0c;音乐已经成为我们生活中不可或缺的一部分。有时候&#xff0c;我们会在社交媒体、视频分享网站或在线视频平台上看到一些非常喜欢的视频音乐&#xff0c;想要将其保存为MP3格式以便随时随地聆听。那么&#xff0c;如何从视频中提取音乐并转换为MP3格…

SpringBoot源码解读与原理分析(七)BeanFactory

文章目录 3 SpringBoot的IOC容器3.1 SpringFramework的IOC容器3.1.1 BeanFactory3.1.1.1 BeanFactory根接口3.1.1.2 HierarchicalBeanFactory3.1.1.3 ListableBeanFactory3.1.1.4 AutowireCapableBeanFactory3.1.1.5 ConfigurableBeanFactory3.1.1.6 AbstractBeanFactory3.1.1.…

ctfshow-命令执行(web118-web122)

web118 是一个窗口 查看源码 发现是system($code) 命令执行 经过测试禁用了很多东西 很多很多 $IFS可以 思路就是使用系统变量 构造我需要的poc 这些都是系统的环境变量 这是答案${PATH:~A}${PWD:~A}$IFS????.??? 解释一下 PATH变量输出结尾一般都是n 因为网站默认根目…

setState的参数

目录 1、setState的第一个参数 2、setState的第二个参数 3、在 React 底层主要做了那些事呢&#xff1f; 4、类组件如何限制 state 更新视图 React 项目中的 UI 的改变来源于 State 改变&#xff0c;类组件中 setState 是更新组件&#xff0c;渲染视图的 1、setState的第一个参…

中文GPTS,字节中文扣子Coze使用全教程

字节出自己的GPTS了&#xff0c;名字英文名叫coze&#xff0c;中文名叫“扣子”。和OpenAI的GPTS类似。具有可定制性和完成特定任务的强大功能&#xff0c;它提供了一种新的GPT方式&#xff0c;可以让用户根据自己的需求定制化&#xff0c;并与其他用户共享。 国内用的是云雀大…

Python数据可视化库之mplfinance使用详解

概要 Python 是一种强大的编程语言,拥有众多用于数据可视化的库和工具。其中之一是 mplfinance(Matplotlib Finance),它是基于 Matplotlib 的库,专门用于创建金融图表和交互式金融数据可视化。本文将深入介绍 mplfinance,包括其基本概念、功能特性以及如何使用示例代码创…

Java 内存区域介绍

&#xff08;1&#xff09;程序计数器 程序计数器主要有两个作用&#xff1a; 字节码解释器通过改变程序计数器来依次读取指令&#xff0c;从而实现代码的流程控制&#xff0c;如&#xff1a;顺序执行、选择、循环、异常处理。 在多线程的情况下&#xff0c;程序计数器用于记录…

每日一练:LeeCode-106、从中序与后序遍历序列构造⼆叉树、LeeCode-106、从前序与中序遍历序列构造二叉树【二叉树+DFS+分治】

本文是力扣LeeCode-106、从中序与后序遍历序列构造二叉树 LeeCode-105、从前序与中序遍历序列构造二叉树 学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐LeeCode。 106、从中序与后序遍历序列构造⼆叉树 给定两个整数数组 inorder…

【JavaScript 】finally() 方法和Filter() 方法

JavaScript 中的finally() 方法 finally是 JavaScript 构造中使用的方法try-catch。try它在and阻塞之后执行catch&#xff0c;无论 Promise 是已履行还是已拒绝。该函数的主要作用是执行必要的清理任务并向用户传达消息。一个常见的用例可能是通知用户“您的请求已被处理”&am…

C# OpenVino Yolov8 Pose

目录 效果 模型信息 项目 代码 下载 效果 模型信息 Model Properties ------------------------- date&#xff1a;2023-09-07T17:11:43.091306 description&#xff1a;Ultralytics YOLOv8n-pose model trained on /usr/src/app/ultralytics/datasets/coco-pose.yaml a…

93 log4j-slf4j-impl 搭配上 log4j-to-slf4j 导致的 StackOverflow

前言 呵呵 最近想要 做一个 mongo 低版本的客户端读取高版本的服务端传递过来的数据造成的一个错误的时候, 出现了这样的问题 引入了 mongo-java-driver 之后, 使用相关 api 的时候会触发 com.mongo.internal.connection.BaseCluser 的初始化, 其依赖的 Loggers 间接的依赖…

MyBatisPlus之分页查询及Service接口运用

一、分页查询 1.1 基本分页查询 配置分页查询拦截器 package com.fox.mp.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springfra…

分享86个表单按钮JS特效,总有一款适合您

分享86个表单按钮JS特效&#xff0c;总有一款适合您 86个表单按钮JS特效下载链接&#xff1a;https://pan.baidu.com/s/1WwQGFPWv8464JBcuEMJZ_Q?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;…

SpringBoot3整合Mybatis-Plus,自定义动态数据源starter

文章目录 前言正文一、项目总览二、核心代码展示2.1 自定义AbstractRoutingDataSource2.2 动态数据源DynamicDataSource2.3 动态数据源自动配置2.4 动态数据源上下文DynamicDataSourceContextHolder2.5 动态数据源修改注解定义2.6 修改切面DynamicDataSourceAspect2.7 动态数据…

嵌入式系统的前景:未来智能汽车

&#xff08;本文为简单介绍&#xff0c;个人的观点仅供参考&#xff09; 智能汽车时代已经来临!未来十年,我们的汽车将变得越来越智能化。各大汽车公司在研发自动驾驶技术,目标是实现真正的无人驾驶。要实现这一目标,嵌入式系统将发挥关键作用。 简单来说,嵌入式系统就是在汽…

【Linux】指令提权-sudo

Hello everybody&#xff0c;新年快乐&#xff01;哈哈&#xff01;今天打算给大家讲讲指令提权的相关知识&#xff0c;虽然内容不多&#xff0c;但有时却很有用。在我们学习过权限&#xff0c;vim后就可以学习指令提权啦&#xff0c;没看过的宝子们建议先去看一看我之前的文章…
最新文章