由於要導入大量數據,后台會耗費很長時間,導致超時。
本項目前端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" style="display: inline" action="#" class="upload-demo"> <el-button id="uploadButton" style="display: none" 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) { return request({ url: baseUrl + '/import', method: 'post', data: data, headers: { 'Content-Type': 'multipart/form-data' } }) }, getStatus() { return request({ url: baseUrl + "/getStatus", method: "GET" }); }, };
后台代碼:
controller:
@RestController @RequestMapping("/medicineList") @Slf4j public class MedicineListController extends BaseController { @Autowired private MedicineListService medicineListService; /** * 導入葯品信息 * * @param file * @return * @throws Exception */ @PostMapping("/import") public JSONObject importNodeInfo(MultipartFile file) throws Exception { return medicineListService.importNodeInfo(file.getInputStream()); } @GetMapping("/getStatus") public JSONObject getStatus() { return medicineListService.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 private MedicineListMapper 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); new Thread() { @Override public void run() { 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 = new DrugData(); 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(); return json; } @Override public JSONObject getStatus() { String isFinished = redisTemplate.opsForValue().get("isFinished"); JSONObject result = new JSONObject(); 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"); return result; } }
注意:導入Excel時,String.valueOf(row.getCell(0))獲取的值出了要進行非空判斷外,還要判斷不為字符串null,再執行插入,否則Excel中未填寫的單元格插入數據庫會變成字符串null。