ONLYOFFICE Api Documentation - Basic concepts
前端初化时是可以修改 document 对像里的key的,可以根据业务要求自己定义。
说明:
上图是第一次解决 【提示:这份文件无法保存。请检查连接设置或联系你的管理员。当你点击ok按钮,系统将提示你下载文档。】这个提示时的方法和经验。
经过对onlyoffice的开发不断增加对onlyoffice的理解也更多了。这个问题的主要原因是onlyoffice服务端无法调用我们传给onlyoffice服务器的回调地址或是我们传的回调接口返回的值是onlyoffice服务端无法识别的。如文档中要求接口返回{\"error\":0}表示回调成功。结果我们的接口没有返回或返回的值不是{\"error\":0},这时onlyoffice服务端就是认为接口无法保存文档,这时这个提示就出来了。
本文的接口是按onlyoffice文档示例写的。使用net core也是可以写这样一个接口的,但写法有点不一样。想了解这个版本的回调接口代码可以看我的另一个博文地址如下。
https://www.cnblogs.com/stevenchen2016/p/17524781.html
1、回调接口问题
C# 一定要以.net web 项目里的[一般处理程序]CallbackApiHandler.ashx 方式来处理。
具体代码
using LDOnlyOfficeWebApi.Comm;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Script.Serialization;
using LDOnlyOffice.Models;
namespace LDOnlyOfficeWebApi
{
///
/// 回调接口 CallbackApiHandler 的摘要说明
///
public class CallbackApiHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
//context.Response.AddHeader("Access-Control-Allow-Origin", "*");
//context.Response.AddHeader("Access-Control-Allow-Headers", " x-www-form-urlencoded, Content-Type,x-requested-with");
//context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
string errorMsg = string.Empty;
string body = string.Empty;
try
{
body = HttpContextHelper.GetRequestBody(context);
if (!string.IsNullOrEmpty(body))
{
var fileData = new JavaScriptSerializer().Deserialize(body);
errorMsg = "try 0";
if (fileData != null && (fileData.status == 2 || fileData.status == 6))
{
try
{
errorMsg = "try 1";
var sFilePath = FileHelper.GetFilePath(fileData.key);
string sFileName = string.Format("{0}.docx", fileData.key);
var PATH_FOR_SAVE = sFilePath + "/" + sFileName; //文件的绝对路径
var req = WebRequest.Create(fileData.url);
errorMsg = "try 2";
using (var stream = req.GetResponse().GetResponseStream())
{
errorMsg = "try 3";
using (var fs = File.Open(PATH_FOR_SAVE, FileMode.Create))
{
var buffer = new byte[4096];
int readed;
errorMsg = "try 4";
while ((readed = stream.Read(buffer, 0, 4096)) != 0)
{
fs.Write(buffer, 0, readed);
errorMsg = "try 5";
}
errorMsg += "try 6";
}
}
}
catch (Exception ex)
{
errorMsg = "异常2:" + ex.Message;
}
}
}
}
catch (Exception ex)
{
errorMsg = "异常1:" + ex.Message;
}
LogHelper.WriteLog("接口异常信息:" + errorMsg + ",收到的请求参数" + body);
context.Response.Write("{\"error\":0}");//0表示回调成功,不能修改
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
提示:这份文件无法保存。请检查连接设置或联系你的管理员。当你点击ok按钮,系统将提示你下载文档。
还是回调接口的问题。context.Response.Write("{\"error\":0}");//0表示回调成功,接口最后返回值 不能修改。否则onlyoffice会认为回调失败。
2、保存成功后要能打开上次保存的文件
这里以回调接口的KEY做为保存文件名。前端生成一个KEY传到接口来获取文档地址。有的话返回上次保存的地址。没有的就返回默认的空文档地址
具体代码如下
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Script.Serialization;
using LDOnlyOfficeWebApi.Comm;
using LDOnlyOffice.Models;
namespace LDOnlyOfficeWebApi
{
///
/// 获取文件接口 GetFileHandler 的摘要说明
///
public class GetFileHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
//context.Response.ContentType = "text/plain";
//context.Response.AddHeader("Access-Control-Allow-Origin", "*");
//context.Response.AddHeader("Access-Control-Allow-Headers", " x-www-form-urlencoded, Content-Type,x-requested-with");
//context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
var rspModel = new GetFileRspModel();
rspModel.message = "操作失败";
string body = string.Empty;
try
{
body = HttpContextHelper.GetRequestBody(context);
if (!string.IsNullOrEmpty(body))
{
var reqModel = new JavaScriptSerializer().Deserialize(body);
if (reqModel != null)
{
if (!string.IsNullOrEmpty(reqModel.Key))
{
rspModel.data = FileHelper.GetFileByKey(reqModel.Key);
rspModel.success = true;
rspModel.message = "操作成功";
}
else
{
rspModel.message = "Key不能为空";
}
}
}
}
catch (Exception ex)
{
rspModel.message = ex.Message;
rspModel.success = false;
}
var responseJson = new JavaScriptSerializer().Serialize(rspModel);
LogHelper.WriteLog("GetFileHandler() 请求参数:" + body+",响应参数:"+ responseJson);
context.Response.Write(responseJson);
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
实体层
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace LDOnlyOffice.Models
{
///
/// onlyoffice 回调处理程序 参数模型
///
public class CallbackApiReqModel
{
public CallbackApiReqModel()
{
Actions = new List();
history = new List();
users = new List();
}
///
/// 如果连接到文档的新用户共同编辑或与其断开连接,则定义收到的对象。在第一种情况下,类型字段值为1,在另一种情况下为0。的用户ID字段值是谁连接到或从共同编辑文件断开的用户的标识符。
///
public List Actions { get; set; }
//public List changeshistory { get; set; }
///
/// 使用用于跟踪和显示文档更改历史记录的文档编辑数据定义文件的链接。
/// 当状态值仅等于2或3时,链接存在。必须保存该文件,并且必须使用setHistoryData方法将其地址作为changesUrl参数发送,以显示与特定文档版本对应的更改。
///
public string changesurl { get; set; }
///
/// 执行强制保存请求时定义启动器的类型。可以具有以下值:
/// * 0 - 对命令服务执行强制保存请求,
/// * 1 - 每次保存时执行强制保存请求(例如,单击“ 保存”按钮),仅当forceave选项设置为true时才可用。
/// * 2 - 使用服务器配置中的设置由计时器执行强制保存请求。
/// 当状态值仅等于6或7时,存在类型。
///
public int forcesavetype { get; set; }
///
/// 定义编辑的文档标识符。
///
public string key { get; set; }
///
/// 定义文档的状态。可以具有以下值:
/// * 0 - 找不到带有密钥标识符的文档,
/// * 1 - 正在编辑文档,
/// * 2 - 文件已准备好保存,
/// * 3 - 发生了文档保存错误,
/// * 4 - 文件关闭,没有变化,
/// * 6 - 正在编辑文档,但保存当前文档状态,
/// * 7 - 强制保存文档时发生错误。
///
public int status { get; set; }
///
/// 定义要与文档存储服务一起保存的已编辑文档的链接。
/// 当状态值仅等于2或3时,链接存在。
///
public string url { get; set; }
///
/// 定义发送到命令服务的自定义信息,以防它出现在请求中。
///
public List users { get; set; }
///
/// 使用文档更改历史记录定义对象。当状态值仅等于2或3时,该对象存在。它包含对象serverVersion和变化,它们必须作为属性被发送serverVersion和变化发送作为参数传递给该对象的refreshHistory方法。
///
public List history { get; set; }
///
/// 定义打开文档进行编辑的用户的标识符列表;
/// 当文档被更改时,用户将返回最后一个编辑文档的用户的标识符(状态2和状态6回复)。
///
public string userdata { get; set; }
}
public class CallbackApiActionModel
{
public int type { get; set; }
public string userid { get; set; }
}
public class CallbackApiHistoryModel
{
public string changes { get; set; }
public string serverVersion { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace LDOnlyOffice.Models
{
///
/// 获取文件接口 请求参数 实体
///
public class GetFileReqModel
{
///
/// 可以是文件ID
///
public string Key { get; set; }
///
/// 创建用户ID
///
public string CreateUserId { get; set; }
///
/// 文件名称
///
//public string FielName { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace LDOnlyOffice.Models
{
///
/// 获取文件 接口响应参数 实体
///
public class GetFileRspModel
{
public bool success { get; set; }
///
/// 操作结果类型
///
public object state { get; set; }
///
/// 获取 消息内容
///
public string message { get; set; }
///
/// 获取 返回数据
///
public object data { get; set; }
}
}
帮助类
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Web;
namespace LDOnlyOfficeWebApi.Comm
{
///
/// 文件帮助类
///
public class FileHelper
{
public static string GetFilePath(string key)
{
//var filePath = string.Format("{0}Files\\{1}", AppDomain.CurrentDomain.BaseDirectory, key);
var filePath = string.Format("{0}Files", AppDomain.CurrentDomain.BaseDirectory);
if (!Directory.Exists(filePath))//验证路径是否存在
{
Directory.CreateDirectory(filePath);
//不存在则创建
}
return filePath;
}
///
/// 获取相对文件路径
///
///
///
public static string GetFileByKey(string key)
{
var filePath = GetFilePath(key);
var fileAllName = string.Format("{0}\\{1}.docx", filePath, key);
var relativePaths = string.Format("Files/{0}.docx", key);
if (!File.Exists(fileAllName))//验证路径是否存在
{
//不存在则返回默认文档
relativePaths = "Files/default.docx";
}
return relativePaths;
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
namespace LDOnlyOfficeWebApi.Comm
{
public class HttpContextHelper
{
///
/// 获取请求参数
///
///
///
public static string GetRequestBody(HttpContext context)
{
var body = string.Empty;
using (var reader = new StreamReader(context.Request.InputStream))
{
body = reader.ReadToEnd();
}
return body;
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
namespace LDOnlyOfficeWebApi.Comm
{
///
/// 日志帮助类
///
public class LogHelper
{
///
/// 写日志
///
///
public static void WriteLog(string strLog)
{
//H:\stevenchen\LDProject\WebApplication3\WebApplication3
string sFilePath = AppDomain.CurrentDomain.BaseDirectory + DateTime.Now.ToString("yyyyMM");
string sFileName = "rizhi" + DateTime.Now.ToString("dd") + ".log";
sFileName = sFilePath + "\\" + sFileName; //文件的绝对路径
if (!Directory.Exists(sFilePath))//验证路径是否存在
{
Directory.CreateDirectory(sFilePath);
//不存在则创建
}
FileStream fs = null;
try
{
if (File.Exists(sFileName))
//验证文件是否存在,有则追加,无则创建
{
fs = new FileStream(sFileName, FileMode.Append, FileAccess.Write);
}
else
{
fs = new FileStream(sFileName, FileMode.Create, FileAccess.Write);
}
using (StreamWriter sw = new StreamWriter(fs))
{
sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + " --- " + strLog);
}
}
catch (Exception ex)
{
}
finally
{
if (fs != null)
{
fs.Close();
}
}
}
}
}
测试项目
using LDOnlyOffice.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace TestConsoleApp
{
internal class Program
{
static void Main(string[] args)
{
var responseJosn = exeGetFileApi();
//var responseJosn = exeCallbackApi();
Console.WriteLine(responseJosn);
}
public static string exeCallbackApi()
{
// {
// "key": "asdsadnsajdksakj",
//"status": 6,
//"url": "http://192.168.1.17:8011/cache/files/asdsadnsajdksakj_8672/output.docx/output.docx?md5=ce5Rc3-EZjFyGZi-2G2wvQ&expires=1661516286&filename=output.docx",
//"changesurl": "http://192.168.1.17:8011/cache/files/asdsadnsajdksakj_8672/changes.zip/changes.zip?md5=7OqYLM7zjubbq_Qa4Yhwuw&expires=1661516286&filename=changes.zip",
//"history": {
// "serverVersion": "6.4.2",
//"changes": [{
// "created": "2022-08-26 11:52:48",
//"user": {
// "id": "uid-1660548530924",
//"name": "请问 (Guest)"
// }
// }, {
// "created": "2022-08-26 12:02:43",
//"user": {
// "id": "uid-1661484841786",
//"name": "匿名"
// }
// }]
//},
//"users": ["uid-1661484841786"],
//"actions": [{
// "type": 2,
//"userid": "uid-1661484841786"
// }],
//"lastsave": "2022-08-26T12:03:00.000Z",
//"forcesavetype": 1
//}
// {
// "key": "e092c57f08e1178a2517",
//"status": 6,
//"url": "http://192.168.1.17:8011/cache/files/e092c57f08e1178a2517_3326/output.docx/output.docx?md5=vOcGuNRbJ8S3m7blxMf-Dg&expires=1661427973&filename=output.docx",
//"changesurl": "http://192.168.1.17:8011/cache/files/e092c57f08e1178a2517_3326/changes.zip/changes.zip?md5=ozAoIjcgYIDPmSM-JtgxfQ&expires=1661427973&filename=changes.zip",
//"history": {
// "serverVersion": "6.4.2",
//"changes": [{
// "created": "2022-08-25 11:30:20",
//"user": {
// "id": "uid-1660548530924",
//"name": "请问 (Guest)"
// }
// }]
//},
//"users": ["uid-1660548530924"],
//"actions": [{
// "type": 2,
//"userid": "uid-1660548530924"
// }],
//"lastsave": "2022-08-25T11:31:10.000Z",
//"forcesavetype": 1
//}
var requestModel = new CallbackApiReqModel()
{
forcesavetype = 0,
Actions = new List() {
new CallbackApiActionModel { type = 0, userid = "78e1e841" }
},
changesurl = "http://192.168.1.17:8011/cache/files/e092c57f08e1178a2517_3326/changes.zip/changes.zip?md5=ozAoIjcgYIDPmSM-JtgxfQ&expires=1661427973&filename=changes.zip",
history = new List() {
new CallbackApiHistoryModel() { changes = "changes", serverVersion = "serverVersion" }
},
key = "asdsadnsajdksakj",
status = 2,
url = "http://192.168.1.17:8011/cache/files/asdsadnsajdksakj_8672/output.docx/output.docx?md5=ce5Rc3-EZjFyGZi-2G2wvQ&expires=1661516286&filename=output.docx",
users = new List() { "6d5a81d0" },
userdata = "sample userdata"
};
//var url = "http://192.168.1.17:8088/CallbackApiHandler.ashx";
var url = "https://localhost:44347/CallbackApiHandler.ashx";
var parm = new ServiceParam()
{
ServiceUrl = url,
PostData = JsonConvert.SerializeObject(requestModel)
};
var errorMsg = string.Empty;
var responseJosn = HttpHelper.Post(parm, ref errorMsg);
return responseJosn;
}
public static string exeGetFileApi()
{
var requestModel = new GetFileReqModel()
{
Key = "001"
};
//var url = "http://192.168.1.17:8088/GetFileHandler.ashx";
var url = "https://localhost:44347/GetFileHandler.ashx";
var parm = new ServiceParam()
{
ServiceUrl = url,
PostData = JsonConvert.SerializeObject(requestModel)
};
var errorMsg = string.Empty;
var responseJosn = HttpHelper.Post(parm, ref errorMsg);
return responseJosn;
}
}
}
using System.IO;
using System.Net.Security;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System;
namespace TestConsoleApp
{
///
/// HTTP请求帮助类
///
public class HttpHelper
{
private const int HttpMaxLimit = 100;
#region 提交POST请求
///
/// 提交POST请求
///
/// 请求参数
///
public static string Post(ServiceParam param)
{
var errorMsg = string.Empty;
return Post(param, ref errorMsg);
}
///
/// 提交POST请求
///
/// 请求参数
///
public static string Post(ServiceParam param, ref string errorMsg)
{
var responseContent = string.Empty;
if (param != null && !string.IsNullOrEmpty(param.ServiceUrl))
{
#region 调用接口
//请求
HttpWebRequest request = null;
//响应
HttpWebResponse response = null;
try
{
var postData = param.PostData;
byte[] data;
if (param.RequestEncoding.Equals(Encoding.GetEncoding("gb2312")))
{
data = Encoding.GetEncoding("gb2312").GetBytes(postData);
}
else if (param.RequestEncoding.Equals(Encoding.GetEncoding("GBK")))
{
data = Encoding.GetEncoding("GBK").GetBytes(postData);
}
else if (param.RequestEncoding.Equals(Encoding.GetEncoding("ASCII")))
{
data = Encoding.ASCII.GetBytes(postData);
}
else
{
data = Encoding.UTF8.GetBytes(postData);
}
request = WebRequest.Create(param.ServiceUrl) as HttpWebRequest;
if (request != null)
{
#region 请求接口
SetConnectionLimit(param.ServiceUrl);
request.KeepAlive = false;//关闭TCP的长连接
request.ServicePoint.Expect100Continue = false;
request.ServicePoint.UseNagleAlgorithm = false;
request.Headers.Clear();//清除http请求头信息
request.Timeout = param.TimeOut;//超时时间10秒
request.Method = "POST";//POST方式提交
//request.ContentType = "text/xml;charset=utf-8"; //编码格式utf-8
// application/xml;charset=utf-8
string contentType = "application/x-www-form-urlencoded;";
request.ContentType = contentType;
if (!string.IsNullOrEmpty(param.ContentType) && !param.ContentType.Equals(request.ContentType))
{
request.ContentType = param.ContentType;
}
request.ContentLength = data.Length;
//是否使用证书
//if (param.IsUseCert && !string.IsNullOrEmpty(param.CertPath) && !string.IsNullOrEmpty(param.CertPassword))
//{
// string path = HttpContext.Current.Request.PhysicalApplicationPath;
// var cert = new X509Certificate2(path + param.CertPath, param.CertPassword);
// request.ClientCertificates.Add(cert);
//}
using (Stream datasteam = request.GetRequestStream())
{
datasteam.Write(data, 0, data.Length);
}
#endregion
#region 获取接口返回
response = request.GetResponse() as HttpWebResponse;
using (var reader = new StreamReader(response.GetResponseStream(), param.ResponseEncoding))
{
responseContent = reader.ReadToEnd();
}
#endregion
}
}
catch (WebException wex)
{
errorMsg = wex.Message;
}
catch (Exception ex)
{
errorMsg = ex.Message;
}
finally
{
if (request != null)
{
request.Abort();
}
if (response != null)
{
response.Close();
}
}
#endregion
}
return responseContent;
}
#endregion
#region 提交Get请求
///
/// 提交Get请求 SetConnectionLimit(param.ServiceUrl, isSetMaxLimit);
///
/// 请求参数
///
public static string Get(ServiceParam param, bool isSetMaxLimit = false)
{
var errorMsg = string.Empty;
return Get(param, ref errorMsg, isSetMaxLimit);
}
///
/// 提交Get请求
///
/// 请求参数
///
public static string Get(ServiceParam param, ref string errorMsg, bool isSetMaxLimit = false)
{
var responseContent = string.Empty;
if (param != null && !string.IsNullOrEmpty(param.ServiceUrl))
{
#region 调用接口
HttpWebRequest request = null;
HttpWebResponse response = null;
try
{
string serviceGetUrl = string.IsNullOrEmpty(param.PostData) ? param.ServiceUrl : string.Format("{0}?{1}", param.ServiceUrl, param.PostData);
设置最大并发连接数 改到配置文件中去
//ServicePointManager.DefaultConnectionLimit = 512;
设置https验证方式
//if (param.ServiceUrl.StartsWith("https", StringComparison.OrdinalIgnoreCase))
//{
// ServicePointManager.ServerCertificateValidationCallback =
// new RemoteCertificateValidationCallback(CheckValidationResult);
//}
SetConnectionLimit(param.ServiceUrl);
request = WebRequest.Create(serviceGetUrl) as HttpWebRequest;
if (request != null)
{
if (!string.IsNullOrEmpty(param.ContentType))
{
request.ContentType = param.ContentType;
}
request.KeepAlive = false;//关闭TCP的长连接
request.Timeout = param.TimeOut; //超时时间10秒
response = request.GetResponse() as HttpWebResponse;
using (var reader = new StreamReader(response.GetResponseStream(), param.ResponseEncoding))
{
responseContent = reader.ReadToEnd();
}
}
}
catch (WebException wex)
{
errorMsg = wex.Message;
}
catch (Exception ex)
{
errorMsg = ex.Message;
}
finally
{
if (request != null)
{
request.Abort();
}
if (response != null)
{
response.Close();
}
}
#endregion
}
return responseContent;
}
///
/// 设置最大连接数
///
/// 请求URL
/// 是否设置最大并发连接数
private static void SetConnectionLimit(string url, bool isSetMaxLimit = true, int maxLimit = HttpMaxLimit)
{
if (isSetMaxLimit)
{
//设置最大连接数
ServicePointManager.DefaultConnectionLimit = maxLimit;
}
//设置https验证方式
if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
ServicePointManager.ServerCertificateValidationCallback =
new RemoteCertificateValidationCallback(CheckValidationResult);
}
}
public static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
//直接确认,否则打不开
return true;
}
#endregion
}
#region 请求接口的参数
///
/// 请求接口的参数
///
public class ServiceParam
{
public ServiceParam()
{
RequestEncoding = Encoding.UTF8;
ResponseEncoding = Encoding.UTF8;
LogPrefix = string.Empty;
PostData = string.Empty;
TimeOut = 30000;
}
///
/// 请求参数
///
public string PostData { get; set; }
///
/// 请求地址
///
public string ServiceUrl { get; set; }
///
/// 请求内容类型
///
public string ContentType { get; set; }
///
/// 超时时间(毫秒),默认30000毫秒
///
public int TimeOut { get; set; }
///
/// 记录日志前缀,可以写入说明性文字放在记录前,非必传
///
public string LogPrefix { get; set; }
///
/// 响应的编码方式,默认UTF-8
///
public Encoding ResponseEncoding { get; set; }
///
/// 请求的编码方式,默认UTF-8
///
public Encoding RequestEncoding { get; set; }
///
/// 是否使用证书
///
public bool IsUseCert { get; set; }
///
/// 证书
///
public string CertPath { get; set; }
///
/// 证书密码
///
public string CertPassword { get; set; }
}
#endregion
}
onlyOffice相关文章
1.解决Onlyoffice文件版本发生变化的提示问题 和 使用动态key后无法多人协同编辑示问题
2.onlyoffice 开发问题整理. 提示:这份文件无法保存。请检查连接设置或联系你的管理员。当你点击ok按钮,系统将提示你下载文档。
3.OnlyOffice文件回调接口net core 版本