Java 集成 onlyoffice 预览文件功能
Java 集成 onlyoffice 预览文件功能
准备工作,安装 部署好 onlyoffice 服务,部署时修改密钥为标准的密钥。
符合RFC 7518 规范的密钥。
引入一个用于生成 jwt 令牌的 jar
我这里是用的 jjwt(无法使用长度不足32位的密钥,32*8=256)
<dependencies><!-- jjwt --><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.13.0</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.13.0</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.13.0</version><scope>runtime</scope></dependency>
</dependencies>
官方示例代码的jar 为 prime-jwt;联系官方开发确认是 prime-jwt-1.3.1.jar
<dependency><groupId>com.inversoft</groupId><artifactId>prime-jwt</artifactId><version>1.3.1</version>
</dependency>
使用 prime-jwt 测试结果并不理想,虽然支持非标准密钥,但是生成后的令牌 未能通过onlyoffice 服务器校验,
提供的js 示例 支持非标准密钥可以通过校验。
验证 html
使用 js 示例 验证
<!DOCTYPE html>
<html style="height: 100%;" lang="">
<head><title>ONLYOFFICE 官方示例验证</title><meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body style="height: 100%; margin: 0;">
<div style="height: 100%"><div id="placeholder"></div>
</div>
<script type="text/javascript" src="https://you.onlyService.com/web-apps/apps/api/documents/api.js"></script>
<script>// 封装初始化逻辑async function initEditor() {const config = {"document": {"title": "测试onlyOffice.docx",//不同的文件不能使用相同的key"key": "690acb4889e2ec096645ddf8","fileType": "docx","url": "https://you.file.com/测试onlyOffice.docx"},"documentType": "word","editorConfig": {"coEditing": {"mode": "strict","change": false},"mode": "view",//指定编辑器为中文"lang": "zh"},"width": "100%","height": "100%"};try {// 使用安全的UTF-8编码生成JWTconfig.token = await createJWT(config, 'W0fZRKvsq2YGS4EiUtP8wvd2PEfqHe11');window.docEditor = new DocsAPI.DocEditor("placeholder", config);} catch (error) {console.error("初始化失败:", error);alert("编辑器初始化失败: " + error.message);}}// 修复的JWT生成函数(支持UTF-8字符)async function createJWT(json, secret) {const header = {typ: "JWT",alg: "HS256"};// 使用TextEncoder进行UTF-8编码const encoder = new TextEncoder();const headerData = encoder.encode(JSON.stringify(header));const payloadData = encoder.encode(JSON.stringify(json));// Base64编码(URL安全)const base64EncodeURL = bytes => {return btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');};const encodedHeader = base64EncodeURL(headerData);const encodedPayload = base64EncodeURL(payloadData);// 生成签名const algorithm = {name: "HMAC", hash: "SHA-256"};const key = await crypto.subtle.importKey("raw",encoder.encode(secret),algorithm,false,["sign"]);const signature = await crypto.subtle.sign(algorithm.name,key,encoder.encode(`${encodedHeader}.${encodedPayload}`));return `${encodedHeader}.${encodedPayload}.${base64EncodeURL(new Uint8Array(signature))}`;}// 启动初始化initEditor();
</script>
</body>
</html>
使用其他语言生成的密钥验证
<!DOCTYPE html>
<html style="height: 100%;">
<head><title>ONLYOFFICE Api Documentation</title>
</head>
<body style="height: 100%; margin: 0;">
<div id="placeholder" style="height: 100%"></div>
<script type="text/javascript" src="https://you.onlyService.com/web-apps/apps/api/documents/api.js"></script>
<script type="text/javascript">//一般为调用后端接口,由后端生成 config,之后 用config 生成令牌,并将令牌 设置到 config.token。const config = {"editorConfig": {"mode": "view","coEditing": {"mode": "strict","change": false},//指定编辑器为中文"lang": "zh"},"documentType": "word","document": {"title": "测试onlyOffice.docx",//不同的文件不能使用相同的key"key": "690acb4889e2ec096645ddf8","fileType": "docx","url": "https://you.file.com/测试onlyOffice.docx"},"width": "100%","height": "100%","token": ""}window.docEditor = new DocsAPI.DocEditor("placeholder", config);</script>
</body>
</html>
java 代码
controller
@PostMapping("onlyForm/{id}")public R<SysFile> onlyForm(@PathVariable("id") String id) {return sysFileService.onlyForm(id);}
service
@Overridepublic R<SysFile> onlyForm(String id) {SysFile file = super.findOne(id);if (file == null) {return R.ok();}//生成configMap<String, Object> config = getConfig(file);if(config!=null){//生成令牌String token = OnlyOfficeConfig.getToken(config);config.put("token", token);file.setConfig(config);}return R.ok(file);}public Map<String, Object> getConfig(SysFile file) {//文件类型String fileType = file.getFileType();if (StringUtils.isBlank(fileType)) {return null;}//根据文件类型获取 only中的类型String documentType = OnlyOfficeConfig.getDocumentType(fileType);if (StringUtils.isBlank(documentType)) {return null;}fileType = fileType.substring(1);if (StringUtils.isBlank(fileType)) {return null;}String srcName = file.getSrcName();String url = OnlyOfficeConfig.servicePath + file.getFileurl();Map<String, Object> customClaims = new HashMap<>();Map<String, Object> document = new HashMap<>();customClaims.put("document", document);document.put("key", file.getId());document.put("fileType", fileType);document.put("title", srcName);document.put("url", url);customClaims.put("documentType", documentType);Map<String, Object> editorConfig = new HashMap<>();customClaims.put("editorConfig", editorConfig);Map<String, Object> coEditing = new HashMap<>();editorConfig.put("coEditing", coEditing);coEditing.put("mode", "strict");coEditing.put("change", false);editorConfig.put("mode", "view");customClaims.put("width", "100%");customClaims.put("height", "100%");editorConfig.put("region","china");//重要:指定编辑器页面为中文editorConfig.put("lang","zh");return customClaims;
}
OnlyOfficeConfig 类
/* version 1.8 */
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;
import java.util.Map;/*** only office config**/
public class OnlyOfficeConfig {private static final Logger log = LoggerFactory.getLogger(OnlyOfficeConfig.class);/*** 密钥*/public static String secret;/*** 我方服务器请求路径*/public static String servicePath;/*** jjwt key*/public static Key key;/*** 生成 jjwt 的key*/public static void generateKey() {key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));}/*** 获取token* @param customClaims 请求对象* @return token*/public static String getToken(Map<String, Object> customClaims) {String realName = "获取登录用户信息";String token = Jwts.builder().issuer(realName) // 用户
// .subject("") // 主题
// .audience() // 受众配置
// .add("mobile-app") // 添加单个受众
// .add("web-client") // 添加多个受众
// .and() // 返回父对象继续配置.expiration(new Date(System.currentTimeMillis() + 3600000)) // 1小时过期.notBefore(new Date()) // 立即生效.issuedAt(new Date()) // 签发时间//.id("dfadadsffasdf") // 唯一令牌ID// 自定义声明.claims(customClaims) // 批量添加自定义声明// 签名配置.signWith(key).compact();log.info("token:{}",token);return token;}public static String getDocumentType(String fileType) {//fileType = fileType.toLowerCase();switch (fileType) {case ".doc":case ".docm":case ".docx":case ".dot":case ".dotm":case ".dotx":case ".epub":case ".fb2":case ".fodt":case ".htm":case ".html":case ".mht":case ".mhtml":case ".odt":case ".ott":case ".pages":case ".rtf":case ".stw":case ".sxw":case ".txt":case ".wps":case ".wpt":case ".xml":return "word";case ".csv":case ".et":case ".ett":case ".fods":case ".numbers":case ".ods":case ".ots":case ".sxc":case ".xls":case ".xlsb":case ".xlsm":case ".xlsx":case ".xlt":case ".xltm":case ".xltx"://case ".xml":return "cell";case ".dps":case ".dpt":case ".fodp":case ".key":case ".odp":case ".otp":case ".pot":case ".potm":case ".potx":case ".pps":case ".ppsm":case ".ppsx":case ".ppt":case ".pptm":case ".pptx":case ".sxi":return "slide";case ".djvu":case ".docxf":case ".oform":case ".oxps":case ".pdf":case ".xps":return "pdf";case ".vsdm":case ".vsdx":case ".vssm":case ".vssx":case ".vstm":case ".vstx":return "diagram";default:return null;}}
}
vue 代码示例
点击预览,跳转单页面
单页面内容
<template><!-- 这里的高度需要指定,不然高度会有问题 --><div style="height: 100%;"><div id="placeholder"></div></div>
</template><script>export default {name: '',data() {return {id:""}},mounted() {const script = document.createElement('script');script.src = 'https://you.onlyService.com/web-apps/apps/api/documents/api.js';document.head.appendChild(script);this.id = this.$route.query.id;this.initDetail();},methods: {initDetail() {if (!this.id) return;//后端接口let url = `onlyForm${this.id}`this.$post(url).then(result => {if ( result.data && result.data.config) {setTimeout(function () {window.docEditor = new DocsAPI.DocEditor("placeholder", result.data.config);}, 1000)} else {this.$message.warning("当前文件不支持预览!");}});},}}
</script><style>
</style>
关于内网判断
添加一个接口,添加一个只能内网访问的域名,需要 https;将域名指向这个接口
在网关中 添加
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, @NotNull FilterChain filterChain) throws IOException {String requestURI = request.getRequestURI();if (requestURI.startsWith("特定接口")) {response.setHeader("Access-Control-Allow-Origin", "你的web域名");response.setHeader("Access-Control-Allow-Credentials", "true");}//也可以校验 originString origin = request.getHeader("Origin");//判断请求是否来自特定域名 之后再设置 Access-Control-Allow-Origin
}
