通过阅读本文,了解如何为 Confluence 与 ONLYOFFICE 创建集成插件以在 Confluence 中添加 DOCX、XLSX 与 PPTX 编辑功能

在线文档编辑功能可作为数字化协同工作区的宝贵补充,为此您还可使用开源工具。在本文中,我们将向您展示如何通过集成 ONLYOFFICE 文档来在 Confluence(Atlassian 推出的网页端企业 Wiki)中引入文档编辑功能

我们将创建一个集成应用程序(插件)来发挥沟通 Confluence 与 ONLYOFFICE 的桥梁作用。在其帮助下,用户将可在 Confluence 中编辑 DOCX、XLSX、PPTX 和其他办公文件

安装 ONLYOFFICE 文档

在此过程中,我们将需要可正常工作的 ONLYOFFICE 文档实例。其已被打包为了文档服务器,所以我们将在本文中使用此说法

您可使用 Docker 来进行安装,或选择其他安装选项

docker run -i -t -d -p 8080:80 onlyoffice/documentserver

本行代码将安装免费社区版。其中包含文档、电子表格与演示文稿编辑器,此外还有集成示例(一个简单的文档管理系统),其中演示了集成的各种功能,允许您在将编辑器集成至应用之前对其进行测试。

集成测试示例使用 Node.js 进行构建。您可查看其他语言的选项,包括 Java 测试示例。

现在,我们还是回到创建将编辑器连接至 Confluence 的插件上来。

使用 Atlassian SDK

在进行开发工作之前,我们需要安装 Atlassian SDK。基本上来说,这一 SDK 就是 Maven 的封装器。您可借助它来创建插件主体,然后在运行中的 Atlassian 应用程序中对其进行测试。

如需进行安装,可参考 WindowsLinux 或 Mac 的官方安装指南

之后我们将为 Confluence 创建一个插件主体。打开命令行并运行 atlas-create-confluence-plugin。在创建简单插件前,这一工具将要求您提供一些细节

您也可以在 IDE 中打开插件。部分 IDE 可能需要进行额外配置

虽然在对 VSCode 进行配置方面没有太多说明,但相关工作非常简单:您只需将其指向 SDK 自带的 Maven 即可。在 .vscode 文件夹中创建 settings.json并填入以下内容:

