easyexcel的导入和导出

easyexcel的导入和导出的实际运用
前端 - vue3
后端 - springboot
数据库-oracle
tip:逐步完善中

2023.7.28 优化后端下载失败,返回JSON结果显示给前端

带参数的导入

前端

tip: button按钮的:loading属性仅是禁止了按钮绑定方法,搭配:disabled才能达到禁用上传按钮的目的

<el-dialog v-model="state.dialogVisible"  title="导入" :close-on-click-modal="false" :before-close="handleClose" >
		<el-upload
			:disabled="state.uploadLoading"
			ref="upload"
			accept=".xlsx,.xls"
			v-model:file-list="state.fileList"
			class="upload-demo"
			:limit="1"
			:auto-upload="false"
			:on-change="uploadFileChange"
		> 
			<el-button type="primary" :loading="state.uploadLoading">上传</el-button>
			<template #tip>
				<div class="el-upload__tip">
					tip:仅可以上传.xlsx、.xls类型的文件
				</div>
			</template>
		</el-upload>
</el-dialog>
const uploadFileChange = async(file: any) => {

	state.uploadLoading=true;//禁用上传按钮
	
	//此处可对上传的文件进行限制处理,比如文件大小、格式等
	//......

	// 上传文件
	let formData = new FormData();
	formData.append("file", file.raw);
	const data = {
		id:"123",
	}
	const strJSON = JSON.stringify(data) 
	let rsp: any = await sysApi.importExcel(formData);
	state.uploadLoading=false;//启用上传按钮

	if(rsp.RespCode=="00"){
		ElMessage.success('上传成功');
		state.dialogVisible=false;//关闭弹窗
		eventBus.$emit('refreshList')//调用父组件页面的列表刷新方法
	}else{//上传失败
		ElMessage.error('上传失败');
	}
	upload.value!.clearFiles();//清除文件列表
};
//关闭弹窗提示
const handleClose = (done: any) => {
	if (state.uploadLoading) {
		ElMessage.info('正在上传,请等待上传完成')
	} else {
		done()  //关闭 Dialog
	}
}

后端

controller层

 @ResponseBody
 @RequestMapping(value = "/importExcel",method = RequestMethod.POST)
 public Result importExcel(@RequestParam(value = "file", required = false) MultipartFile file,@RequestParam(value = "strJSON", required = false) String strJSON, HttpServletRequest request){
        try{
            Gson gson = new Gson();
            Map reqMap = gson.fromJson(strJSON, Map.class); //body参数
            reqMap.put("fileName",file.getOriginalFilename());

			//防止文件名出现重复导致后续删除数据出错
			//sql:select count(1) from table_name where FILE_NAME = #{fileName, jdbcType=VARCHAR}
            int row = userService.getExistByFileName(reqMap.get("fileName"));
            if(row > 0){
                return Result.TranError("文件名重复");
            }
            
            //文件输入流
            InputStream in=file.getInputStream();
            //调用方法进行读取
            //把service直接注入进来为了后面能使用
            //因为在listener中不能注入service所以在这个serviceiimpl中,通过listener使service注入进去,为了在listener中能够使用service中的发方法save/
            EasyExcel.read(in, ExcelUserEntity.class,new UploadUserDataListener(userService,reqMap.get("fileName").toString())).sheet().doRead();
  
            return Result.TranSuccess("插入成功!");
        }catch (Exception e){
            log.error("",e);
            if(e.getMessage().toString().indexOf("ORA-00001") != -1){
                return Result.TranError("存在重复数据!");
            }
        }
        return null;
    }

读的对象 :ExcelUserEntity.class

@Getter
@Setter
@EqualsAndHashCode
public class ExcelUserEntity {

    @ExcelProperty("用户id")
    private String UserId;
    @ExcelProperty("姓名")
    private String UserName;

    private String FileName;//文件名
}

监听器 :UploadUserDataListener.class

@Slf4j
public class UploadUserDataListener implements ReadListener<ExcelUserEntity> {

    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    /**
     * 缓存的数据
     */
    private List<ExcelUserEntity> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private UserService userService;
    // private UserMapper userMapper;

    private String fileName;

    // public UploadUserDataListener() {
    //     // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
    //     demoDAO = new DemoDAO();
    // }

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

    /**
     * 当解析抛出异常,会被此方法进行捕获
     * 抛出异常-停止解析
     * 不抛出-继续解析
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        log.info("解析出错:"+exception.getMessage());
        int row=0,column=0;
        if (exception instanceof ExcelDataConvertException){
            ExcelDataConvertException convertException=(ExcelDataConvertException) exception;

            row=convertException.getRowIndex();
            column=convertException.getColumnIndex();
            log.error("解析出错:{}行 {}列",row,column);
        }
        //捕获到异常,删除之前已经存储的数据,保证数据库的数据干净
        //sql:delete from table_name where FILE_NAME = #{fileName, jdbcType=VARCHAR}
        userService.deleteByFileName(fileName);
        //如果抛出该异常,controller层无法捕获
        // throw new ExcelAnalysisStopException("解析出错:"+row+"行 "+column+"列,停止运行");
        throw new RuntimeException(exception.getMessage());
    }

    /**
     * 解析表数据的表头,headMap 默认表格第一行数据
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        System.out.println("headMap:"+ headMap);
    }

    /**
     * 一条数据解析后的对象 data,可以在这个方法中进行再次处理
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(ExcelUserEntity data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        data.setFileName(fileName);
        cachedDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    /**
     * 解析完成后,会运行此方法
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        log.info("解析结束");
        if(cachedDataList.size()>0){
            saveData();
        }
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        userService.save(cachedDataList);
        log.info("存储数据库成功!");
    }

    /**
     * 校验是否继续解析,默认true
     * @param context
     * @return
     */
    @Override
    public boolean hasNext(AnalysisContext context){
        return true;
    }
}

