近两天用的最多的就是上传下载以及excel的导入和导出,测试人员提的bug不断,走了很多坑,现将其记录下来,以作记录。
首先将应用情况介绍下:
三按钮之 导入数据集:将excel模板中的数据导入到数据库中。(excel具有指定的格式样式等)
三按钮之 导出数据集:将选中的数据库中的某条数据导出到excel。(用的是同一个模板)
三按钮之 导出数据集模板 设计人员希望在导入数据时,首先下载模板,而且可以根据我所选的模板类型,将不变的东西给我填充好,使用者到时候直接填数据就好了,本质也是一个导出。
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 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 dataGroups = dataSetViewVM.getDataGroups();
List 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 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 result = new ResultDto<>();
result.setCode(2004);
result.setMessage("文件格式错误:" + e.getMessage());
} catch (Throwable e) {
ResultDto 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 upload(HttpServletRequest request, @RequestParam("file") MultipartFile file,
@RequestParam("trdInfId") String trdInfId, @RequestParam("usrCde") String usrCde) throws Exception {
try {
Map result = new HashMap();
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导出,导入,功能都已经介绍了一下。以此记录。