• 使用Layui、Axios、Springboot(Java) 实现EasyExcel的导入导出(浏览器下载)


    实现EasyExcel的导入导出(浏览器下载)

    实现三个按钮的功能,但是却花费了一天的时间包括总结。

    image-20210128215145536

    使用到的技术:springboot layui axios EasyExcel mybatis-plus

    上传模板

    不需要用到后端的交互,只需要前段<a>即可

    <a href="../static/excel/用户信息上传模板.xlsx" style="color:white">上传模版</a>
    

    参考链接:

    Excel如何对某一列设置下拉选择项 https://jingyan.baidu.com/article/f7ff0bfccae0e62e26bb1380.html

    下载上传模板 https://www.bilibili.com/video/BV1dQ4y1A75e?from=search&seid=16041556233389040505

    导入数据

    使用到EasyExcel的读Excel

    后端

    引入pom.xml 依赖

    		<dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>2.2.7</version>
            </dependency>
    

    对象

    image-20210128220945557

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class UserDataExcel {
    
        @ExcelProperty("用户名")
        private String userName;
        @ExcelProperty("密码")
        private String password;
        @ExcelProperty("姓名")
        private String name;
        @ExcelProperty("联系方式")
        private String phone;
        @ExcelProperty("用户类型")
        private String typeStr;//取得的名字和User不一样,否则BeanUtils.copyProperties报错
        @ExcelProperty("备注")
        private String remark;
    }
    

    监听器

    public class UserDataExcelListener extends AnalysisEventListener<UserDataExcel> {
       /**
         * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
         */
        private static final int BATCH_COUNT = 5;
        List<User> list = new ArrayList<>();
        /**
         * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
         */
    
        private UserService userService;
    
        public UserDataExcelListener() {
    
        }
        /**
         * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
         */
        public UserDataExcelListener(UserService userService ) {
            this.userService = userService;
        }
        /**
         * 这个每一条数据解析都会来调用
         *
         * @param data
         *            one row value. Is is same as {@link AnalysisContext#readRowHolder()}
         * @param context
         */
        //一行行读取
        @Override
        public void invoke(UserDataExcel data, AnalysisContext context) {
            if(data==null){
                throw new DormitoryException(ResultCode.ERROR,"文件数据为空");
            }
            //进行数据的转换 第一个参数复制到第二个参数中
            User user = new User();
            BeanUtils.copyProperties(data,user);
            if(!StringUtils.isEmpty(data.getTypeStr())){
                switch (data.getTypeStr()){
                    case "管理员":
                        user.setType(0);
                        break;
                    case "宿管员":
                        user.setType(1);
                        break;
                    case "学生":
                        user.setType(2);
                        break;
                }
            }
            System.out.println("将excel的data复制到user,user:"+user);
            list.add(user);
            // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
            if (list.size() >= BATCH_COUNT) {
                saveData();
                // 存储完成清理 list
                list.clear();
            }
        }
        /**
         * 所有数据解析完成了 都会来调用
         *
         * @param context
         */
        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            // 这里也要保存数据,确保最后遗留的数据也存储到数据库
            saveData();
        }
        /**
         * 加上存储数据库 在监听器中直接添加
         */
        private void saveData() {
    
            userService.saveBatch(list);
    
        }
    }
    

    UserController 控制层

    @PostMapping("addUsers")
    public Result addUsers(@RequestParam("uploadFile") MultipartFile file) throws IOException {
        if(file==null){
            System.out.println("文件为空");
        }
        boolean save = userService.addUsers(file);
        return  save ? Result.ok().mesaage("新增用户成功"):Result.error().mesaage("新增用户失败");
    }
    

    UserServiceImpl 业务实现层

    需要注意的点,因为监听器没有Spring进行管理,所以这边的处理方式是将UserService 传入进去

    @Override
    public boolean addUsers(MultipartFile file)  {
        try{
            InputStream in = file.getInputStream();
            EasyExcel.read(in, UserDataExcel.class, new UserDataExcelListener(this)).sheet().doRead();
        }catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }
    

    经过上面的步骤后,我们就可以先在apipost中测试接口

    apipost测试MultipartFile 下面有几个注意点需要注意
    image-20210128222642455

    前端

    <button class="layui-btn  layui-btn-sm " lay-event="importData" style="background: #2ecc71" > 导入数据 </button>
    

    使用到了layui的上传组件,由于使用到axios,也简单地修改了一下upload的源码中的axios

    upload.render({
        elem:'#importExcel',
        url:'/user/addUsers',
        size : '5000',//文件最大可允许上传的大小,单位 KB
        accept:'file',//允许上传文件
        exts:'xls|xlsx|xlsm|xlt|xltx|xltm',
        field:'uploadFile',
        headers: {token: store.getToken()},
        done:function (result) {//执行上传请求后的回调。返回三个参数,分别为:res(服务端响应信息)、index(当前文件的索引)、upload(重新上传的方法,一般在文件上传失败后使用)
            console.log(result);
            if(result.code==0){
                layer.msg("Excel导入数据成功",{
    
                },function () {
                    table.reload("user-table-id");
                })
            }else{
                layer.msg("Excel导入数据失败",function () {
    
                })
            }
        }
    });
    

    小插曲的Bug,写在下面这篇博客中。

    layui在toolbar使用上传控件在reload后失效的问题解决

    参考链接 :

    java+layui实现Excel的导入导出 https://www.cnblogs.com/bbllw/p/10800161.html

    EasyExcel 文档 https://www.yuque.com/easyexcel/doc/easyexcel

    课程上传的例子 https://www.bilibili.com/video/BV1dQ4y1A75e?from=search&seid=16041556233389040505

    layui的upload文档说明 https://www.layui.com/doc/modules/upload.html

    导出全部

    使用到EasyExcel的写Excel,这一点也是最难的一点,主要是卡在文件在浏览器中下载

    如果直接使用EasyExcel中简单写的例子,只能将xlsx文件下载到后端的项目文件中,不能在浏览器中下载。

    后端

    	@GetMapping("exportAll")
        public void exportAll(HttpServletResponse response) throws IOException {
            List<UserDataExcel> listExcel = new ArrayList<>();
            List<User> list = userService.list();
            for(int i = 0;i<list.size();i++){
                UserDataExcel dataExcel = new UserDataExcel();
                BeanUtils.copyProperties(list.get(i),dataExcel);//小技巧
                if(list.get(i).getType()!=null){//处理数据库中存放int 不是String
                    switch (list.get(i).getType()){
                        case 0:
                            dataExcel.setTypeStr("管理员");
                            break;
                        case 1:
                            dataExcel.setTypeStr("宿管员");
                            break;
                        case 2:
                            dataExcel.setTypeStr("学生");
                            break;
                    }
                }
                listExcel.add(dataExcel);
            }
            //文件名需要这样写,不能在setHeader直接写中文名,否则下载的文件名字为空,只有后缀
            String fileName = new String("用户信息.xlsx".getBytes(), StandardCharsets.ISO_8859_1);
            response.setContentType("application/msexcel");
            response.setCharacterEncoding("utf8");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName );
            EasyExcel.write(response.getOutputStream(), UserDataExcel.class)
                    .sheet("sheet")
                    .doWrite(listExcel);
    //        return Result.ok().mesaage("下载成功"); 不要写
        }
    

    如果加了return Result.ok().mesaage("下载成功");后端会报错,但是还是可以正常下载。

    org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class com.dj.dormitory.commonutils.Result] with preset Content-Type 'application/msexcel;charset=utf8'
    

    前端

    <button class="layui-btn  layui-btn-sm " lay-event="exportData" style="background: #27ae60"> 导出全部 </button>
    

    刚开始的时候,自己想直接使用windows.open('/接口地址'),但是因为项目中用到token,所以就放弃了这个方法。使用了下面的方法

    layer.confirm('确定导出所有用户信息吗?', {
        btn: ['确定', '取消']
    }, function(index){
        //window.open("http://localhost:8888/dormitory/user/exportAll");
        axios({
            method: 'get',
            url:'/user/exportAll',
            responseType: 'blob',   // 重要, 限制返回的数据结构为blob格式,方便前端做转换
        }).then(data=>{
            const link = document.createElement('a')
            const blob = new Blob([data], { type: 'application/vnd.ms-excel' })
            link.style.display = 'none'
            link.href = URL.createObjectURL(blob)
            link.setAttribute('download','用户信息.xlsx')
            document.body.appendChild(link)
            link.click()
            document.body.removeChild(link)
        })
        layer.close(index);
    }, function(index){
        //按钮【按钮二】的回调
    });
    
    

    参考链接:

    学到了 web中的代码具体应该怎么写

    EasyExcel web中的写 https://www.yuque.com/easyexcel/doc/write#afb7324a

    了解到window.open("/download");,可以在浏览器中实现成功下载

    EasyExcel实现下载Excel(解决无法从浏览器下载问题)https://blog.csdn.net/ruanbigshuai/article/details/108554896

    了解到window.open("/download");最简单,但是不可以携带token

    axios实现excel文件下载 https://blog.csdn.net/xuesheng1610748/article/details/83865679

    成功帮助到自己的方法三,使用token并且在浏览器中实现成功下载

    Vue项目利用axios请求接口下载excel(附前后端代码) https://blog.csdn.net/asmallprogrammer/article/details/91440793

  • 相关阅读:
    hdu3874
    spoj D-query
    hdu4348
    hdu4417
    hdu2665
    [LUOGU] P1057 传球游戏
    [CODEVS] 2193 数字三角形WW
    [CODEVS] 2189 数字三角形W
    [模板] 线段树
    [模板] 树状数组
  • 原文地址:https://www.cnblogs.com/10134dz/p/14342503.html
Copyright © 2020-2023  润新知