{"maven.executable.path": "C:\\Applications\\Atlassian\\atlassian-plugin-sdk-8.0.16\\apache-maven-3.5.4\\bin\\mvn\","java.configuration.maven.userSettings": "C:\\Applications\\Atlassian\\atlassian-plugin-sdk-8.0.16\\apache-maven-3.5.4\\conf\\settings.xml"}

还要注意的是,具体路径可能会因为 SDK 的安装路径不同而有所区别。

使用 UI

借助 Atlassian 应用的 UI,您可实现很多功能,但是现在我们只需创建一个编辑按钮和用于加载编辑器的页面即可。

添加按钮非常简单。实际上,您甚至都不需要编写代码(除非按钮背后还有额外的逻辑)。基本上而言,Atlassian 应用会使用一个 XML 文件来声明插件的内容。文件位于:src\main\resources\atlassian-plugin.xml。此外还有助于让我们了解应用会对哪些 Java 类进行实例化。

(小提醒:我们将在代码中排除所有 import 行以节省空间)

如需添加按钮,我们只需添加这些代码即可:

<web-item key="onlyoffice-doceditor" name="Link for the attachment editing" section="system.attachment" weight="9">condition class="onlyoffice.IsOfficeFileAttachment"><param name="forEdit">;true</param>;</condition><description>The link and the text for it to open the document which is available for editing.</description><label key="onlyoffice.connector.editlink"/><link><![CDATA[/plugins/servlet/onlyoffice/doceditor?attachmentId=$attachment.id]]></link><styleClass>onlyoffice-doceditor</styleClass>
</web-item>

下面就来看看上面几行代码中的参数。为此我们也准备了一份详尽的官方文档,记得去看看。

大部分参数都无需过多解释。section 属性用于声明我们希望建立链接的位置<condition> 元素用于声明对展示链接的附件进行过滤的 Java 类。

基本上而言,这里的条件将用于检查用户的访问权限、最大文件大小以及文件扩展名。如果您对于其工作原理比较感兴趣,可在这里找到相关代码。

当然,我们需要添加的不仅仅是编辑按钮。此外还有用于查看文档和将其转换为不同格式的按钮,但主要的流程还是相同的。

接着我们来看看 <link> 元素。这里使用附件 id 参数将其导向 Servlet(链接如下:confluence/servlet/attachment_Id)。Servlet 将对用户请求进行处理并返回需要在编辑器中进行加载的页面。

Servlet 本质上是继承自 javax.servlet.http.HttpServlet的类,但需对其 doGet 方法进行重写。这里我们还是保持简洁,暂时不去深究实现细节。

public class OnlyOfficeEditorServlet extends HttpServlet {private static final Logger log = LogManager.getLogger("onlyoffice.OnlyOfficeEditorServlet");@Overridepublic void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {return VelocityUtils.getRenderedTemplate("templates/editor.vm", defaults);}}

我们要做的唯一一件事是渲染一个 Velocity 模板。所以:src\main\resources\templates\editor.vm

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN” "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml\>
<head><meta http-equiv="Content-Type" content="text/html; charset=ANSI" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width" /><title>${docTitle} - ONLYOFFICE</title><link rel="icon" href="/favicon.ico" type="image/x-icon" /> <style type="text/css"> html { height: 100%; width: 100%; }body { background: #fff; color: #333; font-family: Arial, Tahoma, sans-serif;font-size: 12px; font-weight: normal; text-decoration: none;height: 100%; margin: 0; overflow-y: hidden; padding: 0;}.form { height: 100%; }div { margin: 0; padding: 0; } </style>
</head><body><div class="form"><div id="iframeEditor"></div></div>
</body>
</html> 

在模板中将有一个 JavaScript 脚本用于在编辑器中加载页面。您可能会注意到这里有多个 `${variables}`,我们会在稍后提供相关信息。为了让 Servlet 能够正常工作,我们需要让 Atlassian 知晓其存在。所以,这里我们需要对于 atlassian-plugin.xml. 进行一些修改。

<servlet key="OnlyOfficeDocEditor" class="onlyoffice.OnlyOfficeEditorServlet" name="Document Editor"><description>The fully functional editor for most known formats of text documents, spreadsheets and presentations used to open these types of documents for editing or preview.</description><url-pattern>/onlyoffice/doceditor</url-pattern></servlet>

Servlet 和插件配置

此时,我们马上就能去打开文档进行查看了。现在我们还需要为页面模板提供变量。其中一个将包含指向文档服务器的 URL,这里最好不要直接写死在代码中。幸运的是,我们可以借助一个很棒的接口来对插件设置项与 UI 进行操作

下面我们就来创建另一个配置 Servlet(别忘了将其添加至 atlassian-plugin.xml 中)。

public class OnlyOfficeConfServlet extends HttpServlet {@ComponentImportprivate final UserManager userManager;@ComponentImportprivate final PluginSettingsFactory pluginSettingsFactory;@Injectpublic OnlyOfficeConfServlet(UserManager userManager, PluginSettingsFactory pluginSettingsFactory) {this.userManager = userManager;this.pluginSettingsFactory = pluginSettingsFactory;}private static final Logger log = LogManager.getLogger("onlyoffice.OnlyOfficeConfServlet");private static final long serialVersionUID = 1L;private String AppendSlash(String str) {if (str == null || str.isEmpty() || str.endsWith("/"))return str;return str + "/";}private String getBody(InputStream stream) {Scanner scanner = null;Scanner scannerUseDelimiter = null;try {scanner = new Scanner(stream);scannerUseDelimiter = scanner.useDelimiter("\\A");return scanner.hasNext() ? scanner.next() : "";} finally {scannerUseDelimiter.close();scanner.close();}}
}

这里我们有两种实用方法,代码本身已足够简单明了。我们还需要另外两方法。

第一个是 doGet。用于获取当前设置并将其提供给模板。

@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String username = userManager.getRemoteUsername(request);if (username == null || !userManager.isSystemAdmin(username)) {SettingsManager settingsManager = (SettingsManager) ContainerManager.getComponent("settingsManager");String baseUrl = settingsManager.getGlobalSettings().getBaseUrl();response.sendRedirect(baseUrl);return;}PluginSettings pluginSettings = pluginSettingsFactory.createGlobalSettings();String apiUrl = (String) pluginSettings.get("onlyoffice.apiUrl");response.setContentType("text/html;charset=UTF-8");PrintWriter writer = response.getWriter();Map<String, Object> contextMap = MacroUtils.defaultVelocityContext();contextMap.put("docserviceApiUrl", apiUrl);writer.write(getTemplate(contextMap));
}private String getTemplate(Map<String, Object> map) throws UnsupportedEncodingException {return VelocityUtils.getRenderedTemplate("templates/configure.vm", map);
}

