文件上傳與下載以及導出導出(elmentui+springboot)

近兩天用的最多的就是上傳下載以及excel的導入和導出,測試人員提的bug不斷,走了很多坑,現將其記錄下來,以作記錄。

首先將應用情況介紹下:


三按鈕之 導入數據集:將excel模板中的數據導入到數據庫中。(excel具有指定的格式樣式等)

三按鈕之 導出數據集:將選中的數據庫中的某條數據導出到excel。(用的是同一個模板)

三按鈕之 導出數據集模板 設計人員希望在導入數據時,首先下載模板,而且可以根據我所選的模板類型,將不變的東西給我填充好,使用者到時候直接填數據就好了,本質也是一個導出。


 <el-button-group>
                    <el-upload
                            :show-file-list="false"
                            :action="upload.importUrl"
                            :on-success="onDataSetUploadSuccess"
                            :on-error="onDataSetUploadError"
                            :file-list="upload.fileList" style="float:left">
                        <el-tooltip class="item" effect="dark" content="導入數據集" placement="bottom">
                            <el-button icon="yx-upload3"></el-button>
                        </el-tooltip>
                    </el-upload>
                    <el-tooltip class="item" effect="dark" content="導出數據集" placement="bottom">
                        <el-button icon="yx-download3" @click="exportFn"></el-button>
                    </el-tooltip>
                    <el-tooltip class="item" effect="dark" content="下載數據集模板" placement="bottom">
                        <el-button icon="yx-download3" @click="SetDownloadFunc"></el-button>
                    </el-tooltip>
                </el-button-group>
   exportFn: function () {
                    var _data = this.$refs.reqMsgTable.selections;
                    if (_data == null || _data.length != 1) {
                        this.$message("請選擇一條數據", "提示");
                        return;
                    }
                    for (var i in _data) {
                        var param = '?trdInfId=' + _data[i].trdInfId + "&pkId=" + _data[i].pkId;
                        var url = backend.adminService + "/api/ymit/dataset/export"
                        yufp.util.download(url + param);
                    }
                },


@GetMapping("/export")
	public void download(String trdInfId, String pkId, HttpServletResponse response, HttpServletRequest request) {
		String fileNm = "接口測試數據集模板示例.xls";
		BufferedOutputStream bufferedOutPut = null;
		try {
			String fileName = fileNm.substring(fileNm.lastIndexOf("/") + 1);
			response.setContentType(FileTypeUtil.getMimeType(fileName) + ";charset=UTF-8");
			response.setHeader("Content-Disposition",
					"attachment; filename=" + FileTypeUtil.getEncodeFileName(request, fileName));
			// Workbook workbook = ymitDatasetService.export(trdInfId, pkId);
			Workbook workbook = ymitDatasetService.export2(trdInfId, pkId);
			bufferedOutPut = new BufferedOutputStream(response.getOutputStream());
			bufferedOutPut.flush();
			workbook.write(bufferedOutPut);

		} catch (IOException e) {
			e.printStackTrace();
			throw new YuspException(MessageProvider.getMessage("200001"));
		} finally {
			IOUtils.closeQuietly(bufferedOutPut);
		}
	}

因爲開發第一版,並未打算使用文件服務器,所以附件,模板這種東西,目前是在工程中resource下建了一個文件夾來存放。如圖所示。

public Workbook export2(String trdInfId, String datasetId) {
		YmitTradeInf ymitTradeInf = new YmitTradeInf();

		ymitTradeInf.setTrdInfId(trdInfId);
		List<YmitTradeInf> list = ymitTradeInfMapper.select(ymitTradeInf);
		if (list.size() > 0) {
			ymitTradeInf = list.get(0);
		}

		DataSetViewVM dataSetViewVM = getDataSetView(datasetId);

		return exportDataset2(dataSetViewVM, ymitTradeInf);

	}

	public Workbook exportDataset2(DataSetViewVM dataSetViewVM, YmitTradeInf ymitTradeInf) {
		InputStream in = null;
		try {
			ClassPathResource classPathResource = new ClassPathResource("tml/接口測試數據集模板示例.xls");
			in = classPathResource.getInputStream();
			Workbook workBook;
			workBook = new HSSFWorkbook(in);
			Sheet sheet = workBook.getSheet(TEMPLATE);
			/* 寫入交易信息 */
			trdInfToExcel(sheet.getRow(0), ymitTradeInf);
			/* 寫入數據及信息 */
			datasetInfToExcel(sheet.getRow(1), dataSetViewVM);
			List<YmitDataGroup> dataGroups = dataSetViewVM.getDataGroups();
			List<YmitDataItem> dataItems = dataSetViewVM.getDataItems();
			int grpNum = 4;
			if (null != dataGroups) {
				for (YmitDataGroup grp : dataGroups) {
					Row dataTitle = sheet.getRow(2);
					dataTitle.getCell(grpNum).setCellValue(grp.getDatagrpNm());
					int rowIndex = 3;
					for (YmitDataItem item : dataItems) {
						if (item.getDatagrpId().equals(grp.getDatagrpId())) {
							if (grpNum == 4) {
								Row row = sheet.getRow(rowIndex);
								row.getCell(0).setCellValue(rowIndex - 2);
								row.getCell(1).setCellValue(item.getFldNm());
								row.getCell(2).setCellValue(item.getFldDesc());
								row.getCell(3).setCellValue(item.getDefVal());
								row.getCell(4).setCellValue(item.getFldVal());
								rowIndex++;
							} else {
								Row row = sheet.getRow(rowIndex);
								row.getCell(grpNum).setCellValue(item.getFldVal());
								rowIndex++;
							}
						}
					}
					grpNum++;
				}
			}
			return workBook;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		} finally {
			IOUtils.closeQuietly(in);
		}

	}


