找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2495

积分

0

好友

351

主题
发表于 前天 05:52 | 查看: 7| 回复: 0

最近有个项目需求是实现前端页面可以对Word文档进行编辑,并且支持保存功能。经过调研,选择了开源的第三方工具 OnlyOffice 来实现该功能。OnlyOffice 提供了丰富的文档处理能力,包括文档编辑、多人协同、格式转换等,本文重点介绍如何通过 SpringBoot 集成 OnlyOffice 实现在线 Word 文档的加载、编辑和自动保存。

nuxt-vue-pro 技术架构图

1. OnlyOffice 的部署

OnlyOffice 支持多种部署方式,推荐使用 Docker 进行快速部署,操作简单且环境隔离性好。

使用 Docker 部署(推荐)

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

启动后访问 http://localhost 即可查看服务是否正常运行。

如果你更倾向于手动安装,也可以参考 Ubuntu 手动部署方式:

注意:Docker 方式更为简洁稳定,建议优先选择。


2. 代码逻辑开发

前后端技术栈如下:

2.1 前端代码

参考 OnlyOffice 官方 API 文档进行集成:

首先在页面中引入 OnlyOffice 的 JS SDK:

<div id="placeholder"></div>
<script type="text/javascript" src="https://documentserver/web-apps/apps/api/documents/api.js"></script>

请将 documentserver 替换为你的 OnlyOffice 服务地址。

然后初始化编辑器实例:

const config = {
  document: {
    mode: 'edit',
    fileType: 'docx',
    key: String(Math.floor(Math.random() * 10000)),
    title: route.query.name + '.docx',
    url: import.meta.env.VITE_APP_API_URL + `/getFile/${route.query.id}`,
    permissions: {
      comment: true,
      download: true,
      modifyContentControl: true,
      modifyFilter: true,
      edit: true,
      fillForms: true,
      review: true,
    },
  },
  documentType: 'word',
  editorConfig: {
    user: {
      id: 'liu',
      name: 'liu',
    },
    customization: {
      plugins: false,
      forcesave: true,
    },
    lang: 'zh',
    callbackUrl: import.meta.env.VITE_APP_API_URL + `/callback`,
  },
  height: '100%',
  width: '100%',
};

new window.DocsAPI.DocEditor('onlyoffice', config);

其中:

  • import.meta.env.VITE_APP_API_URL 是后端接口地址,例如:http://192.168.123.123:8089
  • /getFile/{id} 接口用于获取文档流
  • callbackUrl 是文档状态回调地址,用于监听保存事件

2.2 后端代码

Maven 依赖

<!-- httpclient start -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
</dependency>

控制器代码

package com.ruoyi.web.controller.meetingminutes.onlyoffice;

import com.ruoyi.system.domain.MeetingTable;
import com.ruoyi.system.service.IMeetingTableService;
import com.ruoyi.web.controller.meetingminutes.utils.HttpsKitWithProxyAuth;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Collections;

@Api(value = "OnlyOfficeController")
@RestController
public class OnlyOfficeController {

    @Autowired
    private IMeetingTableService meetingTableService;

    private String meetingMinutesFilePath;

    @ApiOperation(value = "OnlyOffice")
    @GetMapping("/getFile/{meeting_id}")
    public ResponseEntity<byte[]> getFile(HttpServletResponse response, @PathVariable Long meeting_id) throws IOException {
        MeetingTable meetingTable = meetingTableService.selectMeetingTableById(meeting_id);
        meetingMinutesFilePath = meetingTable.getMeetingMinutesFilePath();
        if (meetingMinutesFilePath == null || "".equals(meetingMinutesFilePath)) {
            return null;
        }
        File file = new File(meetingMinutesFilePath);
        FileInputStream fileInputStream = null;
        InputStream fis = null;
        try {
            fileInputStream = new FileInputStream(file);
            fis = new BufferedInputStream(fileInputStream);
            byte[] buffer = new byte[fis.available()];
            fis.read(buffer);
            fis.close();
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", URLEncoder.encode(file.getName(), "UTF-8"));
            return new ResponseEntity<>(buffer, headers, HttpStatus.OK);
        } catch (Exception e) {
            throw new RuntimeException("e -> ", e);
        } finally {
            try { if (fis != null) fis.close(); } catch (Exception e) {}
            try { if (fileInputStream != null) fileInputStream.close(); } catch (Exception e) {}
        }
    }