第二个是 doPost。该方法将接收 JSON 对象,对其进行解析并覆盖当前设置。

@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String username = userManager.getRemoteUsername(request);if (username == null || !userManager.isSystemAdmin(username)) {response.setStatus(HttpServletResponse.SC_FORBIDDEN);return;}String body = getBody(request.getInputStream());if (body.isEmpty()) {response.setStatus(HttpServletResponse.SC_BAD_REQUEST);return;}String apiUrl;try {JSONObject jsonObj = new JSONObject(body);apiUrl = AppendSlash(jsonObj.getString("apiUrl"));} catch (Exception ex) {StringWriter sw = new StringWriter();PrintWriter pw = new PrintWriter(sw);ex.printStackTrace(pw);String error = ex.toString() + "\n" + sw.toString();log.error(error);response.setStatus(HttpServletResponse.SC_BAD_REQUEST);response.getWriter().write("{\"success\": false, \"message\": \"jsonparse\"}");return;}PluginSettings pluginSettings = pluginSettingsFactory.createGlobalSettings();pluginSettings.put("onlyoffice.apiUrl", apiUrl);response.getWriter().write("{\"success\": true}");
}

在这两种方法中,我们还会检查用户是否有权访问这些设置。实际上,我们还会检查与文档服务器之间的连接,以便对潜在问题进行识别。完整代码可在此处查看。

我们还能在 Atlassian 插件管理页面中添加一个配置按钮。只需将此行代码添加至 atlassian-plugin.xml 中的 <plugin-info> 元素内即可

<param name="configure.url">/plugins/servlet/onlyoffice/configure</param>

对了,别忘记 Velocity 模板。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"><head><title>ONLYOFFICE</title><meta name="decorator" content="atl.admin" /></head><body><div id="onlyofficeMsg"></div><form id="onlyofficeConf" class="aui top-label">          <h3>$i18n.getText('onlyoffice.configuration.doc-section')</h3><div class="field-group"><label for="apiUrlField">$i18n.getText('onlyoffice.configuration.doc-url')</label><input type="text" id="apiUrlField" value="${docserviceApiUrl}" name="apiUrlField" class="text onlyoffice-tooltip" title="$i18n.getText('onlyoffice.configuration.doc-url-tooltip')"></div><div class="field-group"><input id="onlyofficeSubmitBtn" type="submit" value="$i18n.getText('onlyoffice.configuration.save')" class="button"></div></form></body>
</html> 

下面让我们返回编辑器 Servlet 并对其进行修改。