肯其中業務邏輯的一部分代碼沒有貼。

導出到此爲止

導入

el-upload不多說了,根據參數就可以明白其中的各個屬性方法的作用,

 upload: {
                        importUrl: "http://" + config.url + "/api/ymit/dataset/uploadFile?access_token=" + yufp.service.getToken() + "&trdInfId=" + pathId + "&usrCde=" + yufp.session.userName,
                        fileList: [],
                    },

這裏因爲公司封裝的一些方法不太好的緣故,導致唯獨上傳url給添加上token,所以這裏自己拼接的。並添加了想要的參數。

@RequestMapping(value = "/uploadFile")
	public ResultDto<DataSetTemplate> uploadDataSet(MultipartFile file, @RequestParam("trdInfId") String trdInfId,
			@RequestParam("usrCde") String usrCde) {
		DataSetTemplate dataSetTemplate = null;
		try {
			dataSetTemplate = dataSetTemplateService.uploadDataSet(file.getInputStream());
			ymitDatasetService.addDataSetFromTemplate(dataSetTemplate, trdInfId, usrCde);
		} catch (IOException e) {
			ResultDto<DataSetTemplate> result = new ResultDto<>();
			result.setCode(2004);
			result.setMessage("文件格式錯誤:" + e.getMessage());
		} catch (Throwable e) {
			ResultDto<DataSetTemplate> result = new ResultDto<>();
			result.setCode(2005);
			result.setMessage(e.getMessage());
		}
		return new ResultDto<>(dataSetTemplate);
	}
	public DataSetTemplate uploadDataSet(InputStream is) throws IOException{
		if(is==null) {
			return null;
		}
		DataSetTemplate template=null;
		HSSFWorkbook workBook=null;
		try {
			workBook=new HSSFWorkbook(is);
			HSSFSheet sheet=workBook.getSheet(SHEET_NAME);
			template=parseSheet2DataSetTemplate(sheet);
		}finally {
			if (workBook != null) {
				workBook.close();
			}
		}
		return template;
	}

核心類已經貼完,其實簡單來說就是首先spring的MultipartFile 獲取流,以此流獲得excel 這裏就是HSSFWorkbook (03版的)

然後就是讀取裏面的各行格列數據,然後填充了,很簡單。

導入 到此結束

上傳附件 下載附件



	@Value("${trdattach.path}")
	private String  attachPath;
// 上傳文件會自動綁定到MultipartFile中
	@RequestMapping(value = "/upload", method = RequestMethod.POST)
	public ResultDto<YmitTrdInfAttach> upload(HttpServletRequest request, @RequestParam("file") MultipartFile file,
			@RequestParam("trdInfId") String trdInfId, @RequestParam("usrCde") String usrCde) throws Exception {
		try {
			Map<String, Object> result = new HashMap<String, Object>();
			if (!file.isEmpty()) {
				// 上傳文件路徑
				String path = attachPath+"/"+SessionUtil.getCurrentProjectCode()+"/"+trdInfId;
				// 上傳文件名
				String filename = file.getOriginalFilename();
				if(filename.contains("\\")) {
					filename = filename.substring(filename.lastIndexOf("\\")+1);
				}
				File filepath = new File(path, filename);
				if (!filepath.getParentFile().exists()) {
					filepath.getParentFile().mkdirs();
				}
				long size = file.getSize();
				// 將上傳文件保存到一個目標文件當中
				YmitTrdInfAttach ymitTrdInfAttach = new YmitTrdInfAttach();
				ymitTrdInfAttach.setTrdInfId(trdInfId);
				ymitTrdInfAttach.setUpdUsrId(usrCde);
				file.transferTo(new File(filepath.getAbsolutePath()));
				ymitTrdInfAttach.setUpdDt(df.format(new Date()));
				// ymitTrdInfAttach.setUpdUsrId(loginCode);
				ymitTrdInfAttach.setFileNm(filename);

				ymitTrdInfAttach.setFileSize(String.valueOf(size));
				ymitTrdInfAttachService.insertSelective(ymitTrdInfAttach);
				result.put("code", "0");
				result.put("message", "操作成功");
				return new ResultDto(result);
			} else {
				result.put("code", "2");
				result.put("message", "上傳文件爲空或者已損壞");
				return new ResultDto(result);
			}
		} catch (FileAlreadyExistsException e) {
			logger.error("上傳失敗",e);
			throw new YuspException(MessageProvider.getMessage(ErrorCodeConst.CODE_200006));
		}

	}
