POI Excel导入导出(下)

news2024/11/14 6:01:11
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

上一篇通过四个简单的小例子,我们已经初步体会了POI导入导出Excle的便捷,但都是测试案例,并不是实际的开发场景。所以,这一篇我们来模拟一下web场景下的Excel上传及下载。

读取资源的几种方式

由于一般设计导出的话,很大概率都是按模板导出,此时我们可以把模板Excel文件放在resources下随着项目一起打包成jar发布:

通常有以下几种读取文件流的方式:

public class ReadResourceTest {

    @Test
    public void testResourceRead() throws IOException {

        // 第一种:使用Spring提供的ClassPathResource,有没有斜杆都可以(推荐,功能都封装好了)
        ClassPathResource classPathResource = new ClassPathResource("excel/student_template.xlsx");
        InputStream inputStream = classPathResource.getInputStream();
        System.out.println(inputStream);

        // 第二种:使用Class#getResourceAsStream(),要加/
        InputStream classResource = this.getClass().getResourceAsStream("/excel/student_template.xlsx");
        System.out.println(classResource);

        // 第三种:使用ClassLoader#getResourceAsStream(),不加/
        InputStream classLoaderResource = this.getClass().getClassLoader().getResourceAsStream("excel/student_template.xlsx");
        System.out.println(classLoaderResource);
    }
}

Excel文件上传、下载

在resources/static下新建一个表单,方便通过页面做Excel导入:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="/importExcel" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="file"/><br>
    <input type="submit" value="上传"/>
</form>

</body>
</html>

编写Controller

@RestController
public class ExcelController {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @GetMapping("/exportExcel")
    public void exportExcel(HttpServletResponse response) throws Exception {
        // 模拟从数据库查询数据
        List<Student> studentList = new ArrayList<>();
        studentList.add(new Student(1L, "周深(web导出)", 28, "贵州", new SimpleDateFormat("yyyy-MM-dd").parse("1992-9-29"), 161.0, true));
        studentList.add(new Student(2L, "李健(web导出)", 46, "哈尔滨", new SimpleDateFormat("yyyy-MM-dd").parse("1974-9-23"), 174.5, true));
        studentList.add(new Student(3L, "周星驰(web导出)", 58, "香港", new SimpleDateFormat("yyyy-MM-dd").parse("1962-6-22"), 174.0, false));

        // 读取模板(实际开发可以放在resources文件夹下,随着项目一起打包发布)
        InputStream excelInputStream = new ClassPathResource("excel/student_template.xlsx").getInputStream();
        // XSSFWorkbook除了直接接收Path外,还可以传入输入流
        XSSFWorkbook workbook = new XSSFWorkbook(excelInputStream);
        // 获取模板sheet
        XSSFSheet sheet = workbook.getSheetAt(0);
        // 找到数据起始行(前两行是标题和表头,要跳过,所以是getRow(2))
        XSSFRow dataTemplateRow = sheet.getRow(2);
        // 构造一个CellStyle数组,用来存放单元格样式。一行有N个单元格,数组初始长度就设置为N
        CellStyle[] cellStyles = new CellStyle[dataTemplateRow.getLastCellNum()];
        for (int i = 0; i < cellStyles.length; i++) {
            // 收集每一个格子对应的格式,你可以理解为准备了一把“格式刷”
            cellStyles[i] = dataTemplateRow.getCell(i).getCellStyle();
        }

        // 创建单元格,并设置样式和数据
        for (int i = 0; i < studentList.size(); i++) {
            // 注意是i+2,模板前两行是大标题和表头。你可能看着难受,想把上面for的i改为i+2,千万别。因为studentList必须从0开始取值
            XSSFRow row = sheet.createRow(i + 2);
            // 为每一行创建单元格并设置数据
            Student student = studentList.get(i);

            XSSFCell nameCell = row.createCell(0);// 创建单元格
            nameCell.setCellValue(student.getName());         // 设置值
            nameCell.setCellStyle(cellStyles[0]);             // 设置单元格样式

            XSSFCell ageCell = row.createCell(1);
            ageCell.setCellValue(student.getAge());
            ageCell.setCellStyle(cellStyles[1]);

            XSSFCell addressCell = row.createCell(2);
            addressCell.setCellValue(student.getAddress());
            addressCell.setCellStyle(cellStyles[2]);

            /**
             * 你可能有疑问,这里是日期类型,是不是要和上一次一样,设置单元格样式为日期类型?
             * 这回不用了,因为上面已经拷贝了模板的样式,生日一栏就是按日期类型展示的
             */
            XSSFCell birthdayCell = row.createCell(3);
            birthdayCell.setCellValue(student.getBirthday());
            birthdayCell.setCellStyle(cellStyles[3]);

            XSSFCell heightCell = row.createCell(4);
            heightCell.setCellValue(student.getHeight());
            heightCell.setCellStyle(cellStyles[4]);

            XSSFCell mainLandChinaCell = row.createCell(5);
            mainLandChinaCell.setCellValue(student.getIsMainlandChina());
            mainLandChinaCell.setCellStyle(cellStyles[5]);
        }

        /**
         * 之前通过本地文件流输出到桌面:
         * FileOutputStream out = new FileOutputStream("/Users/kevin/Documents/study/student_info_export.xlsx");
         * 现在用网络流:response.getOutputStream()
         * 注意,response的响应流没必要手动关闭,交给Tomcat关闭
         */
        String fileName = new String("学生信息表.xlsx".getBytes("UTF-8"), "ISO-8859-1");
        response.setContentType("application/octet-stream");
        response.setHeader("content-disposition", "attachment;filename=" + fileName);
        response.setHeader("filename", fileName);
        workbook.write(response.getOutputStream());
        workbook.close();
        logger.info("导出学生信息表成功!");
    }