public class OnlyOfficeEditorServlet extends HttpServlet {@ComponentImportprivate final LocaleManager localeManager;private final UrlManager urlManager;@Injectpublic OnlyOfficeEditorServlet(LocaleManager localeManager, UrlManager urlManager) {        this.urlManager = urlManager;this.localeManager = localeManager;}private static final Logger log = LogManager.getLogger("onlyoffice.OnlyOfficeEditorServlet");private static final long serialVersionUID = 1L;private Properties properties;@Overridepublic void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {if (!AuthContext.checkUserAuthorisation(request, response)) {return;}String apiUrl = urlManager.getPublicDocEditorUrl();if (apiUrl == null || apiUrl.isEmpty()) {apiUrl = "";}ConfigurationManager configurationManager = new ConfigurationManager();properties = configurationManager.GetProperties();String fileUrl = "";String key = "";String fileName = "";String errorMessage = "";ConfluenceUser user = null;String attachmentIdString = request.getParameter("attachmentId");Long attachmentId;try {attachmentId = Long.parseLong(attachmentIdString);log.info("attachmentId " + attachmentId);user = AuthenticatedUserThreadLocal.get();log.info("user " + user);if (AttachmentUtil.checkAccess(attachmentId, user, false)) {key = DocumentManager.getKeyOfFile(attachmentId);fileName = AttachmentUtil.getFileName(attachmentId);fileUrl = urlManager.GetFileUri(attachmentId);} else {log.error("access deny");errorMessage = "You don not have enough permission to view the file";}} catch (Exception ex) {StringWriter sw = new StringWriter();PrintWriter pw = new PrintWriter(sw);ex.printStackTrace(pw);String error = ex.toString() + "\n" + sw.toString();log.error(error);errorMessage = ex.toString();}response.setContentType("text/html;charset=UTF-8");PrintWriter writer = response.getWriter();writer.write(getTemplate(apiUrl, fileUrl, key, fileName, user, errorMessage));}private String getTemplate(String apiUrl, String fileUrl, String key, String fileName,ConfluenceUser user, String errorMessage) throws UnsupportedEncodingException {Map<String, Object> defaults = MacroUtils.defaultVelocityContext();Map<String, String> config = new HashMap<String, String>();String docTitle = fileName.trim();String docExt = docTitle.substring(docTitle.lastIndexOf(".") + 1).trim().toLowerCase(); config.put("docserviceApiUrl", apiUrl + properties.getProperty("files.docservice.url.api"));config.put("errorMessage\", errorMessage);config.put("docTitle", docTitle);JSONObject responseJson = new JSONObject();JSONObject documentObject = new JSONObject();JSONObject editorConfigObject = new JSONObject();JSONObject userObject = new JSONObject();JSONObject permObject = new JSONObject();try {responseJson.put("type", "desktop");responseJson.put("width", "100%");responseJson.put("height", "100%");responseJson.put("documentType", getDocType(docExt));responseJson.put("document", documentObject);documentObject.put("title", docTitle);documentObject.put("url", fileUrl);documentObject.put("fileType", docExt);documentObject.put("key", key);documentObject.put("permissions", permObject);permObject.put("edit", false);responseJson.put("editorConfig", editorConfigObject);editorConfigObject.put("lang", localeManager.getLocale(user).toLanguageTag());editorConfigObject.put("mode", "edit");if (user != null) {editorConfigObject.put("user", userObject);userObject.put("id", user.getName());userObject.put("name", user.getFullName());}// AsHtml at the end disables automatic html encoding             config.put("jsonAsHtml", responseJson.toString());} catch (Exception ex) {StringWriter sw = new StringWriter();PrintWriter pw = new PrintWriter(sw);ex.printStackTrace(pw);String error = ex.toString() + "\n" + sw.toString();log.error(error);}defaults.putAll(config);return VelocityUtils.getRenderedTemplate("templates/editor.vm", defaults);}private String getDocType(String ext) {if (".doc.docx.docm.dot.dotx.dotm.odt.fodt.ott.rtf.txt.html.htm.mht.pdf.djvu.fb2.epub.xps".indexOf(ext) != -1)return \"text\";if (".xls.xlsx.xlsm.xlt.xltx.xltm.ods.fods.ots.csv".indexOf(ext) != -1)
return "spreadsheet";if (".pps.ppsx.ppsm.ppt.pptx.pptm.pot.potx.potm.odp.fodp.otp".indexOf(ext) != -1)return "presentation";return null;}
}

这将构建一个在页面上打开编辑器时所需的 JSON 对象。请注意,我们会将 JSON 对象放入结尾是 AsHtml 的变量中。其会禁用自动 HTML 编码,所以我们可将其放入模板的 <script>标记内,如下:

var json = '${jsonAsHtml}';

这样就能正常工作了。

我们还使用了三个工具类:AttachmentUtilUrlManager 以及 DocumentManager。这些类有点超出本文所涵盖的范围(除了 AttachmentUtil DocumentManager 中的一部分内容,但我们稍后也会提到),所以如果您对此感兴趣的话,可自行进行探索。

最后还有一件事需要我们去处理:将文档提供给文档服务器。

这里我们再次创建一个 Servlet,其中唯一的方法就是提供文件。

public class OnlyOfficeSaveFileServlet extends HttpServlet {private static final long serialVersionUID = 1L;private static final Logger log = LogManager.getLogger("onlyoffice.OnlyOfficeSaveFileServlet");@ComponentImportprivate final PluginSettingsFactory pluginSettingsFactory;private final PluginSettings settings;@Injectpublic OnlyOfficeSaveFileServlet(PluginSettingsFactory pluginSettingsFactory, JwtManager jwtManager) {this.pluginSettingsFactory = pluginSettingsFactory;settings = pluginSettingsFactory.createGlobalSettings();}@Overridepublic void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String vkey = request.getParameter("vkey");log.info("vkey = " + vkey);String attachmentIdString = DocumentManager.ReadHash(vkey);Long attachmentId = Long.parseLong(attachmentIdString);log.info("attachmentId " + attachmentId);String contentType = AttachmentUtil.getMediaType(attachmentId);response.setContentType(contentType);InputStream inputStream = AttachmentUtil.getAttachmentData(attachmentId);response.setContentLength(inputStream.available());byte[] buffer = new byte[10240];OutputStream output = response.getOutputStream();for (int length = 0; (length = inputStream.read(buffer)) > 0;) {output.write(buffer, 0, length);}}
}

