解决java POI导入Excel超时问题

摘要:
由于要导入大量数据,后台会耗费很长时间,导致超时。后,此时导入按钮处于禁用状态并且处于加载状态。通过window.setInterval定时轮询,每隔30秒去后台查询ifFinished值,如果为字符串“1”表示上传完毕,此时在弹框“上传完毕!”,此时导入按钮禁用状态和加载状态都取消,并且刷新列表。前端代码:下载模板导入导入导出handleUploadChange{if{this.$message.error('上传文件只能是xls、xlsx格式!如果你只想重复执行可以使用setInterval()方法。setTimeout()只执行一次,而setInterval可以多次调用。

由于要导入大量数据,后台会耗费很长时间,导致超时。

本项目前端request.js中设定的超时时间为150s.

const service =axios.create({
    baseURL: baseUrl,
    withCredentials: true,
    timeout: 150000});

我们的做法是:

前端导入Excel向后台发出请求时,后台立即返回信息“后台正在导入,请稍等!”,向redis中存入键isFinished的值为字符串“0”,并且开启一个线程来完成插入大量数据到数据库的工作,当插入完成则将redis中isFinished的值改为字符串“1”。前端收到“后台正在输入,请稍等!”后,此时导入按钮处于禁用状态并且处于加载状态。通过window.setInterval定时轮询,每隔30秒去后台查询ifFinished值,如果为字符串“1”表示上传完毕,此时在弹框“上传完毕!”,此时导入按钮禁用状态和加载状态都取消,并且刷新列表。

前端代码:

<div style="margin-bottom: 10px">
        <el-form :inline="true">
          <el-form-item label="">
            <el-button type="success" icon="el-icon-download" @click="downloadTemplate">下载模板</el-button>
            <el-button :loading="importLoading" type="primary" icon="el-icon-upload" @click="handleImport">导入
            </el-button>
            <el-upload ref="importUpload" :auto-upload="false" :show-file-list="false" :on-change="handleUploadChange"
              :disabled="importDisabled"   action="#" class="upload-demo">
              <el-button     slot="trigger" :loading="importLoading" size="small"
                type="primary" icon="el-icon-upload">导入</el-button>
            </el-upload>
          </el-form-item>
          <el-form-item label="">
            <el-button type="warning" icon="el-icon-lightning" @click="exportExcel">导出</el-button>
          </el-form-item>
        </el-form>
      </div>
handleUploadChange(file) {
if (file.name.lastIndexOf('.') < 0) {
this.$message.error('上传文件只能是xls、xlsx格式!')
return
}
const testMsg = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase()
const extensionXLS = testMsg == 'xls'
const extensionXLSX = testMsg == 'xlsx'
if (!extensionXLS && !extensionXLSX) {
this.$message.error('上传文件只能是xls、xlsx格式!')
return
}
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error('上传文件不能超过 2MB!')
return
}
this.importLoading = true
this.importDisabled = true
const data = new FormData()
data.append('file', file.raw)
medicineListApi.importExcel(data).then(response => {
if (response.status == true) {
this.open2(response.msg)
this.getList()
} else {
this.open2(response.msg)
this.importLoading = false
this.importDisabled = false
}
window.setInterval(() => {
setTimeout(this.getStatus(), 0)
}, 30*1000)
}).catch(() => {
this.open2('抱歉,导入失败')
this.importLoading = false
this.importDisabled = false
})
},
open2(str) {
this.$notify({
title: '提示',
message: str,
duration: 0
})
},
// 请求后台获取是否导入完成的状态
getStatus(){
medicineListApi.getStatus().then(response => {
if (response.data == "1") {
this.open2("上传完毕!")
this.importLoading = false
this.importDisabled = false
this.getList()
}
})
}

项目中我们经常需要实现轮询-每隔几秒请求一次接口刷新数据,一般都会使用setInterval,但要注意单纯使用它会导致页面卡死,解释:setInterval不会清除定时器队列,每重复执行1次都会导致定时器叠加,最终卡死你的网页。但是setTimeout是自带清除定时器的

解决办法:

window.setInterval(() =>{
  setTimeout(fun, 0)
}, 30000)

setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。如果你只想重复执行可以使用setInterval()方法。setTimeout()只执行一次,而setInterval可以多次调用。

medicineList.js代码:

import request from "./request";

const baseUrl = "/medicineList"export const medicineListApi ={
  /**
   * 导入
   * @param data
   */importExcel(data) {
    returnrequest({
      url: baseUrl + '/import',
      method: 'post',
      data: data,
      headers: {
        'Content-Type': 'multipart/form-data'}
    })
  },
  getStatus() {
    returnrequest({
      url: baseUrl + "/getStatus",
      method: "GET"});
  },
};

后台代码:

controller:

@RestController
@RequestMapping("/medicineList")
@Slf4j
public classMedicineListController extends BaseController {

    @Autowired
    privateMedicineListService medicineListService;

    /**
     * 导入药品信息
     *
     * @param file
     * @return
     * @throws Exception
     */@PostMapping("/import")
    publicJSONObject importNodeInfo(MultipartFile file) throws Exception {
        returnmedicineListService.importNodeInfo(file.getInputStream());
    }

    @GetMapping("/getStatus")
    publicJSONObject getStatus() {
        returnmedicineListService.getStatus();
    }
}

service接口:

public interface MedicineListService extends IService<DrugData>{
    JSONObject importNodeInfo(InputStream inputStream) throws Exception;

    JSONObject getStatus();
}

service实现类:

@Service
public class MedicineListServiceImpl extends ServiceImpl<MedicineListMapper,DrugData>implements MedicineListService {

    @Resource
    privateMedicineListMapper medicineListMapper;

    private static Logger logger = LoggerFactory.getLogger(MedicineListService.class);
    @Autowired
    private StringRedisTemplate redisTemplate;

    public JSONObject importNodeInfo(InputStream in) throws Exception {
        redisTemplate.opsForValue().set("isFinished","0",60*60l,TimeUnit.SECONDS);
        JSONObject json = new JSONObject();
        json.put("msg", "后台正在导入,请稍等!");
        json.put("status", true);
        newThread() {
            @Override
            public voidrun() {
                try{
                    //根据类型进行分流导入
                    String str0 = "";
                    String str = "";
                    String fstr = "";
                    int operCount = 0;
                    XSSFWorkbook workbook = new XSSFWorkbook(in);
                    XSSFSheet sheet = workbook.getSheetAt(0);
                    int totalColumnNum = sheet.getRow(0).getLastCellNum();
                    logger.info("导入代码信息excel文件的总列数:" +totalColumnNum);
                    System.out.println(totalColumnNum);
                    int lastRowNum =sheet.getLastRowNum();

                    logger.info("导入节点信息excel文件的总行数:" +lastRowNum);
                    System.out.println(sheet.getLastRowNum());
                    for (int num = 0; num <= lastRowNum; num++) {
                        XSSFRow row =sheet.getRow(num);
                        if(row == null) {
                            str0 = "存在空数据行,行号:" + (num + 1) + ",导入失败!";
                            break;
                        }
                        int hcount = num + 1;
                        if (num == 0) {
                            if (null != String.valueOf(row.getCell(0)) && String.valueOf(row.getCell(0)).equals("药品编码")) {
                                continue;
                            } else{

                                json.put("msg", "导入的模板名称出错,请确认");
                                json.put("status", false);
                                json.put("data", operCount);

                            }
                        }
                        DrugData drugData = newDrugData();
                        String drugNo = String.valueOf(row.getCell(0));
                        if(StringUtils.isNotBlank(drugNo) && !"null".equalsIgnoreCase(drugNo)) {
                            drugData.setDrugno(drugNo);// 药品编码
                        }
                        String drugName = String.valueOf(row.getCell(1));
                        if(StringUtils.isNotBlank(drugName) && !"null".equalsIgnoreCase(drugName)) {
                            drugData.setDrugname(drugName);//药品名称
                        }
                        String indiction = String.valueOf(row.getCell(2));
                        if(StringUtils.isNotBlank(indiction) && !"null".equalsIgnoreCase(indiction)) {
                            drugData.setIndiction(indiction); //适应症
                        }

                        try{
                            QueryWrapper<DrugData> wrapper = new QueryWrapper<>();
                            if(StringUtils.isNotBlank(drugData.getDrugno())){
                                wrapper.eq("drugno",drugData.getDrugno());
                            }
                            List<DrugData> drugDataList =medicineListMapper.selectList(wrapper);

                            if (null != drugDataList && drugDataList.size() > 0) {
                                drugData.setId(drugDataList.get(0).getId());
                                medicineListMapper.updateById(drugData);
                            } else{
                                medicineListMapper.insert(drugData);
                            }
                        } catch(Exception e) {
                            logger.error(e.getMessage());
                            str = str + "第【" + hcount + "】行,";
                            continue;
                        }
                        operCount++;
                    }
                    if(StringUtils.isNotBlank(str)) {
                        str = "其中-->" + str + "导入失败!";
                    }

                    if(StringUtils.isNotBlank(fstr)) {
                        fstr = "==投量-->" + fstr + "附表导入失败!";
                    }
                    redisTemplate.opsForValue().set("isFinished","1");

                    json.put("msg", "操作成功" + str0 + str +fstr);
                    json.put("status", true);
                    json.put("data", operCount);

                } catch(Exception e) {
                    logger.error(e.getMessage());
                } finally{
                    try{
                        in.close();
                    } catch(IOException e) {
                        logger.error(e.getMessage());
                    }
                }
            }
        }.start();
        returnjson;
    }
    @Override
    publicJSONObject getStatus() {
        String isFinished = redisTemplate.opsForValue().get("isFinished");
        JSONObject result = newJSONObject();
        if(StringUtils.isNotBlank(isFinished)) {
            result.put("msg", "获取成功");
            result.put("status", true);
            result.put("data",isFinished);
        } else{
            result.put("msg", "获取失败");
            result.put("status", false);
        }
        redisTemplate.delete("isFinished");
        returnresult;
    }
}

注意:导入Excel时,String.valueOf(row.getCell(0))获取的值出了要进行非空判断外,还要判断不为字符串null,再执行插入,否则Excel中未填写的单元格插入数据库会变成字符串null。

免责声明:文章转载自《解决java POI导入Excel超时问题》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇QTabWidgetArcGIS中标注(label)的使用技巧下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

element table表格中的数据修改弹出框,点击确定更新后table修改的数据高度位置不变

scrollChildren (data, acId) { if (data && data.length > 0) { for (let i = 0; i < data.length; i++) { if (data[i].id === acId) {...

ES:在线迁移集群索引,数据不丢失

一、背景 生产环境由于某些原因需要跨机房迁移ES集群,或者同机房原有集群中所有节点全部更换,期间ES索引要求完整,客户端请求中断不超过五分钟。 二、应用场景 1、同机房不同集群之间数据迁移; 2、跨机房不同集群之间数据迁移。 三、迁移方案A机房ES集群 --> B机房ES集群 1、迁移任务:假设A机房ES集群(3master、3data共三台物理...

Django项目:CMDB(服务器硬件资产自动采集系统)--11--07CMDB文件模式测试采集硬件数据

1 #settings.py 2 # ————————01CMDB获取服务器基本信息———————— 3 import os 4 5 BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))##当前路径 6 7 # 采集资产的方式,选项有:a...

二、vue响应式对象

Object.defineProperty Object.defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象,先来看一下它的语法: Object.defineProperty(obj, prop, descriptor) obj 是要在其上定义属性的对象; prop 是要定义或修改的属性的...

微信小程序基础学习笔记2:数据绑定相关

VUE语法 所有前端wxml中绑定的数据变量,都要定义在data中。 绑定事件,bindTap res ,取数据 currentTarget->dataset 这里的data-id中的data必须写,后面的id、title等可以任意自定义,H5的新属性,后面的id全部要用小写 myTap:function(res){ console.write }...

Axios 各种请求方式传递参数格式

为方便起见,为所有支持的请求方法提供了别名 在使用别名方法时, url、method、data 这些属性都不必在配置中指定 axios.request(config) axios.get(url[, config]) axios.delete(url[, config]) axios.head(url[, config]) axios.post(ur...