easyexcel的批量导入和导出

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

带参数的导入

前端

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;
	}else{//上传失败
		ElMessage.error('上传失败');
	}
	upload.value!.clearFiles();
};

const handleClose = (done: any) => {
	if (state.uploadLoading) {
		ElMessage.info('正在上传,请等待上传完成')
	} else {
		done()
	}
}

后端

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 Result 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);
        }
        return null;
    }

你可能感兴趣的:(前端,java,vue)