运行与测试

此时,一切准备工作都已就绪,我们可以对附件进行打开并查看。

运行 atlas-run 命令以构建插件,然后运行 Confluence。

或者,您也可以运行 atlas-package 来仅构建一个 .jar

无论选择哪种方式,您都应该前往“设置 -> 管理应用”中上传插件并点击配置。指定文档服务器 URL 并测试插件。

编辑与共同编辑

如需对文档进行编辑,我们就还需要进行一些更改。首先来看看其工作原理。

这里有个名为 callbackUrl 的参数。其应该指向一个从文档服务器接收 JSON 数据的 Servlet,用于描述文档编辑的状态。在用户关闭文档时,其会发出一则消息告知编辑完成,还会发送更新后文档的 URL,便于我们进行下载。

您可能也会想了解一下共同编辑的原理。只要 key 参数相同,用户就可以对同一个文档进行编辑。在为文件生成 key 时有两件重要的事需要考虑。

1. 每个文档的 key 都不应相同。

2. 文档中出现变更时 key 也应当变更。

通常而言,ID + 修改日期的组合能够胜任这一工作。但也有其不适用的情况。如果您打算实现强制保存功能,那么就需要生成一个在编辑全程保持相同的 key。

key 的生成方法位于 AttachmentUtil DocumentManager 中。

让我们将下面的代码添加至编辑器 Servlet 中,就放在 fileUrl = urlManager.GetFileUri(attachmentId); 代码行后方即可

if (AttachmentUtil.checkAccess(attachmentId, user, true)) {callbackUrl = urlManager.getCallbackUrl(attachmentId);
}

将其传递给 getTemplate 函数

writer.write(getTemplate(apiUrl, callbackUrl, fileUrl, key, fileName, user, errorMessage));

然后在此处使用

permObject.put("edit", callbackUrl != null && !callbackUrl.isEmpty());...editorConfigObject.put("callbackUrl", callbackUrl);

接着我们需要创建一个回调处理器。我们来修改一下用于提供文件的 OnlyOfficeSaveFileServlet。这一过程非常简单。我们将对传入的 JSON 对象进行解析,查看其中的 status 并在有需要时保存文档。相关文档可在此处查看。

