近兩天用的最多的就是上傳下載以及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導出,導入,功能都已經介紹了一下。以此記錄。