问题描述如图:
泛微自带的文档预览由于大小限制,所以很多文档都无法打开,除非买永中或者金山付费的在线预览服务做集成。 不过现在有很多开源的在线预览软件如 onlyoffice 等,也可以实现在线预览,所以只要跟泛微OA做点集成就可以了。
实现方法:
1、泛微OA开发文档下载接口,供onlyoffice下载到本地进行转换预览。
这一步进我原来的文章:泛微ECOLOGY9实现在不登录状态根据文档ID调用文档的接口实现
https://blog.csdn.net/dycp125/article/details/130950347
不过为了满足文档多附件时的下载调用,对代码做了一点修改,调整为根据 docid和imagefileid 获取文档,或者根据 docid 获取单附件文档,或者根据 imagefileid 直接获取图片。
代码如下:
import weaver.conn.RecordSet;
import weaver.file.ImageFileManager;
import weaver.general.BaseBean;
import weaver.general.Util;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.Null;
import java.io.*;
import java.net.URLEncoder;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
/** * 下载和在线打开PDF文件 * 使用说明 http://note.youdao.com/s/77miwS0Q */
public class FileServlet extends HttpServlet {
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("application/x-zip-compressed;charset=utf-8"); // 获取http传过来的参数
String docid = request.getParameter("docid");
String imageid = request.getParameter("imageid");
String method = "";
Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
String sql = "";
if(imageid !="" && imageid != null && docid !="" && docid !=null){
try {
if (!pattern.matcher(docid).matches()) {
docid = com.api.doc.detail.util.DocDownloadCheckUtil.DncodeFileid(docid);
}
} catch (Exception e) {
e.printStackTrace();
}
sql = " select docimagefile.imagefileid as imagefileid,filerealpath,imagefiletype,imagefile.imagefilename,iszip from imagefile,docimagefile "
+ " where imagefile.imagefileid =docimagefile.imagefileid and docimagefile.docid='" + docid + "' and docimagefile.imagefileid='"+imageid+"'";
}else if((imageid !="" && imageid !=null) && (docid =="" || docid == null)){
try {
if (!pattern.matcher(imageid).matches()) {
imageid = com.api.doc.detail.util.DocDownloadCheckUtil.DncodeFileid(imageid);
}
} catch (Exception e) {
e.printStackTrace();
}
sql = "select imagefileid,filerealpath,imagefiletype,imagefilename,iszip from imagefile "
+ " where imagefileid='" + imageid + "'";
}else if((imageid =="" || imageid ==null) && (docid !="" && docid != null)){
try {
if (!pattern.matcher(docid).matches()) {
docid = com.api.doc.detail.util.DocDownloadCheckUtil.DncodeFileid(docid);
}
} catch (Exception e) {
e.printStackTrace();
}
sql = " select docimagefile.imagefileid as imagefileid,filerealpath,imagefiletype,imagefile.imagefilename,iszip from imagefile,docimagefile "
+ " where imagefile.imagefileid =docimagefile.imagefileid and docimagefile.docid='" + docid + "'";
}
RecordSet rs = new RecordSet();
rs.execute(sql);
if (rs.first()) { // 设置http的头部,可设置下载的zip的文件名
servletOS (response,rs.getString("imagefilename"),rs.getString("filerealpath"),rs.getString("imagefileid"), rs.getInt("iszip"));
}
}
protected void servletOS (HttpServletResponse response,String imagefilename,String filerealpath, String imagefileid, Integer iszip) throws IOException {
response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(imagefilename, "utf-8") + "\"");
new BaseBean().writeLog("iszip==>" + iszip + ",filerealpath=====>" + filerealpath);
ZipFile zf = null;
ZipInputStream zin = null;
ZipEntry ze = null;
InputStream is = null;
File file = null;
try {
if (1 == iszip) {
new BaseBean().writeLog("压缩文件");
zf = new ZipFile(filerealpath);
zin = new ZipInputStream(new BufferedInputStream(new FileInputStream(filerealpath)));
ze = zin.getNextEntry();
is = zf.getInputStream(ze);
} else {
ImageFileManager imageFileManager = new ImageFileManager();
imageFileManager.getImageFileInfoById(Util.getIntValue(imagefileid, 0));
is = imageFileManager.getInputStream();
new BaseBean().writeLog("非压缩文件");
}
// URLEncoder.encode(rs.getString("imagefilename"), "utf-8");
} catch (Exception e) {
e.printStackTrace();
}
byte[] buf = new byte[4096]; /* 创建输出流 */
ServletOutputStream servletOS = response.getOutputStream();
int readLength;
while (((readLength = is.read(buf)) != -1)) {
servletOS.write(buf, 0, readLength);
}
servletOS.flush();
servletOS.close();
}
}
2、泛微OA开发文档回调接口,供onlyoffice将预览的文档修改保存后或关闭时回传文档到OA。
这个接口可以跟步骤1一样,使用 servlet 的方式开发,或都使用 泛微 API接口的方式开发发布。这里使用泛微API接口方式开发发布。
** 注意接口需要放在ecology.classbean.com.api的路径下面 **
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import weaver.docs.webservices.DocAttachment;
import weaver.docs.webservices.DocInfo;
import weaver.docs.webservices.DocServiceImpl;
import weaver.hrm.User;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.io.*;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
@Path("/of")
public class of {
/**
* 在线编辑器回调操作
*/
private DocServiceImpl service = new DocServiceImpl();
@POST
@Path("/callback")
@Produces(MediaType.TEXT_PLAIN)
public String savedoc(@Context HttpServletRequest request, @Context HttpServletResponse response, @QueryParam("docid") int docid,@QueryParam("imagefileid") int imagefileid,@QueryParam("userid") int userid) throws Exception {
User user=User.getUser(userid,0);
DocInfo doc= service.getDocByUser(docid,user,"");
DocAttachment[] das = doc.getAttachments();
int k = 0;
for (int i = 0; i < das.length; i++){ //多个附件时,选指定的附件
int imageid = das[i].getImagefileid();
if(imageid==imagefileid){
k=i;
break;
}
}
DocAttachment da=das[k];
//得到附件内容
//得到附件在服务器上的绝对路径
String filepath = da.getFilerealpath();
Map<String, Object> res = new HashMap<>();
Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\\A");
String body = scanner.hasNext() ? scanner.next() : "";
JSONObject jsonObj = JSON.parseObject(body);
int status = (Integer) jsonObj.get("status");
if(status == 2 || status == 3)
{
String downloadUri = (String) jsonObj.get("url");
URL url = new URL(downloadUri);
java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
InputStream stream = connection.getInputStream();
File savedFile = new File("d:\\WEAVER\\document\\temp\\"+da.getFilename()); //将onlyoffice回调回来的文档保存到OA服务器指定的临时目录
try (FileOutputStream out = new FileOutputStream(savedFile)) {
int read;
final byte[] bytes = new byte[1024];
while ((read = stream.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
out.flush();
}
connection.disconnect();
//onlyoffice 文件回调保存到临时文件夹内结束。
// 推断文件夹或文件是否存在
if (savedFile.exists() && savedFile.isFile()) { // 不存在返回 false
// 推断是否为文件
savedFile.delete();
}
}
res.put("error","0") ;
return JSONObject.toJSONString(res);
}
}
由于我这里只需要文档预览,不需要文档在onlyoffice中做编辑,然后上传回OA做文档更新,所以对于回调回来的文档不做更新OA操作,直接做了删除。如果有需要做更新操作的,可以在删除临时文件前增加更新上传到OA的代码。
开放白名单,使OA在不登录状态下也能访问这个接口
路径:/ecology/WEB-INF/prop/weaver_session_filter.properties,在unchecksessionurl= 尾部添加刚刚开发的接口 /api/of/callback;修改完保存,重启服务。
3、搭建onlyoffice服务
onlyoffice搭建不是本文的重点,所以就忽略了。
4、泛微OA增加onlyoffice预览页面,并在OA文档查看页面增加按钮调用预览页面。
4.1、新建预览页面
在OA ecology 下建一个onlyoffice.jsp文件。注意文件中的SQL是 MSSQL,如果使用ORACLE的数据库,则请自行修改rs2中不支持的函数,如果不修改预览页面会出错空白显示。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@page import="weaver.conn.RecordSet"%>
<%@ page import="weaver.general.Util,weaver.conn.RecordSet" %>
<%@ page import="java.util.Map"%>
<%@ page import="java.util.HashMap"%>
<%@ page import="net.sf.json.JSONObject"%>
<%
String docid = request.getParameter("docid");
String imagefileid = request.getParameter("imagefileid");
String userid = request.getParameter("userid");
RecordSet rs = new RecordSet();
rs.executeSql("select loginid,lastname from HrmResource where id='"+userid+"'");
rs.next();
RecordSet rs1 = new RecordSet();
rs1.executeSql(" select right(imagefilename,suffixlen)as suffix,imagefilename from (select CHARINDEX('.',REVERSE(imagefile.imagefilename))-1 as suffixlen,docimagefile.docid,docimagefile.imagefileid as imagefileid,filerealpath,imagefiletype,imagefile.imagefilename,iszip from imagefile,docimagefile where imagefile.imagefileid =docimagefile.imagefileid )as PP where suffixlen>=3 and docid='"+docid+"' and imagefileid='"+imagefileid+"'");
rs1.next();
%>
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>OnlyOffice转换查看</title>
<style>
.app {
display: flex;
height: calc(100vh - 10px);
background-color: bisque;
}
</style>
</head>
<body>
<div class="app">
<div id="placeholder" class="placeholder">
</div>
</div>
<script type="text/javascript" src="http://onlyoffice服务器地址/web-apps/apps/api/documents/api.js"></script>
<script src="jquery.js"></script>
<script type="text/javascript">
function handleDocType(fileType) {
let docType = '';
let fileTypesDoc = [
'doc', 'docm', 'docx', 'dot', 'dotm', 'dotx', 'epub', 'fodt', 'htm', 'html', 'mht', 'odt', 'ott', 'pdf', 'rtf', 'txt', 'djvu', 'xps'
];
let fileTypesCsv = [
'csv', 'fods', 'ods', 'ots', 'xls', 'xlsm', 'xlsx', 'xlt', 'xltm', 'xltx'
];
let fileTypesPPt = [
'fodp', 'odp', 'otp', 'pot', 'potm', 'potx', 'pps', 'ppsm', 'ppsx', 'ppt', 'pptm', 'pptx'
];
if (fileTypesDoc.includes(fileType)) {
docType = 'word';
}
if (fileTypesCsv.includes(fileType)) {
docType = 'cell';
}
if (fileTypesPPt.includes(fileType)) {
docType = 'slide';
}
return docType;
};
var config = {
"height": "100%",
"width": "100%",
"documentType": handleDocType("<%=rs1.getString("suffix")%>"),
"type": "desktop",
"document": {
"lang": "zh-CN",
"fileType": "<%=rs1.getString("suffix")%>",
"key": "<%=imagefileid%>", //唯一标识
"title": "<%=rs1.getString("imagefilename")%>",
"url": "http://OA服务器地址/file/fileservlet?docid=<%=docid%>&imageid=<%=imagefileid%>", //这里填写文档的url路径
"permissions": {
"comment": false, //定议是否注释文档
"chat": true, //定义“聊天”菜单按钮是显示还是隐藏
"copy": false, //是否允许您将内容复制到剪贴板。默认值为true。
"deleteCommentAuthorOnly": false, //定义用户是否能删除他人的评论。默认值为false。
"download": false, //定义是否可以下载文档或仅在线查看或编辑文档。如果下载权限设置为“false”下载为菜单选项将没有。默认值为true。
"edit": false, //文件是否可以编辑,false时文件不可编辑
"editCommentAuthorOnly": false, //定义用户是否能编辑他人的评论。默认值为false。
"fillForms": false, //定义是否能在文档中填充表单
"modifyContentControl": false, //定义是否可以更改内容控件设置。仅当mode参数设置为edit时,内容控件修改才可用于文档编辑器。默认值为true。
"modifyFilter": false, //定义过滤器是否可以全局应用(true)影响所有其他用户,或局部应用(false),即仅适用于当前用户。如果将mode参数设置为edit,则过滤器修改仅对电子表格编辑器可用。默认值为true。
"print": false, //定义文档是否能打印
"protect": false,
"review": true, //是否显示审阅文档菜单
"reviewGroups": null,
"commentGroups": {},
"userInfoGroups": null
}
},
"editorConfig": {
"lang": "zh", //语言环境
"mode": "view", //文档操作模式 view 视图模式不可编辑 edit 编辑模式可编辑文档
"plugins": {
"autostart": [
"asc.{7470f8ef-bcb4-4f07-8887-6ebf973075cf}",
],
"pluginsData": []
},
"callbackUrl": "http://OA服务器地址/api/of/callback?docid=<%=docid%>&imagefileid=<%=imagefileid%>&userid=<%=userid%>", //保存文件时的回调地址
"customization": {
//匿名访问
"anonymous": {
"request": true,
"label": "Guest",
},
//设置logo
"logo": {
"image": "",
"imageEmbedded": "",
"url": "",
},
"features": {
"spellcheck": false, //定义在加载编辑器时是否自动打开或关闭拼写检查器
},
"about": false, //关于
"hideRulers": true, //关闭标尺
"comments": false, //关闭评论
},
"user": {
"group": "",
"id": "<%=rs.getString("loginid")%>",
"name": "<%=rs.getString("lastname")%>",
}
},
}
var docEditor = new DocsAPI.DocEditor("placeholder", config);
</script>
</body>
</html>
到这一步就可以测试预览服务是否正常了。
直接在浏览器输入这个预览页面的地址如:
http://OA服务器地址/onlyoffice.jsp?docid=53333&imagefileid=623445&userid=11
传入三个参数 docid 文档ID, imagefileid 文档对应的附件ID,userid 当前操作文档的用户ID
确保文档可以被正常转换加载并显示,如。
4.2、在OA文档查看页面增加一下第三方查看按钮
如图:
点击该按钮,自动跳转到4.1步骤的页面中。
具体实现如下:
在ecode中 新建文件夹,并新增一个 init.js 文件,并设置为 前置加载。
具体代码如下:
var k = 0;
ecodeSDK.overwritePropsFnQueueMapSet('WeaReqTop',{ //复写WeaReqTop组件
fn:(newProps)=>{
if (!ecodeSDK.checkLPath('/spa/document/index2file.jsp#/main/document/fileView')) return;
var user_params = (typeof(global.localStorage.ecode_params)==='string') ? JSON.parse(global.localStorage.ecode_params) : global.localStorage.ecode_params;
var userid = user_params._user.id; //获取当前用户ID
function getQueryVariable(variable)
{
var query = window.location.href.split('?')[1];
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
var docid=getQueryVariable("id"); //获取URL中docid
var imagefileid = getQueryVariable("imagefileId"); //获取URL中imagefileId
if(k == 0 ){ //只运行一次,添加一次按钮。
var conent = '<span style="display: inline-block; line-height: 28px; vertical-align: middle; margin-left: 10px;"><button ecid="'+docid+'"'+
'type="button" οnclick="window.location.href=\'../../onlyoffice.jsp?docid='+docid+'&imagefileid='+imagefileid+'&userid='+userid+'\'" class="ant-btn ant-btn-primary"><span>第三方打开</span></button></span>';
setTimeout(() => { //一开始加载时取不到元素,需要延迟一秒。
$(".ant-btn-primary").parent().parent().eq(0).prepend(conent);
}, 1000)
}
k=k+1;
},
order:1,
desc:'添加第三方打开按钮'
});
总结
整体流程就是 当文档 碰到过大打不开时,点 第三方打开按钮 跳转到 onlyoffice 预览页面, 页面通过参数获取需要调用的 docid,imagefileid及userid, 然后根据参数去OA文档下载接口下载文件到onlyoffice服务器进行转换并显示出来。当浏览结束后关闭onlyoffice预览页面,5秒内触发 callback 回调接口,接文档回传到OA指定临时目录中,然后直接删除该保存下来的临时文件,结束。