@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {response.setContentType("text/plain; charset=utf-8");String vkey = request.getParameter("vkey");log.info("vkey = " + vkey);String attachmentIdString = DocumentManager.ReadHash(vkey);String error = "";try {processData(attachmentIdString, request);} catch (Exception e) {error = e.getMessage();}PrintWriter writer = response.getWriter();if (error.isEmpty()) {writer.write("{\"error\":0}");} else {response.setStatus(500);writer.write("{\"error\":1,\"message\":\"" + error + "\"}");}log.info("error = " + error);
}private void processData(String attachmentIdString, HttpServletRequest request) throws Exception {log.info("attachmentId = " + attachmentIdString);InputStream requestStream = request.getInputStream();if (attachmentIdString.isEmpty()) {throw new IllegalArgumentException("attachmentId is empty");}HttpURLConnection connection = null;try {Long attachmentId = Long.parseLong(attachmentIdString);String body = getBody(requestStream);log.info("body = " + body);if (body.isEmpty()) {throw new IllegalArgumentException("requestBody is empty");}JSONObject jsonObj = new JSONObject(body);long status = jsonObj.getLong("status");log.info("status = " + status);// MustSave, Corruptedif (status == 2 || status == 3) {ConfluenceUser user = null;JSONArray users = jsonObj.getJSONArray("users");if (users.length() > 0) {String userName = users.getString(0);UserAccessor userAccessor = (UserAccessor) ContainerManager.getComponent("userAccessor");user = userAccessor.getUserByName(userName);log.info("user = " + user);}if (user == null || !AttachmentUtil.checkAccess(attachmentId, user, true)) {throw new SecurityException("Try save without access: " + user);}String downloadUrl = jsonObj.getString("url");log.info("downloadUri = " + downloadUrl);URL url = new URL(downloadUrl);connection = (HttpURLConnection) url.openConnection();int size = connection.getContentLength();log.info("size = " + size);InputStream stream = connection.getInputStream();AttachmentUtil.saveAttachment(attachmentId, stream, size, user);}} catch (Exception ex) {StringWriter sw = new StringWriter();PrintWriter pw = new PrintWriter(sw);ex.printStackTrace(pw);String error = ex.toString() + "\n" + sw.toString();log.error(error);throw ex;} finally {if (connection != null) {connection.disconnect();}}
}
private String getBody(InputStream stream) {Scanner scanner = null;Scanner scannerUseDelimiter = null;try {scanner = new Scanner(stream);scannerUseDelimiter = scanner.useDelimiter("\\A");return scanner.hasNext() ? scanner.next() : "";} finally {scannerUseDelimiter.close();scanner.close();}
}

好了,这 OK 啦。接下来就是对项目进行重新编译与测试了。

安全性

您可能想知道,我们如何确保通过 POST 方式传递至 Servlet 的 JSON 确实来自文档服务器。答案很简单:文档服务器使用 JWT 来实现这一功能。JWT 是很热门的话题,也是值得单独用一些篇幅来进行介绍的话题,这里我们就不展开了。

我们所使用的是 JwtManager 类。基本上而言,JWT 是基于密钥的哈希加密 JSON。

首先我们需要为密钥添加一个新的设置。这应该不难。您可在这里找到代码:configure.vm 模板配置 Servlet

然后我们会向编辑器 Servlet 构造函数中添加 JwtManager,并在 config.put("jsonAsHtml", responseJson.toString()); 代码行之前对其进行使用:

if (jwtManager.jwtEnabled()) {responseJson.put("token", jwtManager.createToken(responseJson));
}

现在我们就取得了文档服务器的信任

为了确保文档服务器也能得到我们的信任,我们将在处理回调时使用 JWT。

文档服务器传递 JWT 的方式有两种:通过 HTTP 标头传递,或包含在 JSON 中传递,具体取决于配置情况。这里我们将同时对两者进行了解。

下面来修改一下回调 Servlet,在其构造函数中添加 JwtManager,并在 JSONObject jsonObj = new JSONObject(body);代码行后使用:

if (jwtManager.jwtEnabled()) {String token = jsonObj.optString("token");Boolean inBody = true;if (token == null || token == "") {String jwth = (String) settings.get("onlyoffice.jwtHeader");String header = (String) request.getHeader(jwth == null || jwth.isEmpty() ? "Authorization" : jwth);token = (header != null && header.startsWith("Bearer ")) ? header.substring(7) : header;inBody = false;}if (token == null || token == "") {throw new SecurityException("Try save without JWT");}if (!jwtManager.verify(token)) {throw new SecurityException("Try save with wrong JWT");}JSONObject bodyFromToken = new JSONObject(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]), "UTF-8"));if (inBody) {jsonObj = bodyFromToken;} else {jsonObj = bodyFromToken.getJSONObject("payload");}
}

好了,收工!现在我们就能在 JWT 的保护下查看和编辑文档了,其将不会受到未授权访问的侵扰,此外还有便捷的配置项可供使用。

