回调处理程序
文档编辑服务用JavaScript API的callbackUrl通知文档存储服务有关文档编辑的状态。文档编辑服务使用POST 请求,请求信息在正文中。
参数
| 参数 | 类型 | 描述 |
|---|---|---|
| actions | array of object | 定义当用户对文档执行操作时接收到的对象。type字段值可以具有以下值: 0- 用户断开与文档共同编辑的连接, 1- 新用户连接到文档共同编辑, 2- 用户单击强制保存按钮。 userid字段值是用户标识符。 |
| changeshistory | array of object | 定义具有文档更改历史的对象数组。仅当status值等于2或3时,对象才存在。 必须作为对象的属性changes以参数形式发送给refreshHistory方法。自 4.2 版起已删除,请改用history。 |
| changesurl | string | 使用文档编辑数据定义文件的链接,用于跟踪和显示文档更改历史记录。仅当status等于2,3,6或7时,链接才存在。必须保存文件,并且必须使用setHistoryData方法将其地址作为changesUrl参数发送,以显示与特定文档版本对应的更改。 |
| filetype | string | 定义从url参数指定的链接下载文档的扩展名。文件类型默认为 OOXML,但如果启用了assemblyFormatAsOrigin服务器设置,则文件将以原始格式保存。 |
| forcesavetype | integer | 定义执行强制保存请求时的启动器类型。可以有以下值: 0- 为命令服务执行强制保存请求, 1- 每次保存完成时都会执行强制保存请求(例如单击保存按钮),这仅在forcesave选项设置为true时可用, 2- 强制保存请求由计时器按服务器配置中的设置执行, 3- 每次提交表单时都会执行强制保存请求Complete & Submit按钮被点击 )。 该类型仅在status值等于6或7时出现。 |
| formsdataurl | object | 用提交的表单数据,定义 JSON 文件的 URL。此处描述了包含表单数据的数组结构。当status值等于6并且forcesavetype值等于3时,该对象才存在。 |
| formsdataurl.key | string | 表单键。 如果当前表单是单选按钮,则该字段包含表单组键。 |
| formsdataurl.tag | string | 表单标签。 |
| formsdataurl.value | string | 当前表单值。 |
| formsdataurl.type | string | 表单类型 (text,checkBox,picture,comboBox,dropDownList,dateTime,radio)。 |
| history | object | 定义有文档更改历史的对象。仅当status值等于2或3时,对象才存在。它包含对象changes和serverVersion,它们必须作为对象的属性changes和serverVersion以参数形式发送给refreshHistory方法。 |
| key* | string | 定义编辑的文档标识符。 |
| status* | integer | 定义文档的状态。可以有以下值: 1- 正在编辑文档, 2- 文档已准备好保存, 3- 发生文档保存错误, 4- 文档已关闭,没有任何更改, 6- 正在编辑文档,但保存了当前文档状态, 7- 强制保存文档时发生错误。 |
| url | string | 定义已编辑的要由文档存储服务保存的文档的链接。仅当status值等于2,3,6或7时,链接才存在。 |
| userdata | string | 定义发送到forcesave和info命令的命令服务的自定义信息(如果它在请求中存在)。 |
| users | array of string | 定义打开文档进行编辑的用户的标识符列表;当文档被更改时,用户将返回最后编辑文档的用户的标识符(对于status2和status6的应答)。 |
*- 必需参数
服务器存储所有callbackUrl,并根据执行操作的用户选择使用哪一个。
状态 1
每次用户连接或断开文档共同编辑时都会收到它。他们的callbackUrl被使用。
请注意,当用户在遭遇Internet 故障后返回到没有更改的文档时也可以收到status1。这种情况可以描述如下:
- 当用户打开文档时,会发送 status 1。
- 如果 Internet 连接丢失并且用户没有对文档进行任何更改,则会发送 status 4。屏幕上显示错误并在查看器中打开文档。
- 在 100 秒内,互联网连接恢复,用户重新连接到文档并再次发送 status 1。
- 现在用户可以继续编辑文档。将收到 status 2 或 4,具体取决于用户是否对文档进行了任何更改。
状态 2/3
它在编辑文档关闭后10 秒收到,该用户的标识符是最后一个将更改发送到文档编辑服务的用户。对文件进行最后更改的用户的callbackUrl被使用。
状态 4
它是在最后一个用户关闭所编辑的没有更改的文档情况下收到的。他们的callbackUrl被使用。
状态 6/7
在执行强制保存请求时接收到。callbackUrl依赖于forcesavetype参数:
- 如果 forcesavetype 参数设置为 1,则使用点击 保存 按钮的用户的 callbackUrl。
- 如果 forcesavetype 参数设置为 0 或 2,则使用对文件进行最后更改的用户的 callbackUrl。
- 如果 forcesavetype 参数设置为3,则使用点击 提交 按钮的用户的 callbackUrl。
请求示例
当两个用户共同编辑文档时,文档编辑服务发送到"callbackUrl"地址的 JSON 对象示例
{
"actions": [
{"type": 1,
"userid": "78e1e841"}
],
"key": "Khirz6zTPdfd7",
"status": 1,
"users": ["6d5a81d0", "78e1e841"]
}
当用户更改文档并关闭编辑的文档时,文档编辑服务发送到"callbackUrl"地址的 JSON 对象示例
{
"actions": [
{
"type": 0,
"userid": "78e1e841"
}
],
"changesurl": "https://documentserver/url-to-changes.zip",
"history": {
"changes": changes,
"serverVersion": serverVersion
},
"filetype": "docx",
"key": "Khirz6zTPdfd7",
"status": 2,
"url": "https://documentserver/url-to-edited-document.docx",
"users": ["6d5a81d0"]
}
当最后一个用户关闭编辑的无更改的文档时,文档编辑服务发送到 "callbackUrl" 地址的 JSON 对象示例
{
"key": "Khirz6zTPdfd7",
"status": 4
}
接收到forcesave命令后文档编辑服务发送到 "callbackUrl" 地址的JSON对象示例
{
"changesurl": "https://documentserver/url-to-changes.zip",
"forcesavetype": 0,
"history": {
"changes": changes,
"serverVersion": serverVersion
},
"filetype": "docx",
"key": "Khirz6zTPdfd7",
"status": 6,
"url": "https://documentserver/url-to-edited-document.docx",
"users": ["6d5a81d0"],
"userdata": "sample userdata"
}
来自文档存储服务的响应
文档存储服务必须返回以下响应,否则文档编辑器将显示错误消息:
{
"error": 0
}
文档保存示例
- .Net (C#)
public class WebEditor : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string body;
using (var reader = new StreamReader(context.Request.InputStream))
body = reader.ReadToEnd();
var fileData = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(body);
if ((int) fileData["status"] == 2)
{
var req = WebRequest.Create((string) fileData["url"]);
using (var stream = req.GetResponse().GetResponseStream())
using (var fs = File.Open(PATH_FOR_SAVE, FileMode.Create))
{
var buffer = new byte[4096];
int readed;
while ((readed = stream.Read(buffer, 0, 4096)) != 0)
fs.Write(buffer, 0, readed);
}
}
context.Response.Write("{\"error\":0}");
}
}
- Java
public class IndexServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter writer = response.getWriter();
Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\\A");
String body = scanner.hasNext() ? scanner.next() : "";
JSONObject jsonObj = (JSONObject) new JSONParser().parse(body);
if((long) jsonObj.get("status") == 2)
{
String downloadUri = (String) jsonObj.get("url");
URL url = new URL(downloadUri);
java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
InputStream stream = connection.getInputStream();
File savedFile = new File(pathForSave);
try (FileOutputStream out = new FileOutputStream(savedFile)) {
int read;
final byte[] bytes = new byte[1024];
while ((read = stream.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
out.flush();
}
connection.disconnect();
}
writer.write("{\"error\":0}");
}
}
- Node.js
import {fs} from "node:fs";
import {syncRequest} from "sync-request";
app.post("/track", (req, res) => {
function updateFile(response, body, path) {
if (body.status === 2) {
const file = syncRequest("GET", body.url);
fs.writeFileSync(path, file.getBody());
}
response.write("{\"error\":0}");
response.end();
}
function readbody(request, response, path) {
let content = "";
request.on("data", (data) => {
content += data;
});
request.on("end", () => {
const body = JSON.parse(content);
updateFile(response, body, path);
});
}
if (req.body.hasOwn("status")) {
updateFile(res, req.body, pathForSave);
} else {
readbody(req, res, pathForSave);
}
});
- PHP
<?php
if (($body_stream = file_get_contents("php://input"))===FALSE){
echo "Bad Request";
}
$data = json_decode($body_stream, TRUE);
if ($data["status"] == 2){
$downloadUri = $data["url"];
if (($new_data = file_get_contents($downloadUri))===FALSE){
echo "Bad Response";
} else {
file_put_contents($path_for_save, $new_data, LOCK_EX);
}
}
echo "{\"error\":0}";
?>
- Ruby
class ApplicationController < ActionController::Base
def index
body = request.body.read
file_data = JSON.parse(body)
status = file_data["status"].to_i
if status == 2
download_uri = file_data["url"]
uri = URI.parse(download_uri)
http = Net::HTTP.new(uri.host, uri.port)
if download_uri.start_with?("https")
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
req = Net::HTTP::Get.new(uri.request_uri)
res = http.request(req)
data = res.body
File.open(path_for_save, "wb") do |file|
file.write(data)
end
end
render :text => "{\"error\":0}"
end
end