技术选型:spring cloud + vue + nginx
需求:前端上传文件到服务器后可以实现下载与预览
问题与解决方案:因为前端的兼容性等问题、对于文件预览不是很友好、而且实现也都比较复杂、从而想通过后端将文件转化成pdf、前端通过vue的pdf.js插件实现预览效果、但是java对office文件的转化pdf支持性不是很好、比较好的jar还收费、由此利用java调用python脚本进行实现pdf文件的转换。
实现逻辑:前端已将文件上传到服务器、点击预览按钮时文件生成pdf文件并返回文件路径
java:
/**
* OFFICE文件转PDF方法
*
* @author liangyy
* @Date 2021/4/2 15:06
*/
public class TurnPDFUtil {
/**
* 文件转pdf
* @param path 文件保存路径
* @param name 文件名称
* @param pythonPath python安装路径
*/
public static void turnPDF(String path,String name,String pythonPath){
try {
String fileType = name.substring(name.lastIndexOf(StringConstant.DOT)+1).toUpperCase();
String oldName = name.substring(0,name.lastIndexOf(StringConstant.DOT));
String excelToPDFPY = getPDFPY(fileType);
// 获取文件夹名称
String folderName = "file";
// 生成文件名
String pdfPath = path + folderName + File.separator + oldName + FebsConstant.PDF_SUFFIX;
// 脚本路径
String excelToPDFToosPath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + File.separator + FebsConstant.RESOURCES_PY + File.separator + excelToPDFPY;
File file = new File(path + folderName + File.separator + name);
List<String> command = Lists.newArrayList();
command.add(pythonPath);
command.add(excelToPDFToosPath.replaceFirst("/",""));
command.add(file.getAbsolutePath());
command.add(pdfPath);
ExecuteShellUtil.execute(command);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 获取文件类型
* @param fileType
* @return
*/
private static String getPDFPY(String fileType){
if(FebsConstant.EXCEL.indexOf(fileType) > -1){
return "excelToPDF.py";
}else if(FebsConstant.WORD.indexOf(fileType) > -1){
return "wordToPDF.py";
}else if(FebsConstant.PPT.indexOf(fileType) > -1){
return "pptToPDF.py";
}
return null;
}
}
/**
* 操作系统进程
*
* @author liangyy
* @date 2021-03-31 10:20:07
*/
public class ExecuteShellUtil {
/**
* 执行(java启动系统进程时,启动成功后就直接返回了,并不会等待系统进程执行结束,这里我们需要等待系统进程调用结束后java方法再返回)
* @param command
*/
public static void execute(List<String> command) {
try {
// 创建系统进程
ProcessBuilder processBuilder = new ProcessBuilder();
// 设置系统进程要执行的系统程序和参数
processBuilder.command(command);
// 使用此进程生成器的属性启动一个新进程
Process process = processBuilder.start();
dealWith(process);
try {
// 等待子进程的结束,子进程就是系统调用文件转换这个新进程
process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 处理进程的IO防止出现阻塞、死锁等情况
* @param pro
*/
private static void dealWith(final Process pro) {
// 下面是处理堵塞的情况
try {
// 启动单独线程来清空pro.getInputStream()的缓冲区
new Thread() {
@Override
public void run() {
BufferedReader br1 = new BufferedReader(new InputStreamReader(pro.getInputStream()));
try {
String text;
while ((text = br1.readLine()) != null) {
System.out.println(text);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
} catch (Exception e) {
e.printStackTrace();
}
try {
// 不要忘记处理出理时产生的错误信息,不然会堵塞不前的
new Thread() {
@Override
public void run() {
BufferedReader br2 = new BufferedReader(new InputStreamReader(pro.getErrorStream()));
String text;
try {
while ((text = br2.readLine()) != null) {
System.err.println(text);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
python脚本:
下面是三个脚本文件
python安装如下两个模块:
pip install comtypes
pip install pypiwin32
excelToPDF.py:
# coding=utf-8
from sys import argv
from win32com import client
path = argv[1]
pdf_path = argv[2]
xlApp = client.Dispatch("Excel.Application")
books = xlApp.Workbooks.Open(path)
books.ExportAsFixedFormat(0, pdf_path)
books.Close(SaveChanges=0)
xlApp.Quit()
pptToPDF.py:
# coding=utf-8
from sys import argv
from comtypes.client import CreateObject
path = argv[1]
pdf_path = argv[2]
powerpoint = CreateObject("Powerpoint.Application")
powerpoint.Visible = 1
pdfCreate = powerpoint.Presentations.Open(path)
pdfCreate.SaveAs(pdf_path, 32)
pdfCreate.Close()
powerpoint.Quit()
wordToPDF.py:
# coding=utf-8
from sys import argv
from comtypes.client import CreateObject
path = argv[1]
pdf_path = argv[2]
wdToPDF = CreateObject("Word.Application")
pdfCreate = wdToPDF.Documents.Open(path)
pdfCreate.SaveAs(pdf_path, 17)
pdfCreate.Close()
wdToPDF.Quit()
java后端处理完毕。
注:文件转换直接调用TurnPDFUtil.turnPDF()方法,因为是在ServiceImpl中调用了该方法,文件返回路径由service直接替换后缀返回数据,所以在turnPDF方法中没有返回,根据你自己的逻辑修改该方法。该文件路径是由nginx所映射的路径。用python脚本时,先搭建python环境,然后在安装python脚本需要用的两个模块。在java代码中要把python的安装路径传到方法中,脚本的路径根据你自己的实际路径去修改修改配置
vue:
先执行下载 pdf.js 指令
npm install --save vue-pdf
预览页面引用 pdf.js
import pdf from 'vue-pdf'
<template>
<div>
<pdf
v-for="i in numPages"
ref="pdf"
:key="i"
:src="url"
:page="i"
/>
</div>
</template>
<script>
import pdf from 'vue-pdf'
export default {
name: 'PdfView',
components: {
pdf
},
data() {
return {
url: null,
numPages: null
}
},
// 每次进入页面时请求
activated() {
this.getNumPages()
},
methods: {
getNumPages() {
// 计算PDF页数
var loadingTask = pdf.createLoadingTask(this.url)
loadingTask.promise.then(pdf => {
this.numPages = pdf.numPages
loading.close()
}).catch(err => {
console.error(err)
loading.close()
this.$message({
message: this.$t('website.home.pdfOpenError'),
type: 'error'
})
})
}
}
}
</script>
<style lang="scss" scoped>
</style>
前端处理完毕。
注:该处是用router,带了一个文件的id,请求后端方法来查找该文件的映射路径,跳转的方法此处省略,跳转到预览路径后,调用getNumPages()方法,this.url是文件路径。