公司有需求要实现在线预览excel, PDF, PPT等文档的需求,
后台直接说前端做
他们的逻辑就是这是在前端展示跟后台无关( 这是什么逻辑? 懒人逻辑? )
稍微动点脑子就知道, 前端解析不了文件, 拿流转字符串再去分析字符串( 做个人吧 ),
不同软件Office, wps, 永中, 飞书文档等的格式不一样,根本不可能去解析,
可能存在实现的可能, 但是个无比巨大的工程,
放到前后端扯皮上来说, 后端也可以去解析, 返回对应的数据给前端不是么
多方搜索发现OpenOffice可以做到以上功能, 且支持window和linux
又想到既然有这个套件是不是其实已经有对应的免费的, 开源的项目了呢?
kkFileView完美解解决了我的需求
项目下载下来后首先看看
application.properties
是否有需要改动的
项目启动后直接浏览器访问 8012 端口( 默认是8012 )界面如下
上传列表
更新日志, 讨论评论等
放到自己公司项目当中肯定是不合适的, 所以这部分代码首先就要改掉
更新记录, 评论
获取数据
删掉
再运行进首页就变成如下
改完后的首页
可以满足部署了
接下来就是几个缺省页
1645425578(1).jpg
删改您随意,
然后就是核心代码
1645425884(1).jpg
基本上都在这里可以满足大部分需求更改场景, 复杂的就自己看咯
我这边由于需要加水印, 水印得避免明文传参, 下载等功能, 所以另外包了一层壳, 预览做为iframe嵌套进去,
考虑到性能所以就做成原生项目
具体代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="referrer" content="no-referrer" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta http-equiv="pragma" content="no-cache" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<link rel="shortcut icon" href="https://oss.syounggroup.com/static/project/hermes/favicon.png" type="image/x-icon">
<title>文件预览</title>
<script src="https://image.syounggroup.com/public/yjh-watermark.js"></script>
<script>
/**
* Minified by jsDelivr using Terser v5.3.5.
* Original file: /npm/js-base64@3.6.0/base64.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):function(){const r=e.Base64,o=t();o.noConflict=()=>(e.Base64=r,o),e.Meteor&&(Base64=o),e.Base64=o}()}("undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:this,(function(){"use strict";const e="3.6.0",t="function"==typeof atob,r="function"==typeof btoa,o="function"==typeof Buffer,n="function"==typeof TextDecoder?new TextDecoder:void 0,a="function"==typeof TextEncoder?new TextEncoder:void 0,f=[..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="],i=(e=>{let t={};return e.forEach(((e,r)=>t[e]=r)),t})(f),c=/^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/,u=String.fromCharCode.bind(String),s="function"==typeof Uint8Array.from?Uint8Array.from.bind(Uint8Array):(e,t=(e=>e))=>new Uint8Array(Array.prototype.slice.call(e,0).map(t)),d=e=>e.replace(/[+\/]/g,(e=>"+"==e?"-":"_")).replace(/=+$/m,""),l=e=>e.replace(/[^A-Za-z0-9\+\/]/g,""),h=e=>{let t,r,o,n,a="";const i=e.length%3;for(let i=0;i<e.length;){if((r=e.charCodeAt(i++))>255||(o=e.charCodeAt(i++))>255||(n=e.charCodeAt(i++))>255)throw new TypeError("invalid character found");t=r<<16|o<<8|n,a+=f[t>>18&63]+f[t>>12&63]+f[t>>6&63]+f[63&t]}return i?a.slice(0,i-3)+"===".substring(i):a},p=r?e=>btoa(e):o?e=>Buffer.from(e,"binary").toString("base64"):h,y=o?e=>Buffer.from(e).toString("base64"):e=>{let t=[];for(let r=0,o=e.length;r<o;r+=4096)t.push(u.apply(null,e.subarray(r,r+4096)));return p(t.join(""))},A=(e,t=!1)=>t?d(y(e)):y(e),b=e=>{if(e.length<2)return(t=e.charCodeAt(0))<128?e:t<2048?u(192|t>>>6)+u(128|63&t):u(224|t>>>12&15)+u(128|t>>>6&63)+u(128|63&t);var t=65536+1024*(e.charCodeAt(0)-55296)+(e.charCodeAt(1)-56320);return u(240|t>>>18&7)+u(128|t>>>12&63)+u(128|t>>>6&63)+u(128|63&t)},g=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g,B=e=>e.replace(g,b),x=o?e=>Buffer.from(e,"utf8").toString("base64"):a?e=>y(a.encode(e)):e=>p(B(e)),C=(e,t=!1)=>t?d(x(e)):x(e),m=e=>C(e,!0),U=/[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g,F=e=>{switch(e.length){case 4:var t=((7&e.charCodeAt(0))<<18|(63&e.charCodeAt(1))<<12|(63&e.charCodeAt(2))<<6|63&e.charCodeAt(3))-65536;return u(55296+(t>>>10))+u(56320+(1023&t));case 3:return u((15&e.charCodeAt(0))<<12|(63&e.charCodeAt(1))<<6|63&e.charCodeAt(2));default:return u((31&e.charCodeAt(0))<<6|63&e.charCodeAt(1))}},w=e=>e.replace(U,F),S=e=>{if(e=e.replace(/\s+/g,""),!c.test(e))throw new TypeError("malformed base64.");e+="==".slice(2-(3&e.length));let t,r,o,n="";for(let a=0;a<e.length;)t=i[e.charAt(a++)]<<18|i[e.charAt(a++)]<<12|(r=i[e.charAt(a++)])<<6|(o=i[e.charAt(a++)]),n+=64===r?u(t>>16&255):64===o?u(t>>16&255,t>>8&255):u(t>>16&255,t>>8&255,255&t);return n},E=t?e=>atob(l(e)):o?e=>Buffer.from(e,"base64").toString("binary"):S,v=o?e=>s(Buffer.from(e,"base64")):e=>s(E(e),(e=>e.charCodeAt(0))),D=e=>v(z(e)),R=o?e=>Buffer.from(e,"base64").toString("utf8"):n?e=>n.decode(v(e)):e=>w(E(e)),z=e=>l(e.replace(/[-_]/g,(e=>"-"==e?"+":"/"))),T=e=>R(z(e)),Z=e=>({value:e,enumerable:!1,writable:!0,configurable:!0}),j=function(){const e=(e,t)=>Object.defineProperty(String.prototype,e,Z(t));e("fromBase64",(function(){return T(this)})),e("toBase64",(function(e){return C(this,e)})),e("toBase64URI",(function(){return C(this,!0)})),e("toBase64URL",(function(){return C(this,!0)})),e("toUint8Array",(function(){return D(this)}))},I=function(){const e=(e,t)=>Object.defineProperty(Uint8Array.prototype,e,Z(t));e("toBase64",(function(e){return A(this,e)})),e("toBase64URI",(function(){return A(this,!0)})),e("toBase64URL",(function(){return A(this,!0)}))},O={version:e,VERSION:"3.6.0",atob:E,atobPolyfill:S,btoa:p,btoaPolyfill:h,fromBase64:T,toBase64:C,encode:C,encodeURI:m,encodeURL:m,utob:B,btou:w,decode:T,isValid:e=>{if("string"!=typeof e)return!1;const t=e.replace(/\s+/g,"").replace(/=+$/,"");return!/[^\s0-9a-zA-Z\+/]/.test(t)||!/[^\s0-9a-zA-Z\-_]/.test(t)},fromUint8Array:A,toUint8Array:D,extendString:j,extendUint8Array:I,extendBuiltins:()=>{j(),I()},Base64:{}};return Object.keys(O).forEach((e=>O.Base64[e]=O[e])),O}));
//# sourceMappingURL=/sm/8bca8602e2256d240cef904e1c1df432ccfdd2a2a73f6911c60be79e526e3e1e.map
function getQueryParams(str = location.href) {
var o = {};
// 匹配出所有参数,
var queryArr = str.match(new RegExp('[?&][^?&]+=[^?&]+', 'g'));
if (queryArr) {
// 并去除 ? & 符号 以及#号之后的字符串;
var filterQuery = queryArr.map((v) => v.replace(/\?|\&|\#.*$/g, ''));
if (filterQuery) {
filterQuery.forEach((v) => {
var a = v.split('=');
o[a[0]] = a[1] || '';
});
}
}
return o;
}
function toQueryParams(param, key, encode) {
if (param == null) return '';
var arr = [];
var t = typeof (param);
if (t == 'string' || t == 'number' || t == 'boolean') {
arr.push(key + '=' + ((encode == null || encode) ? encodeURIComponent(param) : param));
} else {
for (var i in param) {
var k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i);
arr.push(toQueryParams(param[i], k, encode));
}
}
return arr.join("&");
}
</script>
<style>
*{padding: 0;margin: 0;}
html, body, iframe{
width: 100%;
height: 100%;
}
#downLoad{
padding: 4px 8px;
color: #fff;
background-color: #5f3bce;
font-size: 14px;
border-radius: 4px;
text-decoration: none;
}
</style>
</head>
<body>
<iframe id="iframe" src="" frameborder="0"></iframe>
<script>(function(){
var baseUrl = 'https://filepreview.xxx.com/onlinePreview'
// var baseUrl = 'http://kelfilepreview.xxx.com:8012/onlinePreview'
var url = getQueryParams()
if (url.url) {
var params = getQueryParams(baseUrl + '?url=' + Base64.decode(url.url))
var keys = Object.keys(params)
// 单纯的预览
if (keys.length === 2 && Object.prototype.hasOwnProperty.call(params, 'url') && Object.prototype.hasOwnProperty.call(params, 'watermarkTxt')) {
params.url = Base64.encode(params.url)
document.getElementById('iframe').src = baseUrl + '?' + toQueryParams(params)
} else {
// 带参数的(加密型文件预览)
var o = {}
for (const key in params) {
if (key != 'url' && key != 'watermarkTxt') {
o[key] = params[key]
}
}
var str = Base64.encode(params.url + '?' + toQueryParams(o, null, false))
document.getElementById('iframe').src = baseUrl + '?url=' + str
}
watermark.init({
// 水印文案
watermark_txt: params.watermarkTxt
});
} else {
confirm('没有获取到预览的文件地址')
}
})()
</script>
</body>
</html>
至此一个前后端融合的可以预览大部分文档的项目就做完了
由于java项目与前端项目是分开部署的, java服务器上没有nginx,所以还得配置下nginx
具体如下
upstream filepreview.com
{
server xx.xx.xx.xx:8012;
}
server {
listen 80;
server_name filepreview.com;
listen 443 ssl;
ssl_certificate xx.com.pem;
ssl_certificate_key xx.com.key;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!RC4:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!EXP:+MEDIUM;
ssl_prefer_server_ciphers on;
if ($scheme = http ) {return 301 https://$host$request_uri;}
location /.git {
deny all;
return 403;
}
location /.svn {
deny all;
return 403;
}
if ( $host ~* "\d+\.\d+\.\d+\.\d+" ) {
return 400;
}
location / {
proxy_pass http://filepreview.com;
access_log off;
log_not_found off;
}
# location / {
# proxy_pass xx.xx.xx.xx:8012;
# access_log off;
# log_not_found off;
# }
location /preview {
alias /app/static/dist/preview;
}
access_log /app/nginx/logs/filepreview.access.log log_access;
}
且与业务和项目完全解耦,面向整个集团服务