PDF开发
一、文件预览
1、业务需求
一笔订单关联多个文件,要求点击查看按钮即可预览对应的文件
2、完整代码
前端代码:
-
template
:onCheck
点击事件,预览文件<template slot="actionSlot" slot-scope="text,record"> <div> <xxx-button @click="onCheck(record)" type="primary" size="small" > {{ $tt('table.check') }} </xxx-button> </div> </template>
-
javascript
:跳转浏览器预览文件export default class XXX extends Vue { //查看方法 public onCheck(row:orderRelFile) { this.data.id = row.id;//获取选中行id(与影像文件关联的订单id) getDownloadFile(this.data).then((res) => {//axios请求后端 const binaryData: any = []; binaryData.push(res.data);//响应的数据添加进数组中 let str = row.fileName;//获取选中行的文件名 let nameValue = ''; let url = null; if (str.length > 1){ nameValue = str.substring(str.length - 3);//截取文件名后三位字符串 if (nameValue == 'pdf') {//判断文件是否为pdf格式 if (window.URL.createObjectURL !== undefined) {//文件流转化为url地址,获取Blob连接 url = window.URL.createObjectURL( new Blob(binaryData, { type: 'application/pdf'})//Blob中的数组内容的MIME类型 ); window.open(url, 'blank');//浏览器跳转地址为url的新页面 } } } if (nameValue == 'jpg' || nameValue == 'png') {//判断文件是否为图片格式 if (window.URL.createObjectURL !== undefined) { url = window.URL.createObjectURL( new Blob(binaryData, { type: 'image/jgeg'}) ); window.open(url, '_blank'); } } } }); } }
-
typescript
:发送axios
请求后端接口/**预览pdf */ export const getDownloadFile = (params: any) => { return axiosIns.request({ url: '/orderrelfile/downloadRelFile', method: 'post', params: params, headers: { 'Content-Type': 'appliacation/json', }, responseType: 'blob', }); };
后端代码:从对象存储服务器读取文件,返回文件流
@PostMapping(path = "/downloadRelFile")
@SysLogb
public void previewPdf(HttpServletRequest request, HttpServletResponse response,String id){
//接收前端传参,选中行id
OrderRelFile relFile = orderRelFileService.load(id);//查找订单id对应的文件
String filePath = relFile.getFilePath();
String fileName = relFile.getFileName();
S3Object s3Objtect = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try{
s3Object = downloadService.readObject(bucketName, filePath);//从流中读取并返回该对象
request.setCharacterEncoding("UTF-8");
response.setContentType(request.getSession().getServletContext().getMimeType(fileName));
bos = new BufferedOutputSream(response.getOutputStream());
String agent = request.getHeader("user-agent");
if (agent.contains("MSIE")){
fileName = URLEncoder.encode(fileName,"utf-8");
fileName = fileName.replace("+"," ");
}else {
String type = response.getHeader("use-down-type");
if (type != null && agent.contains("Firefox")) {
fileName = new String(fileName.getBytes("utf-8"), StandardCharsets.ISO_8859_1);
}else {
fileName = URLEncoder.encode(fileName, "utf-8");
}
}
response.setHeader("Content-Disposition", "inline; filename=" + fileName);
response.setContentType("application/pdf;charset=UTF-8");
response.setHeader("Content-Length",String.valueOf(s3Object.getObjectMetadata().getContentLength()));
bis = new BufferedInputStream(s3Object.getObjectContent());
byte[] buff = new byte[204800];
int bytesRead;
while(-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
bos.write(buff, 0, bytesRead);
}
}catch (IOException e){
throw new ServiceException("SYSE305");
}finally {
try {
if (null != s3Object) {
s3Object.close();
}
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3、踩坑分析
问题:直接使用浏览器预览对象存储的文件,url
会直接暴露在地址栏,泄露隐私造成安全隐患
解决:后端发送文件流,前端接收文件流并转化url
地址获取Blob
连接
二、文件导入
1、业务需求
一笔订单关联多个影像文件,要求点击导入文件按钮即可导入对应的影像文件
2、完整代码
前端代码:
template
:弹出上传操作弹窗
<xxx-modal
title="上传文件"
v-model="visibleuploadFile"//绑定visibleuploadFile控制是否显示弹窗
@ok="onOkuploadFile"//弹窗确认按钮点击事件
>
<xxx-upload-tool
:format="['jpg','png','txt','xlsx','docx','doc','zip','7z']"//限制上传文件格式
:uploadUrl="uploadData"//uploadData计算属性调用后端接口
:multiple="true"
maxSize="10000"
></xxx-upload-tool>
</xxx-modal>
javascript
:上传文件同时携带参数
export default class XXX extends Vue {
public visibleuploadFile = false;//设置弹窗默认不显示
public orderId = '';
//导入弹窗
public uploadFileModal() {
try {
this.selectedRowKeys.map((id) => {//获取选中行id
this.orderId = id;
});
} catch (e) {
console.log(e);
}
this.visibleuploadFile = true;//显示弹窗
}
//导入弹窗确认
public onOkuploadFile() {
location.reload();//刷新当前页面
}
//自定义按钮
get tableButtons():FormButton[] {
return [
{
type:'primary',
icon:'upload',
clickFn: {
functionName:'uploadFileModal',//绑定操作uploadFileModal方法
componentName:this.$options.name,
},
text:'文件导入',
disabled:this.selectedRowKeys.length === 0,//未选中数据按钮置灰
},
];
}
//返回后端接口url
get uploadData() {
return 'orderinfo/upload?orderId=' + this.orderId;//请求后端接口并传入参数
}
}
后端代码:本地上传文件到对象存储服务器,文件信息落库,重复信息更新
/**
* @description 导入文件
*/
@PostMapping(value = "/upload")
public List<Map<String,Object>> upLoad(HttpServletRequest request, @RequestParam("file"))MultipartFile file,@RequestParam(value = "orderId") String orderId) throws S3Exception,IOException {
List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
Map<String,Object> result = new HashMap<>();
String fileName = file.getOriginalFilename();
FileUploadValidator validator = FileUploadValidator.getInstance();
File filePath = new File(videofilesParentPath + filename);
if (!filePath.getParentFile().exists()) {
filePath.getParentFile().mkdirs();
}
String key = filePath.getAbsolutePath();
boolean validateFile = validator.isValidateFile(file.getBytes(),fileName,videofilesParentPath,1024 * 1024,null,request);
if(validateFile) {
file.transferTo(filePath.getAbsoluteFile());
StopWatch stopWatch = new StopWatch("upLoadS3File:" + filePath);
Map<String,String> userMetadataMap = new HashMap<String,String>();
userMetadaMap.put("name",URLEncoder.encode("文件","UTF-8"));//自定义元数据
logManager.MICROSERVICE_LOGGER.info("开始上传{}文件",filePath);
stopWatch.start();
try {
s3UploadService.createLargeObjectWithKey(key,filePath,userMetadataMap);
} catch (S3Exception s3Exception) {
throw new SerciceException("SYSE302");
}
stopWatch.stop();
logManager.MICROSERVICE_LOGGER.info("上传{}文件成功,耗时{}ms",filePath,stopWatch.getTotalTimeMillis());
//上传对象存储服务器成功后,删除原来下载的目录
boolean delete = filePath.delete();
logManager.MICROSERVICE_LOGGER.info("删除{}文件是否成功{}",filePath,delete);
//上传到对象存储服务器后,进行存储
OrderInfo orderInfo = orderInfoService.load(orderId);
String orderRelFileId = seqGeneratorUtil.getDateSequenceNo("BusiNo");
String s3Url = s3Client.getAmazonS3Client().getUrl(s3Client.getBucketName(),key).toString();
String orderNo = orderInfo.getOrderNo();
OrderRelFile orderRelFile = OrderRelFile.builder()
.id(orderRelFileId)
.orderNo(orderNo)
.fileName(filename)
.filePath(key)//对象存储时路径相当于key值
.fullPath(s3Url)//直接可访问的http连接
.build();
List<OrderRelFile> orderRelFiles = orderRelFilesService.findByOrderNo(orderInfo.getOrderNo());
if (CollectionUtils.isEmpty(orderRelFiles)) {
result.put("msg","该笔订单没有文件");
}
//存在重复数据即更新数据
Boolean isDbHava = Booelean.FALSE;
for(OrderRelFile relFile : orderRelFiles) {
String relFileName = relFile.getFileName();
if (relFileName.equals(fileName)) {
isDbHava = Boolean.True;
break;
}
}
if (isDbHava) {
orderRelFileService.update(orderRelFile);
}else {
orderRelFileService.update(orderRelFile);
}
logManager.MICROSERVICE_LOGGER.info("对象存储文件地址{}",s3Url);
logManager.MICROSERVICE_LOGGER.info("存储{}文件成功",filePath);
result.put("returnCode","success");
}else {
result.put("returnCode","fail");
result.put("msg","文件检验失败");
}
list.add(result);
return list;
}
@Value("${xxx.videofiles}")
public void setVideofilesParentPath(String vfPath) {
videofilesParentPath = vfPath;
}
3、踩坑分析
问题:上传文件使用post请求无法携带参数
解决:上传组件中直接写uploadUrl
请求后端接口无法携带参数,使用计算属性uploadData
转化地址并传入参数,后端使用@RequestParam
注解接收参数