SpringBoot+EasyExcel+Vue3实现前后端导出导入Excel,解决Vue Excel损坏打不开【前后端完整代码】。

后端

导入

Controller

@PostMapping("/upload")
    public Result upload(MultipartFile file) throws IOException {
        return userService.upLoad(file);
    }

Service

EasyExcel的DataListener

package com.erju.excel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.erju.dao.UserMapper;
import com.erju.pojo.User;
import com.erju.utils.JasyptUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * 有个很重要的点 DataListener 不能被spring管理,要每次读取excel都要new,
 * 然后里面用到spring可以构造方法传进去
 *
 * @author sunyuan
 * @date 2022/3/22 18:09
 */

public class DataListener extends AnalysisEventListener<User> {
    private static final Logger LOGGER = LoggerFactory.getLogger(DataListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<User> list = new ArrayList<User>();

    private UserMapper userMapper;

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param userMapper
     */
    public DataListener(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param user
     * @param context
     */
    @Override
    public void invoke(User user, AnalysisContext context) {
        String password = JasyptUtil.setPass(user.getUsername());
        //默认密码是用户名
        user.setPassword(password);
        //默认角色是学生
        user.setPermissions("student");
        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(user));
        list.add(user);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            System.out.println(list);
            // 存储完成清理 list
            list.clear();
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        LOGGER.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOGGER.info("{}条数据,开始存储数据库!", list.size());
        userMapper.batchInsert(list);
        LOGGER.info("存储数据库成功!");
    }
}

接口

Result<?> upLoad(MultipartFile file) throws IOException;

实现类

 @Override
    public Result upLoad(MultipartFile file) throws IOException {
        try {
            EasyExcel.read(file.getInputStream(), User.class, new DataListener(userMapper))
                    .sheet()
                    .doRead();
            return Result.success("导入成功");
        } catch (IOException e) {
            e.printStackTrace();
            return Result.error(e.getMessage());
        }
    }

Dao

 void batchInsert(@Param("list") List<User> list);

批量插入数据库提高效率

<insert id="batchInsert">
        insert into `sl_user`
        (
        username, password, user_type, use_flag, phone_number,permissions
        )
        values
        <foreach collection="list" item="user" separator=",">
            (#{user.username}, #{user.password}, #{user.user_type}, #{user.use_flag},
            #{user.phone_number},#{user.permissions})
        </foreach>
    </insert>

导出

controller

 @GetMapping("/downloadFailedUsingJson")
    public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
        userService.downloadFailedUsingJson(response);
    }

service

void downloadFailedUsingJson(HttpServletResponse response) throws IOException;

实现类

public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
        // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
        try {
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("测试", "UTF-8");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
            //数据
            List<User> list = userMapper.selectAllStudent();
            // 这里需要设置不关闭流 数据在response.OutPutStream里面  前端需要res.data
            EasyExcel.write(response.getOutputStream(), User.class).autoCloseStream(Boolean.FALSE).sheet("模板")
                    .doWrite(list);
//            return Result.success("导出成功");
        } catch (Exception e) {
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = new HashMap<String, String>();
            map.put("status", "failure");
            map.put("message", "下载文件失败" + e.getMessage());
            response.getWriter().println(JSON.toJSONString(map));
//            return Result.error(e.getMessage());
        }

    }

dao

List<User> selectAllStudent();
<select id="selectAllStudent" resultType="com.erju.pojo.User">
        select username, phone_number
        from slab.sl_user
        where user_type = 2;
    </select>

前端

我也是前端小白,参考了很多大佬的博客才勉强实现了功能,前端代码逻辑较乱,仅供参考。

我封装的axios,在导出的时候用到了request的get方法,设置了一下responseType=blob 不然导出的excel无法打开