attachPath是yml文件中配置的相對路徑 例如

@GetMapping("/download")
	public void download(String fileNm,String trdInfId,HttpServletResponse response, HttpServletRequest request) {
		logger.debug("請求參數:{}", fileNm,trdInfId);
		String path = attachPath+"/"+SessionUtil.getCurrentProjectCode()+"/"+trdInfId;
		File file = new File(path + File.separator + fileNm);
		try {
			if (file.exists() && file.isFile()) {
				byte[] downloadFile = FileUtils.readFileToByteArray(file);
				String fileName = fileNm.substring(fileNm.lastIndexOf("/") + 1);
				response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
				response.setHeader("Content-Disposition",
						"attachment; filename=" + FileTypeUtil.getEncodeFileName(request, fileName));
				response.getOutputStream().write(downloadFile);
				response.getOutputStream().flush();
			} else {
				throw new YuspException(MessageProvider.getMessage(ErrorCodeConst.CODE_200007));
			}
		} catch (IOException e) {
			e.printStackTrace();
		} 
	}

}
FileTypeUtil的作用就是爲了適應各種瀏覽器而進行的操作,本質是一樣,例如
/**
     * 根據不同瀏覽器設置文件名
     *
     * @param request
     * @param filename-文件名稱
     */
    public static String getEncodeFileName(HttpServletRequest request, String filename) throws
            UnsupportedEncodingException {
        String userAgent = request.getHeader("User-Agent").toLowerCase();
        if (userAgent.contains("msie") || userAgent.contains("trident/7.0")||userAgent.contains("edge")) {
            return URLEncoder.encode(filename, "UTF-8");
        } else if (userAgent.contains("mozilla") || userAgent.contains("chrome")) {
            return new String(filename.getBytes(), "ISO-8859-1");
        } else {
            return URLEncoder.encode(filename, "UTF-8");
        }
    }

主要是爲了解決chrom一般不會出現下載文件文件名亂碼的問題,但是微軟的IE,edge等都會有問題,所以這裏進行了操作,根據您用的是什麼瀏覽器。

代碼介紹完畢

踩過的坑:首先說明,這些代碼目前測試還算一路順風。

1 文件路徑的問題 用絕對路徑不會有問題,但是,不利於維護,而且服務器一般爲linux,路徑和window或許有差異*(我也不太熟悉)。這裏配置的是相對路徑,使用相對路徑,會有大問題,問題不在於技術本身,而在於spring

MultipartFile的transfer方法,會判斷絕對還是相對,如果相對,他會給你添上一些前綴,這一添加,就會導致文件路徑不存在,因爲我們判斷並新建的不是他自動添加的這一個。嘗試了各種網上說的方法,首先麻煩,第二,很多時候環境是公司搭建好的,不是我們可以隨便修改配置文件的。第三,經本人嘗試,真的不能解決問題。

@Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setMaxFileSize("128KB");
        factory.setMaxRequestSize("128KB");
        return factory.createMultipartConfig();
    }

這種方法親測,各種問題頻出,並不適合新人。

最後問了我的導師,他說這麼麻煩嗎,他不讓用相對路徑,那你就轉爲絕對路徑啊

file.transferTo(new File(filepath.getAbsolutePath()));

僅此一個get方法,讓網上多少人汗顏。

再次說明,很多例子都是直接用的絕對路徑。這裏要注意。

第二個坑 excel導出

在本次中因爲模板很特殊,而且樣式比較奇怪,我不想寫代碼去構造excel。所以是將模板放在程序中,導出時直接去填充數據就好了,有很多不便之處,但是目前是業務需要。

剛開始爲讀取到該新增模板路徑下的文件煞費苦心,後終得一法,


被刪除的方法是讀取相對路徑的文件,使用過程中,本地調試,沒有任何問題,但是到服務器就彙報空指針異常。

後導師看了一眼,說道,服務器上都是jar包了,怎麼能讀取到呢。要換一個方法。

雖不甚理解,但是換位下邊的方法,果然服務器上好用。總結來說改工具就是爲了讀取jar包吧

第三  spring的 MultipartFile 這個getOriginalFilename獲取文件名的方法吧,不同瀏覽器也不一樣,Chrome沒問題,但是其他的IE edge openg等瀏覽器上傳文件時,用該法得到的文件名是f://tem//文件夾/a.jpg 帶上傳電腦路徑的,肯定報錯了,所以最好還是加一個判斷並剔除掉。

到此爲止,附件上傳,下載,excel導出,導入,功能都已經介紹了一下。以此記錄。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章