    @PostMapping("/importExcel")
    public Map importExcel(MultipartFile file) throws Exception {
        // 直接获取上传的文件流,传入构造函数
        XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream());
        // 获取工作表。一个工作薄中可能有多个工作表,比如sheet1 sheet2,可以根据下标,也可以根据sheet名称。这里根据下标即可。
        XSSFSheet sheet = workbook.getSheetAt(0);

        // 收集每一行数据(跳过标题和表头,所以int i = 2)
        int lastRowNum = sheet.getLastRowNum();
        List<Student> studentList = new ArrayList<>();
        for (int i = 2; i <= lastRowNum; i++) {
            // 收集当前行所有单元格的数据
            XSSFRow row = sheet.getRow(i);
            short lastCellNum = row.getLastCellNum();
            List<String> cellDataList = new ArrayList<>();
            for (int j = 0; j < lastCellNum; j++) {
                cellDataList.add(getValue(row.getCell(j)));
            }

            // 把当前行数据设置到POJO。由于Excel单元格的顺序和POJO字段顺序一致,也就是数据类型一致,所以可以直接强转
            Student student = new Student();
            student.setName(cellDataList.get(0));
            student.setAge(Integer.parseInt(cellDataList.get(1)));
            student.setAddress(cellDataList.get(2));
            // getValue()方法返回的是字符串类型的 1962-6-22 00:00:00,这里按"yyyy-MM-dd HH:mm:ss"重新解析为Date
            student.setBirthday(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(cellDataList.get(3)));
            student.setHeight(Double.parseDouble(cellDataList.get(4)));
            student.setHeight(Double.parseDouble(cellDataList.get(4)));
            student.setIsMainlandChina(Boolean.valueOf(cellDataList.get(5)));
            studentList.add(student);
        }

        // 插入数据库
        saveToDB(studentList);
        logger.info("导入{}成功!", file.getOriginalFilename());

        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", null);
        result.put("msg", "success");
        return result;
    }

    private void saveToDB(List<Student> studentList) {
        if (CollectionUtils.isEmpty(studentList)) {
            return;
        }
        // 直接打印,模拟插入数据库
        studentList.forEach(System.out::println);
    }

    /**
     * 提供POI数据类型 --> Java数据类型的转换
     * 由于本方法返回值设为String,所以不管转换后是什么Java类型,都要以String格式返回
     * 所以Date会被格式化为yyyy-MM-dd HH:mm:ss
     * 后面根据需要自己另外转换
     *
     * @param cell
     * @return
     */
    private String getValue(Cell cell) {
        if (cell == null) {
            return "";
        }

        switch (cell.getCellType()) {
            case STRING:
                return cell.getRichStringCellValue().getString().trim();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    // DateUtil是POI内部提供的日期工具类,可以把原本是日期类型的NUMERIC转为Java的Data类型
                    Date javaDate = DateUtil.getJavaDate(cell.getNumericCellValue());
                    String dateString = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(javaDate);
                    return dateString;
                } else {
                    /*
                     * 无论Excel中是58还是58.0,数值类型在POI中最终都被解读为Double。
                     * 这里的解决办法是通过BigDecimal先把Double先转成字符串,如果是.0结尾,把.0去掉
                     * */
                    String strCell = "";
                    Double num = cell.getNumericCellValue();
                    BigDecimal bd = new BigDecimal(num.toString());
                    if (bd != null) {
                        strCell = bd.toPlainString();
                    }
                    // 去除 浮点型 自动加的 .0
                    if (strCell.endsWith(".0")) {
                        strCell = strCell.substring(0, strCell.indexOf("."));
                    }
                    return strCell;
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            default:
                return "";
        }
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class Student {
        private Long id;
        private String name;
        private Integer age;
        private String address;
        private Date birthday;
        private Double height;
        private Boolean isMainlandChina;
    }

}

