本文使用OpenOffice+pdf.js实现
最近时间太少 格式没有排好 这种解决方案也实在没有办法 个人非常不满意 有好的解决方案必定更新
花了一周的时间实现此功能 感觉是网络太少实用的教程 或是自己能力不行吧 话不多说 内容如下
网络上现在(2019年5月)实现文件预览的几种方式
- poi转换Office文件为pdf 据说效果不好
- OpenOffice转换Office文件为pdf 大部分教程都在使用
- pdf.js对pdf进行显示
- swfTools将pdf文件转换成swf 通过FlexPaper显示 依赖Flash
被github迷惑 https://github.com/flexpaper/flexpaper 不知为什么是as 不是js
被官网迷惑 网络教程大部分都是官网改变前 直接访问http://flexpaper.devaldi.com/download/就可下载
现在访问 http://flexpaper.devaldi.com/download/ 直接跳转到 https://flowpaper.com/download/ 此页面 而且主页是exe执行文件下载 但是翻译+Google还是在现在的官网找到下载路径 下载页面如下
但是为时已晚发现下载地址时 我的pdf.js已经做好 有机会在用FlexPaper实现一次 FlesPaper实现请参考其它文章比如
swftools下载 http://www.swftools.org/download.html
OpenOffice使用分为Windows和Linux 通过此链接下载 http://www.openoffice.org/zh-cn/download/
Windows
使用cmd进入 C:\Program Files\OpenOffice4\program 执行如下命令启动 任务管理器会有soffice的进程
soffice -headless -accept=“socket,host=127.0.0.1,port=8100;urp;” -nofirststartwizard
可使用telnet localhost 8100 进行测试
Linux
# 下载npm版本 如Apache_OpenOffice_4.1.5_Linux_x86-64_install-rpm_zh-CN.tar.gz
tar -zxvf Apache_OpenOffice_4.1.5_Linux_x86-64_install-rpm_zh-CN.tar.gz
# 解压后进入zh-CN/RPMS 安装所有rpm
cd zh-CN/RPMS
rpm -ivh *.rpm
# 进入desktop-integration目录 安装openoffice4.1.5-redhat-menus-4.1.5-9789.noarch.rpm
cd desktop-integration
rpm -ivh openoffice4.1.5-redhat-menus-4.1.5-9789.noarch.rpm
# 启动openOffice 后面加'&'为永久启动
/opt/openoffice4/program/soffice -headless -accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard&
# 关闭就使用kill命令 如下
ps -ef|grep soffice 或 ps -ef|grep openoffice
kill - 9 进程编号
# 卸载openoffice
rpm -e `rpm -qa |grep openoffice` `rpm -qa |grep ooobasis`
还需要依赖jar包 maven如下 中央仓库现在(2019年5月)只有有2.21版本 不支持docx pptx xlsx的格式 但是官网可以下载 2.2.2的jar包 使用maven命令手动引入即可 2.2.2下载地址
https://sourceforge.net/projects/jodconverter/files/
<!-- jodconverter文档转换类 maven仓库只有2.2.1 2.2.2为手动安装 -->
<dependency>
<groupId>com.artofsolving</groupId>
<artifactId>jodconverter</artifactId>
<version>2.2.2</version>
</dependency>
java代码如下 使用file类的方式 暂时不会使用流的方式转换 造成现在程序只能和预览文件放在一起
如下代码为上传文件的同时判断是否为office文件和pdf文件 如为office文件则生成预览pdf文件 为pdf文件则把此pdf文件复制到预览文件夹
/** 上传文件 */
@PostMapping("/upload")
@ResponseBody
public Map<String, Object> uploadFile(MultipartFile file) {
Map<String, Object> res = new HashMap<>();
Map<String, Object> resUrl = new HashMap<>();
boolean flag = false; // 是否生成文件预览文件
// 取得当前上传文件的文件名称
String fileName = file.getOriginalFilename();
// 如果名称不为"" 说明该文件存在 否则说明该文件不存在
if ("" != fileName.trim()) {
String fileExtName = fileName.substring(fileName.lastIndexOf("."));
String serverFileName = null;
String newFileName = null;
ChannelSftp sftp = null;
String[] types = {".doc",".docx",".xls",".xlsx",".ppt",".pptx",".pdf"};
for (String type:types) {
if(type.equals(fileExtName)) {
try {
// 打印文件名称
LOGGER.info(">>> fileName " + fileName +" <<<");
// 获取扩展名
// 没有'-'的uuid
serverFileName = UUID.randomUUID().toString().replace("-", "");
// 文件新名称
newFileName = serverFileName + fileExtName;
InputStream is = file.getInputStream();
// linux服务器地址 账号 密码
sftp = getChannel("192.168.110.110", 22, "root", "root", 60000);
final String folderName = "/usr/local/upload-files";
// 进入linux服务器文件目录
sftp.cd(folderName);
// 判断子目录文件夹是否存在 不存在即创建
SftpATTRS attrs = sftp.stat(folderName);
if (attrs == null) {
sftp.mkdir(folderName);
LOGGER.info("创建目录 : " + folderName);
}
// 把文件流命名成文件名称推送到linux
sftp.put(is, newFileName);
closeChannel(); // 关闭连接
} catch (IOException e) {
e.printStackTrace();
LOGGER.error("上传文件失败 请检查连接参数", e);
} catch (JSchException e) {
e.printStackTrace();
LOGGER.error("上传文件失败 请检查连接参数", e);
} catch (SftpException e) {
e.printStackTrace();
LOGGER.error("上传文件失败 请检查连接参数", e);
}
// -----生成文件预览开始
String[] PictureTypes = {".pdf"};
for (String PictureType:PictureTypes) {
if (fileExtName.equals(PictureType)) {
flag = false; // 禁止生成文件预览文件
try {
int bytesum = 0;
int byteread = 0;
InputStream inStream = new FileInputStream("/usr/local/upload-files/" + newFileName); // 读入原文件
FileOutputStream fs = new FileOutputStream("/usr/local/upload-files/preview_pdf/" + newFileName);
byte[] buffer = new byte[1444];
while ( (byteread = inStream.read(buffer)) != -1) {
bytesum += byteread; //字节数 文件大小
System.out.println(bytesum);
fs.write(buffer, 0, byteread);
}
inStream.close();
fs.close();
}
catch (Exception e) {
System.out.println("复制单个文件操作出错");
e.printStackTrace();
}
resUrl.put("filePreviewAddress", newFileName);
}
}
// 来自微软官网 https://docs.microsoft.com/zh-cn/deployoffice/compat/office-file-format-reference
String[] officeTypes = {".doc",".docx",".xls",".xlsx",".ppt",".pptx"};
for (String officeType:officeTypes) {
if(fileExtName.equals(officeType)) flag = true;
}
// 判断文件扩展名是否在office的文件类型中
if(flag) {
try {
// windows中启动OpenOffice的服务
// String command = "D:\\OpenOffice\\program" + "soffice -headless -accept=\"socket,host=127.0.0.1,port=8100;urp;\"-nofirststartwizard";
// Process pro = Runtime.getRuntime().exec(command); // 执行exe文件
// 通过ip地址和端口号连接OpenOffice
OpenOfficeConnection connection = new SocketOpenOfficeConnection("127.0.0.1", 8100);
connection.connect();
// 需要转换的文件路径
File inFile = new File("/usr/local/upload-files/" + newFileName);
// 目标文件 准备存进哪里 转换后的文件路径
File outFile = new File("/usr/local/upload-files/preview_pdf/" + serverFileName + ".pdf");
if (!inFile.exists()) {
LOGGER.info(">>> 找不到源文件 <<<");
} else {
LOGGER.info(">>> 预览源文件名称 " + inFile + " <<<");
}
// 转换
DocumentConverter converter = new OpenOfficeDocumentConverter(connection);
converter.convert(inFile, outFile);
// 关闭连接资源
connection.disconnect();
// 关闭OpenOffice服务的进程
//pro.destroy();
LOGGER.info("转换为pdf成功" + outFile);
resUrl.put("filePreviewAddress", serverFileName + ".pdf");
// -----生成文件预览开始结束
} catch (ConnectException e) {
e.printStackTrace();
LOGGER.error("生成预览文件失败 请检查连接参数", e);
}
}
resUrl.put("fileRecordAddress", newFileName);
res.put("data", resUrl);
return res;
}
}
}
res.put("code", 1);
res.put("msg", "");
LOGGER.info(res.toString());
return res;
}
前端页面
pdf.js下载 https://mozilla.github.io/pdf.js/ 点击下载 选择稳定版Stable(现在为v2.0.943)下载
把官网的web 和 build文件导入到项目
- pdf.js 需要使用 localhost:8080/viewer.html?file=localhost:8080/test.pdf方式进行指定fdf位置
- 当然可以简写为localhost:8080/viewer.html?file=/test.pdf pdf文件是不能跨越的 且文件暴露在地址栏 可通过Nginx解决
- 网络上的解决方案是在外面套一个iframe 可以看看我的解决方案
- 默认的pdf路径viewer.js的4347行 但是此路径并没有相应的方法动态修改 没有办法 想要隐藏地址栏pdf路径并不使用iframe 只能在此变量的最近的方法中进行Ajax了
var defaultOptions = {
cursorToolOnLoad: {
value: 0,
kind: OptionKind.VIEWER
},
defaultUrl: {
value: '/web/test.pdf', // 默认路径
kind: OptionKind.VIEWER
},
Ajax如下 但是此Ajax会请求10次 定义boolean变量在Ajax执行后赋值为false也无法阻止 每次都相当于从新执行此方法 可在后台使用缓存解决此问题 大概在viewer.js的4500多行加入Ajax
_createClass(AppOptions, null, [{
key: 'get',
value: function get(name) {
var defaultOption = defaultOptions[name],
userOption = userOptions[name];
if (userOption !== undefined) {
return userOption;
}
// -----Ajax开始
// 构造一个含有目标参数的正则表达式对象
var reg = new RegExp("(^|&)" + "file_id" + "=([^&]*)(&|$)");
// 匹配目标参数
var r = window.location.search.substr(1).match(reg);
// 返回参数值
if(r != null) {
var file_id = decodeURI(r[2]);
}
if (window.XMLHttpRequest) {
var xhr = new XMLHttpRequest();
} else {
//IE5 & IE6 浏览器专属对象
var xhr = new ActivexObject("Microsoft.XMLHTTP");
}
xhr.open("GET", "/file/preview_url?file_id=" + file_id, true);
xhr.send();
xhr.onreadystatechange = function() {
//readyState = 4 读取请求完成响应就绪
if (xhr.readyState == 4) {
console.log("读取请求完成响应就绪");
//status = 200 正常响应 页面可获得数据
if (xhr.status == 200) {
defaultOptions.defaultUrl.value = xhr.responseText;
console.log(">>> 响应成功 preview pdf <<<" + defaultOptions.defaultUrl.value);
} else {
console.log(xhr.status); // 打印状态码
}
}
}
// -----Ajax结束
return defaultOption !== undefined ? defaultOption.value : undefined;
}
},
此时浏览器请求pdf为 localhost:8080/viewer.html?file_id=1
对应后端为
/** 文件预览 查看pdf */
@GetMapping("preview_url")
@ResponseBody
public String previewUrl(Integer file_id) {
// 获取redis中的数据
String filePreviewUrl = (String) redisTemplate.boundHashOps("filePreviewUrl").get(file_id);
if (null == filePreviewUrl || "".equals(filePreviewUrl.trim())) {
String preFile = fileService.selectPreFile(file_id);
if (null != preFile && !"".equals(preFile.trim())) {
filePreviewUrl = "/usr/local/upload-files/preview_pdf/" + preFile;
redisTemplate.boundHashOps("filePreviewUrl").put(file_id, filePreviewUrl); // 放入缓存
LOGGER.info("数据已放到redis中 --- " + filePreviewUrl);
}
}
return filePreviewUrl;
}
pdf.js禁止打印和下载如下 但是Ctrl+S也可保存此页面但是保存的只是空html 大概在viewer.js的11400多行 注释的为原方法
items.print.addEventListener('click', function () {
// eventBus.dispatch('print', { source: self });
alert("禁止打印")
});
items.download.addEventListener('click', function () {
// eventBus.dispatch('download', { source: self });
alert("禁止下载")
});
pdf.js水印功能如下 大概在viewer.js中10000多行
var textLayer = null;
if (this.textLayerMode !== _ui_utils.TextLayerMode.DISABLE && this.textLayerFactory) {
var textLayerDiv = document.createElement('div');
textLayerDiv.className = 'textLayer';
textLayerDiv.style.width = canvasWrapper.style.width;
textLayerDiv.style.height = canvasWrapper.style.height;
// -----开始
var cover = document.createElement('div');
cover.className = "cover";
var defaultSettings = {
watermark_txt: "金荣翔",
watermark_x: 0,//水印起始位置x轴坐标
watermark_y: 0,//水印起始位置Y轴坐标
watermark_rows: 30,//水印行数
watermark_cols: 100,//水印列数
watermark_x_space: 10,//水印x轴间隔
watermark_y_space: 10,//水印y轴间隔
watermark_color: 'red',//水印字体颜色
watermark_alpha: 0.3,//水印透明度
watermark_fontsize: '20px',//水印字体大小
watermark_font: '微软雅黑',//水印字体
watermark_width: 120,//水印宽度
watermark_height: 80,//水印长度
watermark_angle: 45//水印倾斜度数
};
var oTemp = document.createDocumentFragment();
//获取页面最大宽度
var page_width = parseInt(canvasWrapper.style.width);
//获取页面最大长度
var page_height = parseInt(canvasWrapper.style.height);
// 如果将水印列数设置为0,或水印列数设置过大,超过页面最大宽度,则重新计算水印列数和水印x轴间隔
if (defaultSettings.watermark_cols == 0 || (parseInt(defaultSettings.watermark_x + defaultSettings.watermark_width * defaultSettings.watermark_cols + defaultSettings.watermark_x_space * (defaultSettings.watermark_cols - 1)) > page_width)) {
defaultSettings.watermark_cols = parseInt((page_width - defaultSettings.watermark_x + defaultSettings.watermark_x_space) / (defaultSettings.watermark_width + defaultSettings.watermark_x_space));
defaultSettings.watermark_x_space = parseInt((page_width - defaultSettings.watermark_x - defaultSettings.watermark_width * defaultSettings.watermark_cols) / (defaultSettings.watermark_cols - 1));
}
// 如果将水印行数设置为0,或水印行数设置过大,超过页面最大长度,则重新计算水印行数和水印y轴间隔
if (defaultSettings.watermark_rows == 0 || (parseInt(defaultSettings.watermark_y + defaultSettings.watermark_height * defaultSettings.watermark_rows + defaultSettings.watermark_y_space * (defaultSettings.watermark_rows - 1)) > page_height)) {
defaultSettings.watermark_rows = parseInt((defaultSettings.watermark_y_space + page_height - defaultSettings.watermark_y) / (defaultSettings.watermark_height + defaultSettings.watermark_y_space));
defaultSettings.watermark_y_space = parseInt(((page_height - defaultSettings.watermark_y) - defaultSettings.watermark_height * defaultSettings.watermark_rows) / (defaultSettings.watermark_rows - 1));
}
var x;
var y;
for (var i = 0; i < defaultSettings.watermark_rows; i++) {
y = defaultSettings.watermark_y + (defaultSettings.watermark_y_space + defaultSettings.watermark_height) * i;
for (var j = 0; j < defaultSettings.watermark_cols; j++) {
x = defaultSettings.watermark_x + (defaultSettings.watermark_width + defaultSettings.watermark_x_space) * j;
var mask_div = document.createElement('div');
mask_div.id = 'mask_div' + i + j;
mask_div.className = 'mask_div';
mask_div.appendChild(document.createTextNode(defaultSettings.watermark_txt));
// 设置水印div倾斜显示
mask_div.style.webkitTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
mask_div.style.MozTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
mask_div.style.msTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
mask_div.style.OTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
mask_div.style.transform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
mask_div.style.visibility = "";
mask_div.style.position = "absolute";
// 奇偶行错开,这样水印就不对齐,显的不呆板
if (i % 2 != 0) {
mask_div.style.left = x + 100 + 'px';
} else {
mask_div.style.left = x + 'px';
}
mask_div.style.top = y + 'px';
mask_div.style.overflow = "hidden";
mask_div.style.opacity = defaultSettings.watermark_alpha;
mask_div.style.fontSize = defaultSettings.watermark_fontsize;
mask_div.style.fontFamily = defaultSettings.watermark_font;
mask_div.style.color = defaultSettings.watermark_color;
mask_div.style.textAlign = "center";
mask_div.style.width = canvasWrapper.style.width + 'px';
mask_div.style.height = canvasWrapper.style.height + 'px';
mask_div.style.display = "block";
oTemp.appendChild(mask_div);
}
}
cover.appendChild(oTemp);
// -----结束
if (this.annotationLayer && this.annotationLayer.div) {
div.insertBefore(textLayerDiv, this.annotationLayer.div);
// -----开始
div.appendChild(cover); // 追加
// -----结束
} else {
div.appendChild(textLayerDiv);
// -----开始
div.appendChild(cover); // 追加
// -----结束
}
textLayer = this.textLayerFactory.createTextLayerBuilder(textLayerDiv, this.id - 1, this.viewport, this.textLayerMode === _ui_utils.TextLayerMode.ENABLE_ENHANCE);
// -----开始
var cover = document.getElementsByClassName('cover');
for (var i = 0, len = cover.length; i < len; i++) {
cover[i].style.width = canvasWrapper.style.width;
cover[i].style.height = canvasWrapper.style.height;
}
// -----结束
}
openoffice安装参考 https://fireinjava.iteye.com/blog/1712283 pdf.js添加水印参考 https://www.jianshu.com/p/9f349e41a7b2