一、环境说明
前端 vue + 后端 springboot + 文件服务器nginx
项目:https端口8086、http端口8084
文件服务器:http端口35105
注:前后端未分离 ,都在Tomcat容器中
二、问题说明
1.跨域
项目端口为8086或8084,而文件服务器端口为35105,因此直接通过前端发送请求时存在跨域问题。
2.https时不能使用http获取pdf
当使用https协议打开服务端时,因文件服务器对应URL为http协议,浏览器会出于安全考虑对请求进行自动拦截。
3.文件服务器证书为自定义证书,不能通过浏览器的SSL认证。
三、解决方法
方式一:后端接口调用
说明:前端通过调用后端接口获取文件服务器中对应文件的流,从而在前端展示。
前端部分代码示例:
// 预览pdf文件
handlePreviewPdfFile () {
const newFormData = new FormData()
newFormData.append('url', this.url)
apiAPI.previewPdfFile(newFormData).then(res => {
this.pdfUrl = this.getObjectURL(res.data)
})
},
// 将返回的流数据转换为url
getObjectURL (file) {
let url = null
if (window.createObjectURL !== undefined) { // basic
url = window.createObjectURL(file)
} else if (window.webkitURL !== undefined) { // webkit or chrome
try {
url = window.webkitURL.createObjectURL(file)
} catch (error) {
console.log(error)
}
} else if (window.URL !== undefined) { // mozilla(firefox)
try {
url = window.URL.createObjectURL(file)
} catch (error) {
console.log(error)
}
}
return url
}
后端部分代码示例:
public void previewPdfFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
String url = request.getParameter("url");
if (StringUtils.isBlank(url)) {
log.error("请求参数url为空");
return;
}
FileDownLoadConfig fileDownLoadConfig = new FileDownLoadConfig();
//基准参照路径
String baseDir = fileDownLoadConfig.getFileDownloadPath();
// 文件下载时服务器的临时文件夹,用于存放多个文件
String dir = baseDir + File.separator + UUID.randomUUID().toString().replaceAll("\\-", "");
//删除重名的临时文件夹
File tempFile = new File(dir);
if (tempFile.exists() && tempFile.isDirectory()) {
FileOperateUtil.delFilesApache(dir);
}
tempFile.mkdirs();
//将文件服务器文件下载到dir
DownloadFileFromURLUtil.downloadFromUrl(url, dir);
// 获取下载下来的文件
String fileName = DownloadFileFromURLUtil.getFileNameFromUrl(url);
if (IPUtil.isWindowsOS()) {
fileName = "1.zip";
}
File file = new File(dir + File.separator + fileName);
// 设置response
response.reset();
response.setContentType(MediaType.APPLICATION_PDF_VALUE);
response.setHeader("Access-Control-Expose-Headers", "filename");
response.addHeader("filename", java.net.URLEncoder.encode(fileName, "UTF8"));
// 返回
BufferedInputStream in = null;
OutputStream out = null;
try {
in = new BufferedInputStream(Files.newInputStream(file.toPath()));
out = response.getOutputStream();
int bytes = 0;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
out.flush();
} catch (Exception e) {
log.error("预览pdf文件失败:{} ===> 详情:{}", e.getMessage(), e);
throw new BizCommonException("预览pdf文件失败:返回异常");
} finally {
if (null != out) {
out.close();
}
if (null != in) {
in.close();
}
//删除临时文件目录
FileOperateUtil.delFilesApache(dir);
}
}
方式二:另起标签页
说明:在前端增加一个白名单页面preview-pdf.vue(类似login页面),当点击预览按钮时浏览器另外打开一个标签页http://XXX:8084/XX/preview-pdf。
注:默认情况下,我们项目中http协议未开启,故前后端需要对http协议下的preview-pdf页面增加白名单
点击预览按钮方法部分代码示例:
async handlePreviewPdf () {
let httpPort = ''
await api.getPort().then(res => {
// 心跳http端口
httpPort = res
})
if (httpPort === '') {
return
}
Cookies.set('previewPdfUrl', this.previewPdfUrl)
Cookies.set('previewPdfPassword', this.previewPdfPassword)
window.open('http://' + window.location.hostname + ':' + httpPort + '/XX/preview-pdf')
}
preview-pdf页面部分代码示例:
<template>
<div class="pdf-preview-container">
<div class="header">
<h2>pdf文件预览</h2>
</div>
<div class="pdf-container">
<VuePdf :url="pdfData.previewPdfUrl" :password="pdfData.previewPdfPassword"/>
</div>
</div>
</template>
<script>
import Cookies from 'js-cookie'
import VuePdf from '@/components/VuePdf/index.vue'
export default {
// 组件名称
name: 'PreviewPdf',
// 组件
components: { VuePdf },
// 数据
data () {
return {
// pdf预览数据
pdfData: {
// pdf文件路径
previewPdfUrl: '',
// pdf文件密码
previewPdfPassword: ''
}
}
},
// 生命周期 created
created () {
this.handlePreview()
},
mounted () {
// 右键事件监听器
this.contextMenuListener = function (e) {
e.preventDefault()
e.stopPropagation()
}
document.addEventListener('contextmenu', this.contextMenuListener)
// 键盘事件监听器
this.keyDownListener = function (e) {
e.preventDefault()
e.stopPropagation()
}
document.addEventListener('keydown', this.keyDownListener)
},
destroyed () {
// 移除右键事件监听器
document.removeEventListener('contextmenu', this.contextMenuListener)
// 移除键盘事件监听器
document.removeEventListener('keydown', this.keyDownListener)
},
// 方法
methods: {
handlePreview () {
// 取cookie
const key = ['previewPdfUrl', 'previewPdfPassword']
this.pdfData = {
previewPdfUrl: Cookies.get('previewPdfUrl'),
previewPdfPassword: Cookies.get('previewPdfPassword')
}
key.forEach(item => {
Cookies.remove(item)
})
}
}
}
</script>
<style lang="less" scoped>
.pdf-preview-container {
display: flex;
flex-direction: column;
height: 100vh; /* 设置为视口高度,这样容器会占据整个页面 */
overflow: hidden; /* 确保外部容器不显示滚动条 */
}
.header {
text-align: center; /* 使h2标签内容水平居中 */
padding: 10px; /* 可根据需要添加内边距 */
}
.pdf-container {
flex: 1; /* 使pdf容器占据剩余空间 */
height: 90%; /* 设置高度为父容器(去除 header 后)的 90% */
overflow: hidden; /* 确保外部容器不显示滚动条 */
}
</style>
文件服务器nginx支持跨域部分配置示例:
add_header Access-Control-Allow-Origin *;
方式三:网关拦截
说明:在前端和文件服务器中间增加一层网关。前端发出https协议请求,网关拦截后转换为http
网关部分配置示例:
spring:
application:
name: gateway-application
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOriginPatterns: "*"
allowCredentials: true
allowedMethods:
- GET
- POST
- DELETE
- PUT
- PATCH
- OPTIONS
allowedHeaders: "*"
default-filters:
- DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST
routes:
# 与基础平台与子系统交互 后端 》》》》》》start
- id: vios-preview
uri: http://127.0.0.1:35105
order: 1
predicates:
- Path=/XXXX/**
文件服务器nginx支持跨域部分配置示例:
add_header Access-Control-Allow-Origin *;