我想做一个网站 不知道找谁做,网站建设不用虚拟主机,重庆市建设工程信息网 安全监督,网站编程好学吗前言 最近有个项目需求是实现前端页面可以对word文档进行编辑#xff0c;并且可以进行保存#xff0c;于是一顿搜索#xff0c;找到开源第三方onlyoffice#xff0c;实际上onlyOffice有很多功能#xff0c;例如文档转化、多人协同编辑文档、文档打印等#xff0c;我们只用…前言 最近有个项目需求是实现前端页面可以对word文档进行编辑并且可以进行保存于是一顿搜索找到开源第三方onlyoffice实际上onlyOffice有很多功能例如文档转化、多人协同编辑文档、文档打印等我们只用到了文档编辑功能。
目 录
前言
1、onlyoffice的部署
2、代码逻辑开发
2.1、前端代码
2.2、后端代码
3、问题总结
3.1、访问案例失败
3.2、加载word文档失败
3.3、系统后端有token验证问题
3.4、使用文档地址访问问题
4、后记 1、onlyoffice的部署 部署分为docker部署方式和本地直接安装的方式比较两种部署方式docker是比较简单的一种因为只要拉取相关镜像然后启动时配置好对应的配置文件即可。由于搜索的时候先看到的是linux本地部署所以采用了第二种方式下面我将给出两个参考博客 docker的方式博客我未进行尝试对于是否能成功是不知的 ubuntu部署方式博客我按照这个方式走下来是可以走通的 2、代码逻辑开发 前端使用的element框架vue版本后端采用springboot
2.1、前端代码
参考官方文档API
参考文档
记得添加下面的js文件
div idplaceholder/div
script typetext/javascript srchttps://documentserver/web-apps/apps/api/documents/api.js/script
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 ,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为你实际的onlyoffice地址httpip端口号/访问路径例如我们就是http:192.168.123.123:8089/getFile/12,其中12为会议号用于得到文件地址。 其中import.meta.env.VITE_APP_API_URL/callback为回调函数即文档有什么操作后都会通过这个函数进行回调例如编辑保存操作。
2.2、后端代码
pom依赖
!-- httpclient start --dependencygroupIdorg.apache.httpcomponents/groupIdartifactIdhttpclient/artifactId/dependencydependencygroupIdorg.apache.httpcomponents/groupIdartifactIdhttpmime/artifactId/dependency
package com.ruoyi.web.controller.meetingminutes.onlyoffice;/*** Author 不要有情绪的 ljy* Date 2024/10/31 20:26* Description:*/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 {Autowiredprivate IMeetingTableService meetingTableService;//这里仅写死路径测试// private String meetingMinutesFilePath C:\\Users\\qrs-ljy\\Desktop\\王勋\\c1f15837-d8b4-4380-8161-b85e970ad174\\123435_会议纪要公开.docx; //这里仅写死路径测试private String meetingMinutesFilePath;/*** 传入参数 会议id得到会议纪要文件流并进行打开** param response* param meeting_id* return* throws IOException*/ApiOperation(value OnlyOffice)GetMapping(/getFile/{meeting_id})public ResponseEntitybyte[] 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; //当会议纪要文件为空的时候就返回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 ResponseEntityObject handleCallback(RequestBody CallbackData callbackData) {//状态监听//参见https://api.onlyoffice.com/editors/callbackInteger status callbackData.getStatus();switch (status) {case 1: {//document is being edited 文档已经被编辑break;}case 2: {//document is ready for saving,文档已准备好保存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: {//document saving error has occurred,保存出错System.out.println(document saving error has occurred,保存出错);break;}case 4: {//document is closed with no changes,未保存退出System.out.println(document is closed with no changes,未保存退出);break;}case 6: {//document is being edited, but the current document state is saved,编辑保存String url callbackData.getUrl();try {saveFile(url); //保存文件} catch (Exception e) {System.out.println(保存文件异常);}System.out.println(save success.);}case 7: {//error has occurred while force saving the document. 强制保存文档出错System.out.println(error has occurred while force saving the document. 强制保存文档出错);}default: {}}// 返回响应return ResponseEntity.Objectok(Collections.singletonMap(error, 0));}public void saveFile(String downloadUrl) throws URISyntaxException, IOException {HttpsKitWithProxyAuth.downloadFile(downloadUrl, meetingMinutesFilePath);}SetterGetterpublic static class CallbackData {/*** 用户与文档的交互状态。0用户断开与文档共同编辑的连接1新用户连接到文档共同编辑2用户单击强制保存按钮*/
// IsArray()
// actions?:IActions[] null;/*** 字段已在 4.2 后版本废弃请使用 history 代替*/Object changeshistory;/*** 文档变更的历史记录仅当 status 等于 2 或者 3 时该字段才有值。其中的 serverVersion 字段也是 refreshHistory 方法的入参*/Object history;/*** 文档编辑的元数据信息用来跟踪显示文档更改记录仅当 status 等于 2 或者 2 时该字段才有值。该字段也是 setHistoryData显示与特定文档版本对应的更改类似 Git 历史记录方法的入参*/String changesurl;/*** url 字段下载的文档扩展名文件类型默认为 OOXML 格式如果启用了 assemblyFormatAsOriginhttps://api.onlyoffice.com/editors/save#assemblyFormatAsOrigin 服务器设置则文件以原始格式保存*/String filetype;/*** 文档强制保存类型。0对命令服务https://api.onlyoffice.com/editors/command/forcesave执行强制保存1每次保存完成时都会执行强制保存请求仅设置 forcesave 等于 true 时生效2强制保存请求由计时器使用服务器中的设置执行。该字段仅 status 等于 7 或者 7 时才有值*/Integer forcesavetype;/*** 文档标识符类似 id在 Onlyoffice 服务内部唯一*/String key;/*** 文档状态。1文档编辑中2文档已准备好保存3文档保存出错4文档没有变化无需保存6正在编辑文档但保存了当前文档状态7强制保存文档出错*/Integer status;/*** 已编辑文档的链接可以通过它下载到最新的文档仅当 status 等于 2、3、6 或 7 时该字段才有值*/String url;/*** 自定义参数对应指令服务的 userdata 字段*/Object userdata;/*** 打开文档进行编辑的用户标识列表当文档被修改时该字段将返回最后编辑文档的用户标识符当 status 字段等于 2 或者 6 时有值*/String[] users;/*** 最近保存时间*/String lastsave;/*** 加密令牌*/String token;}
}代码中使用了其他类这儿贴出我也是参考的别人的博客后面会给出参考链接
package com.ruoyi.web.controller.meetingminutes.utils;/*** Author 不要有情绪的 ljy* Date 2024/10/31 20:34* Description:*/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;/*** httpclient Sock5支持参考https://blog.csdn.net/weixin_34075268/article/details/92040047* author liujh**/
public class HttpsKitWithProxyAuth {private static Logger logger LoggerFactory.getLogger(HttpsKitWithProxyAuth.class);private static final int CONNECT_TIMEOUT 10000;// 设置连接建立的超时时间为10000msprivate 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;//proxyType的取值之一httppublic static final String SOCKS socks;//proxyType的取值之一socksprivate static boolean needProxy false; //是否需要代理连接private static boolean needLogin false;//代理连接是否需要账号和密码为true时填上proxyUsername和proxyPasswordprivate static String proxyType HTTP; //代理类型,http,socks分别为http代理和sock5代理private static String proxyHost 127.0.0.1; //代理IPprivate static int proxyPort 1080; //代理端口private static String proxyUsername sendi;//代理账号needLogin为true时不能为空private static String proxyPassword 123456;//代理密码needLogin为true时不能为空private static RequestConfig requestConfig RequestConfig.custom().setConnectionRequestTimeout(CONNECT_TIMEOUT).setConnectTimeout(CONNECT_TIMEOUT)//.setCookieSpec(CookieSpecs.IGNORE_COOKIES).setSocketTimeout(SOCKET_TIMEOUT).build();static {/*** Sock5代理账号和密码设置* 如果账号和密码都不为空表示需要账号密码认证,因为这个是全局生效因此在这里直接设置* 可通过Authenticator.setDefault(null)取消全局配置* Authenticator.setDefault(Authenticator a)关于a参数的说明如下* (The authenticator to be set. If a is {code null} then any previously set authenticator is removed.)*/if(needProxy SOCKS.equals(proxyType) needLogin){//用户名和密码验证Authenticator.setDefault(new Authenticator(){protected PasswordAuthentication getPasswordAuthentication(){PasswordAuthentication p new PasswordAuthentication(proxyUsername, proxyPassword.toCharArray());return p;}});}}/*** 设置代理信息可以在发请求前进行调用用于替换此类中的代理相关的变量,全局设置一次就可* needProxy 是否需要代理连接* needLogin 代理连接是否需要账号和密码为true时填上proxyUsername和proxyPassword* proxyType 代理类型,http,socks分别为http代理和sock5代理* proxyHost 代理IP* proxyPort 代理端口* proxyUsername 代理账号needLogin为true时不能为空* proxyPassword 代理密码needLogin为true时不能为空*/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) {// 多线程下多个线程同时调用getHttpClient容易导致重复创建httpClient对象的问题,所以加上了同步锁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() {Overridepublic void run() {// 关闭异常连接manager.closeExpiredConnections();// 关闭5s空闲的连接manager.closeIdleConnections(HttpIdelTimeout,TimeUnit.MILLISECONDS);//logger.info(manager.getTotalStats().toString());//logger.info(close expired and idle for over HttpIdelTimeoutms connection);}}, HttpMonitorInterval, HttpMonitorInterval, TimeUnit.MILLISECONDS);}}}return httpClient;}/*** 构建httpclient实例* return* throws KeyStoreException* throws NoSuchAlgorithmException* throws KeyManagementException*/private static CloseableHttpClient createHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {SSLContextBuilder builder new SSLContextBuilder();// 全部信任 不做身份鉴定builder.loadTrustMaterial(null, new TrustStrategy() {Overridepublic boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {return true;}});ConnectionSocketFactory plainSocketFactory null;LayeredConnectionSocketFactory sslSocketFactory null;/*** 如果需要进行Sock5代理访问开放如下代码* */if(needProxy SOCKS.endsWith(proxyType)){plainSocketFactory new MyConnectionSocketFactory();sslSocketFactory new MySSLConnectionSocketFactory(builder.build());}else {plainSocketFactory PlainConnectionSocketFactory.getSocketFactory();sslSocketFactory new SSLConnectionSocketFactory(builder.build(), NoopHostnameVerifier.INSTANCE);}RegistryConnectionSocketFactory 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() {Overridepublic boolean retryRequest(IOException e, int i, HttpContext httpContext) {if (i 3) {// 重试超过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) {// SSL握手异常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;/*** 如果需要进行HTTPS代理访问开放如下代码* */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,MapString,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,MapString,Object requestParams) {return postJson(url, JsonUtil.toJSONString(requestParams));}public static String postJson(String url,MapString,Object requestParams,MapString,String headerParams) {return postJson(url, JsonUtil.toJSONString(requestParams),headerParams);}public static String postJson(String url,String requestParamStr) {return postJson(url, requestParamStr, null);}/*** PUT方式调用http请求方法* param url* param requestParamStr* param headerParams* return*/public static String put(String url,String requestParamStr,MapString,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;}/*** 创建一个HttpClientContext* return* throws MalformedChallengeException*/public static HttpClientContext createContext() throws MalformedChallengeException{HttpClientContext ctx HttpClientContext.create();/*** 如果需要进行Sock5代理访问*/if(needProxy SOCKS.endsWith(proxyType)){InetSocketAddress socksaddr new InetSocketAddress(proxyHost,proxyPort);ctx.setAttribute(socks.address, socksaddr);}else{/*** 如果需要进行HTTPS代理访问开放如下代码*/if(needProxy HTTP.endsWith(proxyType)){/*** 代理连接认证如果需要认证账号和密码时处理*/if(needLogin){AuthState authState new AuthState();BasicScheme basicScheme new BasicScheme();basicScheme.processChallenge(new BasicHeader(AUTH.PROXY_AUTH, BASIC realmdefault));authState.update(basicScheme, new UsernamePasswordCredentials(proxyUsername, proxyPassword));ctx.setAttribute(HttpClientContext.PROXY_AUTH_STATE, authState);}}}return ctx;}public static String postJson(String url,String requestParamStr,MapString,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;}//requestParamStr---------- nametestage12public static String postFormUrlencoded(String url,String requestParamStr) {return postFormUrlencoded(url, requestParamStr ,null);}public static String postFormUrlencoded(String url,String requestParamStr,MapString,Object headerParams) {MapString,String requestParams new HashMapString,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,MapString,String requestParams) {return postFormUrlencoded(url,requestParams,null);}public static String postFormUrlencoded(String url,MapString,String requestParams,MapString,Object headerParams) {HttpPost httppost new HttpPost(url);//application/jsonhttppost.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));}}ListNameValuePair formparams new ArrayListNameValuePair();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;}//文件上传的通用方法例子测试, 除了file部分参数外写死了格外的字段参数如scene,output后台将接收到file,scene,output三个参数可以根据需求修改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;}/*** 下载文件到本地* param downloadUrl* param savePathAndName*/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();//如果path传进来是/结束的话处理一下先去掉。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);}}}/*** 下载文件到本地* param downloadUrl* param saveFileName* param savePath*/public static void downloadFile(String downloadUrl,String saveFileName,String savePath){//如果path传进来是/结束的话处理一下先去掉。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 {Overridepublic 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);}Overridepublic Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,InetSocketAddress localAddress, HttpContext context) throws IOException {// Convert address to unresolvedInetSocketAddress 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) {// You may need this verifier if target sites certificate is not securesuper(sslContext, NoopHostnameVerifier.INSTANCE);}Overridepublic 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);}Overridepublic Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,InetSocketAddress localAddress, HttpContext context) throws IOException {// Convert address to unresolvedInetSocketAddress 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://api.openai.com/v1/chat/completions;url https://www.baidu.com;System.out.println(HttpsKitWithProxyAuth.get(url));//关闭连接池正式环境中这个不要关闭HttpsKitWithProxyAuth.closeConnectionPool();}}
package com.ruoyi.web.controller.meetingminutes.utils;/*** Author 不要有情绪的 ljy* Date 2024/10/31 20:35* Description:*/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;//https://www.cnblogs.com/christopherchan/p/11071098.html
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{/*** ObjectobjectMapper是JSON操作的核心Jackson的所有JSON操作都是在ObjectobjectMapper中实现。* ObjectobjectMapper有多个JSON序列化的方法可以把JSON字符串保存File、OutputStream等不同的介质中。* writeValue(File arg0, Object arg1)把arg1转成json序列并保存到arg0文件中。* writeValue(OutputStream arg0, Object arg1)把arg1转成json序列并保存到arg0输出流中。* writeValueAsBytes(Object arg0)把arg0转成json序列并把结果输出成字节数组。* writeValueAsString(Object arg0)把arg0转成json序列并把结果输出成字符串。*/objectMapper new ObjectMapper();//对象的所有字段全部列入objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);//取消默认转换timestamps形式objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);//忽略空Bean转json的错误objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);//所有的日期格式都统一为以下的样式即yyyy-MM-dd HH:mm:ssobjectMapper.setDateFormat(new SimpleDateFormat(STANDARD_FORMAT));//忽略 在json字符串中存在但是在java对象中不存在对应属性的情况。防止错误objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);objectMapper.setTimeZone(TimeZone.getTimeZone(GMT8));objectMapper.setVisibility(PropertyAccessor.ALL, Visibility.ANY);//开启美化功能//objectMapper.enable(SerializationFeature.INDENT_OUTPUT);//解决Java8 LocalDate,LocalDateTime等序列化问题JavaTimeModule modulenew 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);}/*** 对象转Json格式字符串* param obj 对象* return Json格式字符串*/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 MapString,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;}}/*** 字符串转换为自定义对象* param str 要转换的字符串* param clazz 自定义对象的class对象* return 自定义对象*/SuppressWarnings(unchecked)public static T T castToObject(String fromValue, ClassT 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, TypeReferenceT 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, ClassT clazz){return castToObject(fromValue,clazz);}public static T T getValue(String fromValue, TypeReferenceT toValueTypeRef){return castToObject(fromValue, toValueTypeRef);}public static T T getValue(String fromValue, Class? collectionClazz, Class?... elementClazzes){return castToObject(fromValue, collectionClazz, elementClazzes);}//可通过点语法获取数据如getValue(data.list,xxxxxxxx,List.class);public static T T getValue(String key, String fromValue, ClassT clazz){MapString,Object infoMap castToObject(fromValue);if(infoMap null) return null;return getValue(key, infoMap, clazz);}//可通过点语法获取数据如getValue(data.list,xxxxxxxx,new TypeReferenceListUser(){});public static T T getValue(String key, String fromValue, TypeReferenceT toValueTypeRef){MapString,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){MapString,Object infoMap castToObject(fromValue);if(infoMap null) return null;return getValue(key, infoMap, collectionClazz, elementClazzes);}//可通过点语法获取数据如getValue(data.list,new TypeReferenceListUser(){});SuppressWarnings(rawtypes)public static T T getValue(String key, Map fromMap, ClassT clazz){try {// 首先将key进行拆分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 {// 首先将key进行拆分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, TypeReferenceT toValueTypeRef){try {// 首先将key进行拆分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;}/*** 将对像转换成具体的其他Bean对像* param fromValue* param toValueTypeRef* return*/public static T T convertValue(Object fromValue, TypeReferenceT 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, ClassT toValueType){try {return objectMapper.convertValue(fromValue, toValueType);} catch (Exception e) {logger.error(convertValue error : , e.getMessage());return null;}}public static String getString(MapString,Object fromMap, String fieldName){return fromMap.get(fieldName)null ? null : fromMap.get(fieldName).toString();}//根据filedName的key查找map为空时使用对应的defaultValue默认值替换返回public static String getString(MapString,Object jsonObject, String fieldName,String defaultValue){return jsonObject.get(fieldName)null ? defaultValue : jsonObject.get(fieldName).toString();}public static Integer getInteger(MapString,Object jsonObject, String fieldName){return jsonObject.get(fieldName)null ? null : (Integer)jsonObject.get(fieldName);}public static Double getDouble(MapString,Object jsonObject, String fieldName){return jsonObject.get(fieldName)null ? null : (Double)jsonObject.get(fieldName);}public static Boolean getBoolean(MapString,Object jsonObject, String fieldName){return jsonObject.get(fieldName)null ? false : (Boolean)jsonObject.get(fieldName);}public static Long getLong(MapString,Object jsonObject, String fieldName){return jsonObject.get(fieldName)null ? null : (Long)jsonObject.get(fieldName);}public static T ListT getList(MapString,Object jsonObject, String fieldName,ClassT clazz){return jsonObject.get(fieldName)null ? null : JsonUtil.getValue(fieldName, jsonObject, List.class,clazz);}}
3、问题总结 开始敲黑板了
3.1、访问案例失败 部署完成后可能存在很多问题例如访问example访问失败那么使用
systemctl status ds* 查看有没有启动对应的服务
3.2、加载word文档失败 修改启动的配置文件将token去除更改配置文件local.json和default.json配置文件位置 /etc/onlyoffice/documentserver local.json中将参数token都改为false去除token
token: {enable: {request: {inbox: false,outbox: false},browser: false},
default.json中将参数request-filtering-agent改为truetoken也改为falserejectUnauthorized改为false
request-filtering-agent : {allowPrivateIPAddress: true,allowMetaIPAddress: true},
token: {enable: {browser: false,request: {inbox: false,outbox: false}},
rejectUnauthorized: false 修改了以上配置参数后重启服务再次测试。 以上更基本能解决报错文档权限问题文档保存失败问题文档下载问题等报错信息。
3.3、系统后端有token验证问题 如果你访问的地址需要携带token进行token验证自己的系统后台并非onlyoffice的token那么可以通过配置下面代码的形式进行解决例如我的访问路径为http:192.168.123.123:8089/getFile/12, 去除token验证 requests.antMatchers(/callback, /getFile/*, /login, /register, /captchaImage).permitAll()
Beanprotected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {return httpSecurity// CSRF禁用因为不使用session.csrf(csrf - csrf.disable())// 禁用HTTP响应标头.headers((headersCustomizer) - {headersCustomizer.cacheControl(cache - cache.disable()).frameOptions(options - options.sameOrigin());})// 认证失败处理类.exceptionHandling(exception - exception.authenticationEntryPoint(unauthorizedHandler))// 基于token所以不需要session.sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 注解标记允许匿名访问的url.authorizeHttpRequests((requests) - {permitAllUrl.getUrls().forEach(url - requests.antMatchers(url).permitAll());// 对于登录login 注册register 验证码captchaImage 允许匿名访问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 filter.logout(logout - logout.logoutUrl(/logout).logoutSuccessHandler(logoutSuccessHandler))// 添加JWT filter.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class).addFilterBefore(corsFilter, LogoutFilter.class).build();}
3.4、使用文档地址访问问题 如果你不采用上面的方式即http:192.168.123.123:8089/getFile/12 的方式你想采用http:192.168.123.123:8089/word/1234.docx 的方式也是可以的但是你需要将文档挂到一个服务上例如使用ngnix作为代理进行/word/1234.docx的重定向。
下面你可以直接使用一个在线的docx链接来测试你是否部署成功在线链接https://d2nlctn12v279m.cloudfront.net/assets/docs/samples/zh/demo.docx
即将前端代码中的url替换为上面的链接。 4、后记 如果你看到这里那么代表你将要成功了整个过程比较艰难前后弄了两三天还好最后结果是好的所以简单总结一下勉励自己。可能有些地方没有描述清楚有让你疑惑欢迎留言call我如果比较急也可以我v 1450053947一般别打扰thankyou 后端参考博客springboot集成开源office软件onlyoffice - 知乎 (zhihu.com)
OnlyOffice文档服务器安装及集成使用_冰之杍-GitCode 开源社区 (csdn.net) 学习之所以会想睡觉是因为那是梦开始的地方。 ଘ(੭ˊᵕˋ)੭ 开心 ଘ(੭ˊᵕˋ)੭ 开心ଘ(੭ˊᵕˋ)੭ 开心ଘ(੭ˊᵕˋ)੭ 开心ଘ(੭ˊᵕˋ)੭ 开心 ------不写代码不会凸的小刘