使用Layui、Axios、Springboot(Java) 实现EasyExcel的导入导出(浏览器下载)
阅读原文时间:2022年05月14日阅读:1

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

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

使用到的技术: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>

对象

@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 下面有几个注意点需要注意

前端

<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