    @CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS})
    @PostMapping("/callback")
    public ResponseEntity<Object> handleCallback(@RequestBody CallbackData callbackData) {

        Integer status = callbackData.getStatus();
        switch (status) {
            case 1: {
                break;
            }
            case 2: {
                System.out.println("document is ready for saving");
                String url = callbackData.getUrl();
                try {
                    saveFile(url);
                } catch (Exception e) {
                    System.out.println("保存文件异常");
                }
                System.out.println("save success.");
                break;
            }
            case 3: {
                System.out.println("document saving error has occurred,保存出错");
                break;
            }
            case 4: {
                System.out.println("document is closed with no changes,未保存退出");
                break;
            }
            case 6: {
                String url = callbackData.getUrl();
                try {
                    saveFile(url);
                } catch (Exception e) {
                    System.out.println("保存文件异常");
                }
                System.out.println("save success.");
            }
            case 7: {
                System.out.println("error has occurred while force saving the document. 强制保存文档出错");
            }
            default: {}
        }

        return ResponseEntity.ok(Collections.singletonMap("error", 0));
    }

    public void saveFile(String downloadUrl) throws URISyntaxException, IOException {
        HttpsKitWithProxyAuth.downloadFile(downloadUrl, meetingMinutesFilePath);
    }

    @Setter
    @Getter
    public static class CallbackData {
        Object changeshistory;
        Object history;
        String changesurl;
        String filetype;
        Integer forcesavetype;
        String key;
        Integer status;
        String url;
        Object userdata;
        String[] users;
        String lastsave;
        String token;
    }
}

工具类:HttpsKitWithProxyAuth

该类用于处理 HTTP 请求,支持代理和文件下载功能。

package com.ruoyi.web.controller.meetingminutes.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;