export const request = (method, url, headers = 'application/json;charset=UTF-8', responseType = null) => {
    if (method == "post") {
        return axios({
            method: 'post',
            url: "http://localhost:8888/" + url,
            // url: url,
            data: data,
            headers: {
                'Content-Type': headers,
                'Authorization': localStorage.getItem("token")
            },
            responseType: responseType // 相应类型如果有的话,就用接口中的,如果没有就默认为null
        });
    } else if (method == "get") {
        return axios({
            method: 'get',
            url: "http://localhost:8888/" + url,
            // url: url,
            headers: {
                'Content-Type': headers,
                'Authorization': localStorage.getItem("token")
            },
            responseType: responseType // 相应类型如果有的话,就用接口中的,如果没有就默认为null
        });
    } else {
        return;
    }
}

导入

导入的时候有个问题是 upload的action没有办法在请求的时候带上token

所以在

:headers=“myHeaders” 里带上了请求的token

const myHeaders = ref({
Authorization: localStorage.getItem(“token”)
})

<el-col :span="1">
                <el-upload
                    action="http://localhost:8888/user/upload"
                    :on-success="handleImportSuccess"
                    :on-error="handleImportError"
                    :headers="myHeaders"
                >
                    <el-button type="primary">导入</el-button>
                </el-upload>
            </el-col>
/**
 * excel 导入
 */
const handleImportSuccess = () => {
    ElMessage.success('导入成功')
    getList();
}
const handleImportError = () => {
    ElMessage.error('导入失败')
    getList();
}
const myHeaders = ref({
    Authorization: localStorage.getItem("token")
})


导出

//导入封装的axios
import {request} from "../../api/index"
<el-col :span="1">
                <el-button type="primary"
                           plain
                           :icon="Plus"
                           @click="expExcel">导出
                </el-button>
</el-col>
const expExcel = () => {
    request("get", `user/downloadFailedUsingJson`, 'application/json; charset=UTF-8', 'blob').then(res => {
        let blob = new Blob([res.data], {type: 'application/vnd.ms-excel'});
        let url = URL.createObjectURL(blob);
        const link = document.createElement('a'); //创建a标签
        link.href = url;
        link.download = '导出学生信息.xlsx'; //重命名文件
        link.click();
        URL.revokeObjectURL(url);
        console.log("下载文件" + res);
    })
}

关于vue导出的excel无法打开的情况

1.postman请求后端接口导出的excel可以打开的话,说明后端没有问题。看前端就可以了

2.前端无法导出的原因大概就是因为后端响应回来的不是二进制流文件,所以在请求的时候就要带上 responseType=‘blob’ 或responseType = ‘arraybuffer’。让响应的时候就是blob

在这里插入图片描述

如果是这样导出的格式还是乱的话,说明你前端处理下载的时候有问题了,我的问题是直接处理res了,导致一直格式不对打不开,困扰了一天。
在这里插入图片描述

后来才知道数据不是直接处理res,数据在res.data里
在这里插入图片描述

最后这样就可以了。完美解决问题!完后收工!

最最可能出现问题的原因就是因为 responseType了,有很多前端开源框架

在这里插入图片描述

直接在axios的create的方法里写死了,所以导致excel格式错误打不开!

困扰一天的问题终于解决了!

参考大佬们的博客还有一个人是说可能是因为,我的不是因为这个原因!

在这里插入图片描述

如果还是解决不了的可以试一试 原文:

axios设置了responseType之后仍然接收不到正确的Blob对象_mob604756fab9f3的技术博客_51CTO博客

最最最后前端还是处理不好的话,可以把文件临时写到后端服务器的一个temp目录下,直接给前端文件地址,让前端简单粗暴

window.open可以试一试这个方法!

参考(感谢你们分享宝贵的经验):

最后的灵感是参考这个博客

vue 通过blob下载流文件 - 简书 (jianshu.com)

vue导出excel表格-后端返回blob流文件,前端接收并导出下载(处理导出以后打开文件损坏问题) - 掘金 (juejin.cn)

vue element-ui upload 实现带token上传_小仙女de成长的博客-CSDN博客

整理不宜,感谢支持!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