测试导出

测试导入

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣刷题day1(两数相加,回文数,罗马数转整数)

题目1&#xff1a;1.两数之和 思路1和解析&#xff1a; //1.暴力枚举解法(历遍两次数组&#xff0c;时间复杂度O&#xff08;N^2)&#xff0c;空间复杂度O&#xff08;1&#xff09; int* twoSum(int* nums, int numsSize, int target, int* returnSize) {for (int i 0; i &…

短波红外相机的原理及应用场景

短波红外 (简称SWIR&#xff0c;通常指0.9~1.7μm波长的光线) 是一种比可见光波长更长的光。这些光不能通过“肉眼”看到&#xff0c;也不能用“普通相机”检测到。由于被检测物体的材料特性&#xff0c;一些在可见光下无法看到的特性&#xff0c;却能在近红外光下呈现出来&…

使用Python Flask搭建Web问答应用程序并发布到公网远程访问

使用Python Flask搭建web问答应用程序框架&#xff0c;并发布到公网上访问 文章目录 使用Python Flask搭建web问答应用程序框架&#xff0c;并发布到公网上访问前言1. 安装部署Flask并制作SayHello问答界面2. 安装Cpolar内网穿透3. 配置Flask的问答界面公网访问地址4. 公网远程…

AI Agent 结构与分类

一、什么是AI agent 在人工智能中&#xff0c;智能代理AI Agent是以智能方式行事的代理&#xff1b;它感知环境&#xff0c;自主采取行动以实现目标&#xff0c;并可以通过学习或获取知识来提高其性能。人其实就是一种复杂代理。 为了理解智能代理的结构&#xff0c;我们应该熟…

go写文件后出现大量NUL字符问题记录

目录 背景 看看修改前 修改后 原因 背景 写文件完成后发现&#xff1a; size明显也和正常的不相等。 看看修改前 buf : make([]byte, 64) buffer : bytes.NewBuffer(buf)// ...其它逻辑使得buffer有值// 打开即将要写入的文件&#xff0c;不存在则创建 f, err : os.Open…

Ruby和HTTParty库下载代码示例

ruby require httparty require nokogiri # 设置服务器 proxy_host "" proxy_port "" # 定义URL url "" # 创建HTTParty对象&#xff0c;并设置服务器 httparty HTTParty.new( :proxy > "#{proxy_host}:#{proxy_port}" ) …

不止有console.log()可以打印日志

1.带错误的打印 //1.醒目的打印 2.方便筛选器筛选 console.log("正常打印") console.warn("警告打印") console.error("错误打印") console.info("信息打印") console.log("%c带样式的打印", "color: red; font-size…

Nginx(十二) gzip gzip_static sendfile directio aio 组合使用测试(2)

测试10&#xff1a;开启gzip、sendfile、aio、directio1m&#xff0c;关闭gzip_static&#xff0c;请求/index.js {"time_iso8601":"2023-11-30T17:20:5508:00","request_uri":"/index.js","status":"200","…

论文阅读[2022sigcomm]GSO-Simulcast Global Stream Orchestration in Simulcast Video

GSO-Simulcast Global Stream Orchestration in Simulcast Video 作者&#xff1a; 1 背景 1视频会议成为全球数十亿人远程协作、学习和个人互动的核心&#xff0c;这些不断增长的虚拟连接需求推动视频会议服务的蓬勃发展 2当前用户越来越希望在低延迟下看到更高质量的视频…

【C/PTA —— 14.结构体1(课外实践)】

C/PTA —— 14.结构体1&#xff08;课外实践&#xff09; 一.函数题6-1 选队长6-2 按等级统计学生成绩6-3 学生成绩比高低6-4 综合成绩6-5 利用“选择排序算法“对结构体数组进行排序6-6 结构体的最值6-7 复数相乘运算 二.编程题7-5 一帮一7-6 考试座位号 一.函数题 6-1 选队长…

【Selenium+Webmagic】基于JAVA语言实现爬取js渲染后的页面,附有代码

事先声明 笔者最近需要查看一些数据&#xff0c;自己挨个找太麻烦了&#xff0c;于是简单的学了一下爬虫。笔者在这里声明&#xff0c;爬的数据只为学术用&#xff0c;没有其他用途&#xff0c;希望来这篇文章学习的同学能抱有同样的目的。 枪本身不坏&#xff0c;坏的是使用枪…

20:kotlin 类和对象 --泛型(Generics)

类可以有类型参数 class Box<T>(t: T) {var value t }要创建类实例&#xff0c;需提供类型参数 val box: Box<Int> Box<Int>(1)如果类型可以被推断出来&#xff0c;可以省略 val box Box(1)通配符 在JAVA泛型中有通配符?、? extends E、? super E&…

peft / bitsandbytes包windows安装问题

peft / bitsandbytes包windows安装问题 环境版本安装peftCUDA Setup failed despite GPU being available报错信息解决方法 ImportError: cannot import name is_npu_available from accelerate.utils报错信息解决方法 AttributeError: NoneType object has no attribute cuDev…

linux部署前端静态页面(实战)

Linux基本命令&#xff08;学习笔记&#xff09;零基础入门linux系统运维_linux find exec rm_Z_Xshan的博客-CSDN博客 如果linux不熟可以看我之前写的入门教程 感谢支持&#xff01;&#xff01; 一、服务器 这里去购买云服务器&#xff0c;如果是练习可以用虚拟机&#xff…

内网穿透的应用-公网环境下移动端通过群晖管家+cpolar远程管理家中本地局域网内黑群晖设备

白嫖怪狂喜&#xff01;黑群晖也能使用群晖管家啦&#xff01; 文章目录 白嫖怪狂喜&#xff01;黑群晖也能使用群晖管家啦&#xff01;1.使用环境要求&#xff1a;2.下载安装群晖管家app3.随机地址登陆群晖管家app4.固定地址登陆群晖管家app 自己组装nas的白嫖怪们虽然也可以通…

软件设计中如何画各类图之五用例图(Use Case Diagram):系统功能需求与用户交互的图形化描述

目录 1 前言2 用例图基本介绍3 用例图的符号及说明3.1 用例&#xff08;Use Case&#xff09;3.2 参与者&#xff08;Actor&#xff09;3.2 关系&#xff08;Relationships&#xff09; 4 画用例图的步骤4.1 确定系统边界4.2 识别参与者4.3 定义用例4.4 绘制关系4.5 完善细节 5…

CopyOnWriteArrayList怎么用

什么是CopyOnWriteArrayListCopyOnWriteArrayList常用方法CopyOnWriteArrayList源码详解CopyOnWriteArrayList使用注意点CopyOnWriteArrayList存在的性能问题CopyOnWriteArrayList 使用实例基本应用实例并发应用实例 拓展写时复制 什么是CopyOnWriteArrayList CopyOnWriteArra…

2023经典软件测试面试题

1、问&#xff1a;你在测试中发现了一个bug&#xff0c;但是开发经理认为这不是一个bug&#xff0c;你应该怎样解决&#xff1f; 首先&#xff0c;将问题提交到缺陷管理库里面进行备案。 然后&#xff0c;要获取判断的依据和标准&#xff1a; 根据需求说明书、产品说明、设计…

2024清理软件排名第一的是CCleaner

CCleaner2024版是一款专业好用的系统优化和隐私保护工具。CCleaner官方版主要用来清除Windows系统不再使用的垃圾文件和使用者的上网记录以空出硬盘容量&#xff0c;按工具同时注重保护用户隐私&#xff0c;被誉为“世界上最受欢迎的PC清洁剂”。 CCleaner下载如下&#xff1a…

【23真题】押题卷的漏网之鱼!

今天分享的是23年中国计量大学805的信号与系统试题及解析。第二大题的第1小题这类题&#xff01;太经典了&#xff0c;他那个相位图像&#xff0c;怎么看都是24真题的样子图片。但是我出的话&#xff0c;会把幅频特性从三角变为矩形&#xff0c;再加上个信号是否无失真的判断。…