import org.apache.commons.codec.CharEncoding;
import org.apache.commons.io.IOUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.auth.AUTH;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HttpsKitWithProxyAuth {

    private static Logger logger = LoggerFactory.getLogger(HttpsKitWithProxyAuth.class);
    private static final int CONNECT_TIMEOUT = 10000;
    private static final int SOCKET_TIMEOUT = 30000;
    private static final int HttpIdelTimeout = 30000;
    private static final int HttpMonitorInterval = 10000;
    private static final int MAX_CONN = 200;
    private static final int Max_PRE_ROUTE = 200;
    private static CloseableHttpClient httpClient;
    private static PoolingHttpClientConnectionManager manager;
    private static ScheduledExecutorService monitorExecutor;

    private static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
    private static final String APPLICATION_JSON = "application/json";
    private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36";
    private static final Object syncLock = new Object();

    public static final String HTTP = "http";
    public static final String SOCKS = "socks";
    private static boolean needProxy = false;
    private static boolean needLogin = false;
    private static String proxyType = HTTP;
    private static String proxyHost = "127.0.0.1";
    private static int proxyPort = 1080;
    private static String proxyUsername = "sendi";
    private static String proxyPassword = "123456";

    private static RequestConfig requestConfig = RequestConfig.custom()
            .setConnectionRequestTimeout(CONNECT_TIMEOUT)
            .setConnectTimeout(CONNECT_TIMEOUT)
            .setSocketTimeout(SOCKET_TIMEOUT).build();

    static {
        if (needProxy && SOCKS.equals(proxyType) && needLogin){
            Authenticator.setDefault(new Authenticator(){
                protected PasswordAuthentication getPasswordAuthentication(){
                    PasswordAuthentication p = new PasswordAuthentication(proxyUsername, proxyPassword.toCharArray());
                    return p;
                }
            });
        }
    }

    public static void setProxy(boolean needProxy, boolean needLogin, String proxyType, String proxyHost, int proxyPort, String proxyUserName, String proxyPassword) {
        HttpsKitWithProxyAuth.needProxy = needProxy;
        HttpsKitWithProxyAuth.needLogin = needLogin;
        HttpsKitWithProxyAuth.proxyType = proxyType;
        HttpsKitWithProxyAuth.proxyHost = proxyHost;
        HttpsKitWithProxyAuth.proxyPort = proxyPort;
        HttpsKitWithProxyAuth.proxyUsername = proxyUserName;
        HttpsKitWithProxyAuth.proxyPassword = proxyPassword;
    }

    private static CloseableHttpClient getHttpClient() {
        if (httpClient == null) {
            synchronized (syncLock) {
                if (httpClient == null) {
                    try {
                        httpClient = createHttpClient();
                    } catch (KeyManagementException e) {
                        logger.error("error",e);
                    } catch (NoSuchAlgorithmException e) {
                        logger.error("error",e);
                    } catch (KeyStoreException e) {
                        logger.error("error",e);
                    }

                    monitorExecutor = Executors.newScheduledThreadPool(1);
                    monitorExecutor.scheduleAtFixedRate(new TimerTask() {
                        @Override
                        public void run() {
                            manager.closeExpiredConnections();
                            manager.closeIdleConnections(HttpIdelTimeout,TimeUnit.MILLISECONDS);
                        }
                    }, HttpMonitorInterval, HttpMonitorInterval, TimeUnit.MILLISECONDS);
                }
            }
        }
        return httpClient;
    }

    private static CloseableHttpClient createHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
        SSLContextBuilder builder = new SSLContextBuilder();
        builder.loadTrustMaterial(null, new TrustStrategy() {
            @Override
            public boolean isTrusted(X509Certificate[] x509Certificates, String s) {
                return true;
            }
        });

        ConnectionSocketFactory plainSocketFactory = null;
        LayeredConnectionSocketFactory sslSocketFactory = null;

        if (needProxy && SOCKS.endsWith(proxyType)){
            plainSocketFactory = new MyConnectionSocketFactory();
            sslSocketFactory = new MySSLConnectionSocketFactory(builder.build());
        } else {
            plainSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
            sslSocketFactory = new SSLConnectionSocketFactory(builder.build(), NoopHostnameVerifier.INSTANCE);
        }

        Registry<ConnectionSocketFactory> registry = RegistryBuilder
                .<ConnectionSocketFactory>create()
                .register("http", plainSocketFactory)
                .register("https", sslSocketFactory).build();

        manager = new PoolingHttpClientConnectionManager(registry);
        manager.setMaxTotal(MAX_CONN);
        manager.setDefaultMaxPerRoute(Max_PRE_ROUTE);

        HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
                if (i > 3) {
                    logger.error("retry has more than 3 time, give up request");
                    return false;
                }
                if (e instanceof NoHttpResponseException) {
                    logger.error("receive no response from server, retry");
                    return true;
                }
                if (e instanceof SSLHandshakeException) {
                    logger.error("SSL hand shake exception");
                    return false;
                }
                if (e instanceof InterruptedIOException) {
                    logger.error("InterruptedIOException");
                    return false;
                }
                if (e instanceof UnknownHostException) {
                    logger.error("server host unknown");
                    return false;
                }
                if (e instanceof ConnectTimeoutException) {
                    logger.error("Connection Time out");
                    return false;
                }
                if (e instanceof SSLException) {
                    logger.error("SSLException");
                    return false;
                }

                HttpClientContext context = HttpClientContext.adapt(httpContext);
                HttpRequest request = context.getRequest();

                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    return true;
                }
                return false;
            }
        };

        CloseableHttpClient client = null;
        if (needProxy && HTTP.endsWith(proxyType)){
            client = HttpClients.custom()
                    .setConnectionManager(manager)
                    .setProxy(new HttpHost(proxyHost, proxyPort))
                    .setRetryHandler(handler).build();
        } else {
            client = HttpClients.custom()
                    .setConnectionManager(manager)
                    .setRetryHandler(handler).build();
        }

        return client;
    }

    public static String get(String url) {
        return get(url, null);
    }

    public static String get(String url, Map<String,Object> headerParams) {
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("User-Agent", USER_AGENT);
        httpGet.setConfig(requestConfig);

        if (headerParams != null && headerParams.size()>0){
            for (String headerName : headerParams.keySet()) {
                httpGet.setHeader(headerName, headerParams.get(headerName)+"");
            }
        }

        CloseableHttpResponse response = null;
        InputStream in = null;
        String result = null;

        try {
            HttpClientContext ctx  = createContext();
            response = getHttpClient().execute(httpGet,ctx);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                result = IOUtils.toString(in, "utf-8");
            }
        } catch (Exception e) {
            logger.error("error",e);
        } finally {
            try { if (in != null) in.close(); } catch (IOException e) { logger.error("error",e); }
            try { if (response != null) response.close(); } catch (IOException e) { logger.error("error",e); }
        }
        return result;
    }

    public static String postJson(String url, Map<String,Object> requestParams) {
        return postJson(url, JsonUtil.toJSONString(requestParams));
    }

    public static String postJson(String url, Map<String,Object> requestParams, Map<String,String> headerParams) {
        return postJson(url, JsonUtil.toJSONString(requestParams), headerParams);
    }

    public static String postJson(String url, String requestParamStr) {
        return postJson(url, requestParamStr, null);
    }

    public static String put(String url, String requestParamStr, Map<String,String> headerParams) {
        HttpPut httpput = new HttpPut(url);
        httpput.setHeader("Content-Type", APPLICATION_JSON+";charset=" + CharEncoding.UTF_8);
        httpput.setHeader("Accept",APPLICATION_JSON+";charset=" +CharEncoding.UTF_8);
        httpput.setHeader("User-Agent",USER_AGENT);

        if (headerParams != null && headerParams.size()>0){
            for (String headerName : headerParams.keySet()) {
                httpput.setHeader(headerName, headerParams.get(headerName)+"");
            }
        }

        StringEntity se = new StringEntity(requestParamStr, CharEncoding.UTF_8);
        se.setContentType(APPLICATION_JSON+";charset=" +CharEncoding.UTF_8);
        httpput.setEntity(se);
        httpput.setConfig(requestConfig);

        CloseableHttpResponse response = null;
        InputStream in = null;
        String result = null;

        try {
            HttpClientContext ctx  = createContext();
            response = getHttpClient().execute(httpput,ctx);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                result = IOUtils.toString(in, "utf-8");
            }
        } catch (Exception e) {
            logger.error("error",e);
        } finally {
            try { if (in != null) in.close(); } catch (IOException e) { logger.error("error",e); }
            try { if (response != null) response.close(); } catch (IOException e) { logger.error("error",e); }
        }
        return result;
    }

    public static HttpClientContext createContext() throws MalformedChallengeException {
        HttpClientContext ctx  = HttpClientContext.create();

        if (needProxy && SOCKS.endsWith(proxyType)){
            InetSocketAddress socksaddr = new InetSocketAddress(proxyHost,proxyPort);
            ctx.setAttribute("socks.address", socksaddr);
        } else {
            if (needProxy && HTTP.endsWith(proxyType)){
                if (needLogin){
                    AuthState authState = new AuthState();
                    BasicScheme basicScheme = new BasicScheme();
                    basicScheme.processChallenge(new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=default"));
                    authState.update(basicScheme, new UsernamePasswordCredentials(proxyUsername, proxyPassword));
                    ctx.setAttribute(HttpClientContext.PROXY_AUTH_STATE, authState);
                }
            }
        }
        return ctx;
    }

    public static String postJson(String url, String requestParamStr, Map<String,String> headerParams) {
        HttpPost httppost = new HttpPost(url);
        httppost.setHeader("Content-Type", APPLICATION_JSON+";charset=" + CharEncoding.UTF_8);
        httppost.setHeader("Accept",APPLICATION_JSON+";charset=" +CharEncoding.UTF_8);
        httppost.setHeader("User-Agent",USER_AGENT);

        if (headerParams != null && headerParams.size()>0){
            for (String headerName : headerParams.keySet()) {
                httppost.setHeader(headerName, headerParams.get(headerName)+"");
            }
        }

        StringEntity se = new StringEntity(requestParamStr, CharEncoding.UTF_8);
        se.setContentType(APPLICATION_JSON+";charset=" +CharEncoding.UTF_8);
        httppost.setEntity(se);
        httppost.setConfig(requestConfig);

        CloseableHttpResponse response = null;
        InputStream in = null;
        String result = null;

        try {
            HttpClientContext ctx  = createContext();
            response = getHttpClient().execute(httppost,ctx);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                result = IOUtils.toString(in, "utf-8");
            }
        } catch (Exception e) {
            logger.error("error",e);
        } finally {
            try { if (in != null) in.close(); } catch (IOException e) { logger.error("error",e); }
            try { if (response != null) response.close(); } catch (IOException e) { logger.error("error",e); }
        }
        return result;
    }

    public static String postFormUrlencoded(String url, String requestParamStr) {
        return postFormUrlencoded(url, requestParamStr, null);
    }

    public static String postFormUrlencoded(String url, String requestParamStr, Map<String,Object> headerParams) {
        Map<String,String> requestParams = new HashMap<String,String>();
        String[] strs = requestParamStr.split("&");
        for (String str : strs) {
            String[] keyValues = str.split("=");
            if (keyValues.length == 2) {
                requestParams.put(keyValues[0], keyValues[1]);
            }
        }
        return postFormUrlencoded(url, requestParams, headerParams);
    }

    public static String postFormUrlencoded(String url, Map<String,String> requestParams) {
        return postFormUrlencoded(url, requestParams, null);
    }

    public static String postFormUrlencoded(String url, Map<String,String> requestParams, Map<String,Object> headerParams) {
        HttpPost httppost = new HttpPost(url);
        httppost.setHeader("Content-Type", APPLICATION_FORM_URLENCODED+";charset=" + CharEncoding.UTF_8);
        httppost.setHeader("Accept",APPLICATION_JSON+";charset=" +CharEncoding.UTF_8);
        httppost.setHeader("User-Agent",USER_AGENT);

        if (headerParams != null && headerParams.size()>0){
            for (String headerName : headerParams.keySet()) {
                httppost.setHeader(headerName, headerParams.get(headerName)+"");
            }
        }

        List<NameValuePair> formparams = new ArrayList<NameValuePair>();
        for (String keyStr : requestParams.keySet()) {
            formparams.add(new BasicNameValuePair(keyStr, requestParams.get(keyStr)));
        }

        UrlEncodedFormEntity uefe = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
        httppost.setEntity(uefe);
        httppost.setConfig(requestConfig);

        CloseableHttpResponse response = null;
        InputStream in = null;
        String result = null;

        try {
            HttpClientContext ctx  = createContext();
            response = getHttpClient().execute(httppost,ctx);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                result = IOUtils.toString(in, "utf-8");
            }
        } catch (Exception e) {
            logger.error("error",e);
        } finally {
            try { if (in != null) in.close(); } catch (IOException e) { logger.error("error",e); }
            try { if (response != null) response.close(); } catch (IOException e) { logger.error("error",e); }
        }
        return result;
    }

    public static String postFormMultipart(String url, InputStream fin, String originalFilename) {
        HttpPost httppost = new HttpPost(url);
        httppost.setConfig(requestConfig);
        InputStreamBody bin = new InputStreamBody(fin, originalFilename);

        MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
        multipartEntityBuilder.addPart("file",bin);
        multipartEntityBuilder.addPart("fileName", new StringBody(originalFilename,ContentType.TEXT_PLAIN));
        multipartEntityBuilder.addPart("fileSize", new StringBody("1024",ContentType.TEXT_PLAIN));
        multipartEntityBuilder.addPart("scene", new StringBody("default",ContentType.TEXT_PLAIN));
        multipartEntityBuilder.addPart("output", new StringBody("json2",ContentType.TEXT_PLAIN));
        HttpEntity reqEntity = multipartEntityBuilder.build();
        httppost.setEntity(reqEntity);

        CloseableHttpResponse response = null;
        InputStream in = null;
        String result = null;

        try {
            HttpClientContext ctx  = createContext();
            response = getHttpClient().execute(httppost,ctx);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                result = IOUtils.toString(in, "utf-8");
            }
        } catch (Exception e) {
            logger.error("error",e);
        } finally {
            try { if (in != null) in.close(); } catch (IOException e) { logger.error("error",e); }
            try { if (response != null) response.close(); } catch (IOException e) { logger.error("error",e); }
        }
        return result;
    }

    public static void downloadFile(String downloadUrl, String savePathAndName) {
        HttpGet httpGet = new HttpGet(downloadUrl);
        httpGet.setHeader("User-Agent",USER_AGENT);
        httpGet.setConfig(requestConfig);

        CloseableHttpResponse response = null;
        InputStream in = null;

        try {
            response = getHttpClient().execute(httpGet, HttpClientContext.create());
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                in = entity.getContent();
                FileOutputStream out = new FileOutputStream(new File(savePathAndName));
                IOUtils.copy(in, out);
                out.close();
            }
        } catch (IOException e) {
            logger.error("error",e);
        } finally {
            try { if (in != null) in.close(); } catch (IOException e) { logger.error("error",e); }
            try { if (response != null) response.close(); } catch (IOException e) { logger.error("error",e); }
        }
    }

    public static void downloadFile(String downloadUrl, String saveFileName, String savePath) {
        String savePathAndName = savePath.endsWith("/") ? savePath.substring(0,savePath.lastIndexOf("/")) : savePath;
        downloadFile(downloadUrl, savePathAndName);
    }

    public static void closeConnectionPool() {
        if (manager != null) manager.close();
        if (monitorExecutor != null) monitorExecutor.shutdown();
        try { if (httpClient != null) httpClient.close();} catch (IOException e) {logger.error("error",e);}
        manager = null;
        monitorExecutor = null;
        httpClient = null;
    }

    private static class MyConnectionSocketFactory extends PlainConnectionSocketFactory {
        @Override
        public Socket createSocket(final HttpContext context) throws IOException {
            InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
            Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
            return new Socket(proxy);
        }

        @Override
        public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
                                    InetSocketAddress localAddress, HttpContext context) throws IOException {
            InetSocketAddress unresolvedRemote = InetSocketAddress.createUnresolved(host.getHostName(), remoteAddress.getPort());
            return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context);
        }
    }

    private static class MySSLConnectionSocketFactory extends SSLConnectionSocketFactory {
        public MySSLConnectionSocketFactory(final SSLContext sslContext) {
            super(sslContext, NoopHostnameVerifier.INSTANCE);
        }

        @Override
        public Socket createSocket(final HttpContext context) throws IOException {
            InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
            Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
            return new Socket(proxy);
        }

        @Override
        public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
                                    InetSocketAddress localAddress, HttpContext context) throws IOException {
            InetSocketAddress unresolvedRemote = InetSocketAddress.createUnresolved(host.getHostName(), remoteAddress.getPort());
            return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context);
        }
    }

    public static void main(String[] args) throws InterruptedException, MalformedURLException {
        String url = "https://www.baidu.com";
        System.out.println(HttpsKitWithProxyAuth.get(url));
        HttpsKitWithProxyAuth.closeConnectionPool();
    }
}