导出

前端

const downloadUserExcel = async ()=>{
  let reqQuery = {
     ...
  }
  //发送请求
  let res: any = await sysApi.downloadUserExcel(reqQuery);

  // 特殊处理返回类型是blob,获取错误信息的情况
  if(res.status==200 ){
      if(res.data.size==0 || res.data.type.includes('json')){//json表示下载失败,后端返回了错误详情的json格式
          let data=res.data
          let fileReader=new FileReader()
          fileReader.readAsText(data)
          fileReader.onload=function(result){
            let jsondata=JSON.parse(result.target.result)
            ElMessage.error(jsondata.RespMsg);
          }
          return;
      }
  }

  // 处理响应
  // 请求参数记得加'【responseType: 'blob'】',否则容易乱码
  let blob = new Blob([res.data], { type: 'application/vnd.ms-excel' });
  let url = URL.createObjectURL(blob);
	 
  let temp = res.headers['content-disposition']
    .split(';')[1]
    .split('filename=')[1] //从返回头取出文件名称
  let fileNameStr = decodeURI(temp) //中文文件名需解码
  let fileName = fileNameStr.replace(/\"/g, '').replace(/\'/g, '')
  const link = document.createElement('a'); //创建a标签
  link.href = url;
  link.download = fileName; //此处可以重命名文件
  link.click();
  //释放内存
  URL.revokeObjectURL(url);
  if (document.body.contains(link)) {
    document.body.removeChild(link)
  }
}

后端

 	@ResponseBody
    @RequestMapping(value = "/downloadUserExcel",method = RequestMethod.POST)
    public void downloadUserExcel(@RequestBody String reqMsg , HttpServletRequest request , HttpServletResponse response){
        try{ 
            Gson gson = new Gson();
            Map reqMap = gson.fromJson(reqMsg,Map.class); 

            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("用户信息表", "UTF-8").replaceAll("\\+", "%20");

            //建议加上该段,否则可能会出现前端无法获取Content-disposition
            response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");

            //getData(reqMap)   返回的数据必须是ExcelUserEntity.class的集合,并且无特殊配置每个字段必须有值不可为null,否则无法生成
            EasyExcel.write(response.getOutputStream(), ExcelUserEntity.class).sheet("用户信息表").doWrite(getData(reqMap));
        }catch (Exception e){
            log.error("",e);
            e.printStackTrace();
	        // 重置response
	        response.reset();
	        //解决跨域问题
	        response.setContentType("application/json;charset=utf-8");
	        response.setHeader("Access-Control-Allow-Credentials", "true");
	        response.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
	        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
	        //向前端写 错误信息 JSON格式
	        response.getWriter().write(JsonUtils.toJson(R.error("下载异常")));
        }
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于EasyExcel库,我们可以使用它来实现数据的导入导出。下面是一个示例代码,演示了如何使用EasyExcel进行数据的导入导出: ```java import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.metadata.Table; import com.alibaba.excel.support.ExcelTypeEnum; import java.util.ArrayList; import java.util.List; public class EasyExcelDemo { public static void main(String[] args) { // 导出数据 exportData(); // 导入数据 importData(); } private static void exportData() { // 准备数据 List<User> userList = new ArrayList<>(); userList.add(new User("张三", 20)); userList.add(new User("李四", 25)); // 导出文件路径 String exportFilePath = "D:/users.xlsx"; // 设置表头 Table table = new Table(0); table.setHead(User.getHead()); // 写入数据到Excel文件 ExcelWriter excelWriter = EasyExcel.write(exportFilePath, User.class).excelType(ExcelTypeEnum.XLSX).build(); Sheet sheet = new Sheet(1, 0, User.class); sheet.setSheetName("用户信息"); excelWriter.write(userList, sheet, table); excelWriter.finish(); } private static void importData() { // 导入文件路径 String importFilePath = "D:/users.xlsx"; // 读取Excel文件并转换为对象列表 List<User> userList = EasyExcel.read(importFilePath).head(User.class).sheet().doReadSync(); // 输出导入的数据 for (User user : userList) { System.out.println(user); } } static class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } public static List<String> getHead() { List<String> head = new ArrayList<>(); head.add("姓名"); head.add("年龄"); return head; } // 省略 getter 和 setter 方法 @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } } } ``` 上述示例代码中,我们先定义了一个 `User` 类作为数据的实体类,然后使用EasyExcel库进行数据的导出导入。在导出数据的部分,我们首先准备了一个用户列表 `userList`,然后指定导出的文件路径 `exportFilePath`,接着设置表头信息,最后使用 `ExcelWriter` 将数据写入到Excel文件中。在导入数据的部分,我们指定导入的文件路径 `importFilePath`,然后使用 `EasyExcel.read()` 方法读取Excel文件,并使用 `doReadSync()` 方法将Excel数据转换为对象列表。 请注意,上述示例代码中使用的是EasyExcel的最新版本,你需要在你的项目中添加相应的依赖。你可以通过访问EasyExcel的官方网站来获取更多关于EasyExcel库的信息和文档:[https://www.yuque.com/easyexcel/doc/easyexcel](https://www.yuque.com/easyexcel/doc/easyexcel)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值