如何将在线文档编辑器集成至 Confluence管理平台相关推荐

  1. 编辑器未包含main类型_利用 ONLYOFFICE 将在线文档编辑器集成到 Python Web 应用程序中...

    通过 API,开发人员可以将 ONLYOFFICE 编辑器集成到网站和利用程序设计语言编写的应用程序中,并能配置和管理编辑器. 来源:https://linux.cn/article-13037-1. ...

  2. 介绍一个开源的在线文档编辑器Etherpad

    我记得google doc刚出来的时候让人眼前一亮,今天偶然间发现一个也是支持多人在线编写文档的编辑器Etherpad,很有意思的一个开源项目(据说谷歌发现这个项目很有前途就把它买下来开源出来),我下 ...

  3. android api在线文档_通过 API 远程管理 Jenkins

    背景介绍 最近接到一个需求,需要对公司内部的Android性能测试平台的分支管理模块进行改造. 为了更好地说明问题,在下图中展示了一个精简的持续集成测试系统. 在该系统中,Jenkins负责定时检测代 ...

  4. 介绍几种在线文档编辑器

    http://wowubuntu.com/markdown/ Markdown 语法说明 (简体中文版) https://github.com/LearnShare/Learning-Markdown ...

  5. 国产化适配(身份证读卡器,条码枪,扫描仪,ca证书,在线文档编辑器)

    操作系统:国产麒麟系统 浏览器:火狐Firefox 51.0b2 (32 位) 详细内容不变透露,详情请留言.

  6. 在线文本文档txt编辑器_审查了6位在线文档和文本编辑者

    在线文本文档txt编辑器 Who wants to limit himself to one computer nowadays? Say hello to online editors, where ...

  7. 【计算机毕业设计】026在线文档管理系统

    一.系统截图(需要演示视频可以私聊) 摘  要 随着科学技术的飞速发展,社会的方方面面.各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,在线文档管理当然也不能排除在外.在线文档管理 ...

  8. 看了10款文档编辑器之后...

    暖春临近 作为一名技术工作者, 我们经常会遇到编写技术文档, 技术分享等需求, 网上也有很多现成的文档管理工具, 出于好奇心, 我拉着朋友一起实现了一个, 用来自给自足. 接下来就来介绍一下轻量级且灵 ...

  9. 推荐一个免费在线文档翻译器,一键搞定PDF文档翻译!

    查阅文献是学术路上必不可少一步.但对很多人来说,查到的很多外文文献都是PDF.怎么翻译成中文,有一个很大的问题.虽然很多软件都有这一功能,但满足我们专业词汇准确性的翻译软件却不多. 今天我就推荐一款个 ...

最新文章

  1. [Android] osx下如何使用SublimeText阅读Android系统源码
  2. 如何用一句话得罪 95% 的中国人?昨天这家公司做到了...
  3. 大话数据结构(十)java程序——队列
  4. C# WinForm开发系列 - DataGridView
  5. 6.2.2 二叉树的创建
  6. 28行代码AC——习题3-12 浮点数(UVA 11809 - Floating-Point Numbers)——解题报告
  7. 关于Google插件Postman的使用方法
  8. LeetCode 218. 天际线问题(multiset优先队列)*
  9. 【MySql】mysql 慢日志查询工具之mysqldumpslow
  10. 室内定位技术(一) TOA TDOA RSS AOA
  11. Kudu : 删除了不存在的数据报错 status=Not found: key not found (error 0)
  12. flow.php 漏洞,Ecshop 3.0的flow.php文件SQL注射漏洞修复
  13. 码农如何写好一封邮件/1
  14. cd oracle home/dbs,Oracle专家高级编程学习笔记
  15. 金山词霸发音功能需要Flash player
  16. JavaScript——监听事件:点击鼠标,视频静音(原神官网)
  17. ui设计师职业规划怎么写_UI设计师职业规划
  18. hdu6070 Dirt Ratio(二分+线段树)
  19. python生成单位阵或者对角阵的三种方法
  20. 颜色恒常知觉的计算理论——Retinex理论

热门文章

  1. 在JavaScript中用DOM做时钟
  2. linux adc时钟设置,linux设备驱动归纳总结(十三):1.触摸屏与ADC时钟
  3. 炫酷计算机网络科技,科技改变未来!30款炫酷科技工具创意设计
  4. 极速搭建Hexo博客【 CentOS7 + Node.js + Hexo + Github Pages】
  5. 直方图绘制(折线图)
  6. 音频 3A 处理实践,让你的应用更「动听」
  7. 简单说明一下数据库审计能带来的价值
  8. TypeError: __init__() missing 1 required positional argument: 'on_delete' 解决办法
  9. 苹果手机怎么关机?2个方法,教你快速关机
  10. STL常见面试题总结