JSON 工具类:JsonUtil

package com.ruoyi.web.controller.meetingminutes.utils;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

public class JsonUtil {

    private final static Logger logger = LoggerFactory.getLogger(JsonUtil.class);

    private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";

    private static ObjectMapper objectMapper;

    static {
        objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.setDateFormat(new SimpleDateFormat(STANDARD_FORMAT));
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        objectMapper.setVisibility(PropertyAccessor.ALL, Visibility.ANY);

        JavaTimeModule module = new JavaTimeModule();
        module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        module.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        module.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        objectMapper.registerModule(module);
    }

    public static String toJSONString(Object o) {
        if (o == null) return null;
        if (o instanceof String) return (String) o;
        String jsonValue = null;
        try {
            jsonValue = objectMapper.writeValueAsString(o);
        } catch (JsonProcessingException e) {
            logger.error("Parse Object to String error", e);
        }
        return jsonValue;
    }

    @SuppressWarnings("unchecked")
    public static Map<String,Object> castToObject(String fromValue) {
        if (fromValue == null || "".equals(fromValue)) return null;
        try {
            return objectMapper.readValue(fromValue, Map.class);
        } catch (Exception e) {
            logger.error("Parse String to Object error:", e);
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> T castToObject(String fromValue, Class<T> clazz) {
        if (fromValue == null || "".equals(fromValue) || clazz == null) return null;
        try {
            return clazz.equals(String.class) ? (T) fromValue : objectMapper.readValue(fromValue, clazz);
        } catch (Exception e) {
            logger.error("Parse String to Object error:", e);
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> T castToObject(String fromValue, TypeReference<T> typeReference) {
        if (fromValue == null || "".equals(fromValue) || typeReference == null) return null;
        try {
            return (T) (typeReference.getType().equals(String.class) ? fromValue : objectMapper.readValue(fromValue, typeReference));
        } catch (IOException e) {
            logger.error("Parse String to Object error:", e);
            return null;
        }
    }

    public static <T> T castToObject(String fromValue, Class<?> collectionClazz, Class<?>... elementClazzes) {
        JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClazz, elementClazzes);
        try {
            return objectMapper.readValue(fromValue, javaType);
        } catch (IOException e) {
            logger.error("Parse String to Object error : ", e.getMessage());
            return null;
        }
    }

    public static <T> T getValue(String fromValue, Class<T> clazz) {
        return castToObject(fromValue, clazz);
    }

    public static <T> T getValue(String fromValue, TypeReference<T> toValueTypeRef) {
        return castToObject(fromValue, toValueTypeRef);
    }

    public static <T> T getValue(String fromValue, Class<?> collectionClazz, Class<?>... elementClazzes) {
        return castToObject(fromValue, collectionClazz, elementClazzes);
    }

    public static <T> T getValue(String key, String fromValue, Class<T> clazz) {
        Map<String,Object> infoMap = castToObject(fromValue);
        if (infoMap == null) return null;
        return getValue(key, infoMap, clazz);
    }

    public static <T> T getValue(String key, String fromValue, TypeReference<T> toValueTypeRef) {
        Map<String,Object> infoMap = castToObject(fromValue);
        if (infoMap == null) return null;
        return getValue(key, infoMap, toValueTypeRef);
    }

    public static <T> T getValue(String key, String fromValue, Class<?> collectionClazz, Class<?>... elementClazzes) {
        Map<String,Object> infoMap = castToObject(fromValue);
        if (infoMap == null) return null;
        return getValue(key, infoMap, collectionClazz, elementClazzes);
    }

    @SuppressWarnings("rawtypes")
    public static <T> T getValue(String key, Map fromMap, Class<T> clazz) {
        try {
            String[] keys = key.split("[.]");
            for (int i = 0; i < keys.length; i++) {
                Object value = fromMap.get(keys[i]);
                if (value == null) return null;
                if (i < keys.length - 1) {
                    fromMap = (Map) value;
                } else {
                    return objectMapper.convertValue(value, clazz);
                }
            }
        } catch (Exception e) {
            logger.error("getValue error : ", e.getMessage());
            return null;
        }
        return null;
    }

    @SuppressWarnings("rawtypes")
    public static <T> T getValue(String key, Map fromMap, Class<?> collectionClazz, Class<?>... elementClazzes) {
        try {
            String[] keys = key.split("[.]");
            for (int i = 0; i < keys.length; i++) {
                Object value = fromMap.get(keys[i]);
                if (value == null) return null;
                if (i < keys.length - 1) {
                    fromMap = (Map) value;
                } else {
                    JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClazz, elementClazzes);
                    return objectMapper.convertValue(value, javaType);
                }
            }
        } catch (Exception e) {
            logger.error("getValue error : ", e.getMessage());
            return null;
        }
        return null;
    }

    @SuppressWarnings("rawtypes")
    public static <T> T getValue(String key, Map fromMap, TypeReference<T> toValueTypeRef) {
        try {
            String[] keys = key.split("[.]");
            for (int i = 0; i < keys.length; i++) {
                Object value = fromMap.get(keys[i]);
                if (value == null) return null;
                if (i < keys.length - 1) {
                    fromMap = (Map) value;
                } else {
                    return objectMapper.convertValue(value, toValueTypeRef);
                }
            }
        } catch (Exception e) {
            logger.error("getValue error : ", e.getMessage());
            return null;
        }
        return null;
    }

    public static <T> T convertValue(Object fromValue, TypeReference<T> toValueTypeRef) {
        try {
            return objectMapper.convertValue(fromValue, toValueTypeRef);
        } catch (Exception e) {
            logger.error("convertValue error : ", e.getMessage());
            return null;
        }
    }

    public static <T> T convertValue(Object fromValue, Class<T> toValueType) {
        try {
            return objectMapper.convertValue(fromValue, toValueType);
        } catch (Exception e) {
            logger.error("convertValue error : ", e.getMessage());
            return null;
        }
    }

    public static String getString(Map<String,Object> fromMap, String fieldName) {
        return fromMap.get(fieldName)==null ? null : fromMap.get(fieldName).toString();
    }

    public static String getString(Map<String,Object> jsonObject, String fieldName, String defaultValue) {
        return jsonObject.get(fieldName)==null ? defaultValue : jsonObject.get(fieldName).toString();
    }

    public static Integer getInteger(Map<String,Object> jsonObject, String fieldName) {
        return jsonObject.get(fieldName)==null ? null : (Integer)jsonObject.get(fieldName);
    }

    public static Double getDouble(Map<String,Object> jsonObject, String fieldName) {
        return jsonObject.get(fieldName)==null ? null : (Double)jsonObject.get(fieldName);
    }

    public static Boolean getBoolean(Map<String,Object> jsonObject, String fieldName) {
        return jsonObject.get(fieldName)==null ? false : (Boolean)jsonObject.get(fieldName);
    }

    public static Long getLong(Map<String,Object> jsonObject, String fieldName) {
        return jsonObject.get(fieldName)==null ? null : (Long)jsonObject.get(fieldName);
    }

    public static <T> List<T> getList(Map<String,Object> jsonObject, String fieldName, Class<T> clazz) {
        return jsonObject.get(fieldName)==null ? null : JsonUtil.getValue(fieldName, jsonObject, List.class, clazz);
    }
}

3. 常见问题与解决方案

3.1 访问示例失败

部署完成后若无法访问示例页面,可通过以下命令检查服务状态:

systemctl status ds*

确保所有相关服务已启动。

3.2 加载 Word 文档失败

修改 OnlyOffice 配置文件以关闭 Token 校验:

配置文件路径:/etc/onlyoffice/documentserver

local.json 中设置:

"token": {
  "enable": {
    "inbox": false,
    "outbox": false
  },
  "browser": false
}

default.json 中设置:

"request-filtering-agent": {
  "allowPrivateIPAddress": true,
  "allowMetaIPAddress": true
},
"token": {
  "enable": {
    "browser": false,
    "request": {
      "inbox": false,
      "outbox": false
    }
  }
},
"rejectUnauthorized": false

修改后重启服务生效。

3.3 系统后端有 Token 验证问题

如果后端启用了 JWT 或其他权限验证机制,需放行 OnlyOffice 相关接口:

@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
    return httpSecurity
        .csrf(csrf -> csrf.disable())
        .headers((headersCustomizer) -> {
            headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
        })
        .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests((requests) -> {
            permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
            requests.antMatchers("/callback", "/getFile/*", "/login", "/register", "/captchaImage").permitAll()
                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
                .anyRequest().authenticated();
        })
        .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
        .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
        .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
        .addFilterBefore(corsFilter, LogoutFilter.class)
        .build();
}

3.4 使用文档地址访问问题

若希望使用类似 /word/1234.docx 的静态路径访问文档,可借助 Nginx 做反向代理或直接挂载静态资源目录。

测试时也可使用在线 DOCX 文件验证部署是否成功:

https://d2nlctn12v279m.cloudfront.net/assets/docs/samples/zh/demo.docx

替换前端 config.url 字段即可测试。


4. 小结

本文详细介绍了如何通过 Spring Boot 集成 OnlyOffice 实现在线 Word 编辑、保存等功能。涵盖了环境部署、前后端集成、常见问题排查等关键环节。整个过程虽有一定复杂度,但 OnceOffice 社区版功能强大且免费,非常适合企业内部系统集成文档协作能力。

如你在集成过程中遇到更多技术难题,欢迎访问 云栈社区,获取更多实战案例与技术支持。




上一篇:Python HTTP客户端库全面对比:从同步的requests到异步的aiohttp如何选型
下一篇:AI财务自动化系统Rever的核心功能详解与应用场景
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 01:38 , Processed in 0.368384 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表