刘光辉
昨天 bb638871a7fb692d80f1b7a758f991dc0879002c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package jnpf.bizcommon.onlyoffice.controller;
 
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jnpf.base.ActionResult;
import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeCallbackParam;
import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeConfigParam;
import jnpf.bizcommon.onlyoffice.service.OnlyOfficeService;
import jnpf.exception.DataException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.Collections;
import java.util.Map;
 
/**
 * OnlyOffice 文档编辑。经网关 /api/biz/onlyoffice/**(StripPrefix=2)落到本类。
 *
 * <p>⚠️ 本类下的端点鉴权形态不统一,新增端点前先想清楚属于哪一类:
 * <ul>
 *   <li>{@code /config} —— 需登录态。它会下发 securityKey,**不可放进网关白名单**;</li>
 *   <li>{@code /callback} —— 在网关白名单内(平台补丁 #9),匿名可达,
 *       安全边界是 OnlyOffice 回调 JWT 的验签,见里程碑 2。</li>
 * </ul>
 */
@Slf4j
@Tag(name = "OnlyOffice 文档编辑", description = "编辑器配置签发与保存回调")
@RestController
@RequestMapping("/onlyoffice")
@RequiredArgsConstructor
public class OnlyOfficeController {
 
    private final OnlyOfficeService onlyOfficeService;
 
    /**
     * 签发编辑器配置。前端拿返回值直接喂 {@code new DocsAPI.DocEditor(id, config)}。
     *
     * <p>入参里的 mode 只是期望值:格式不可编辑、他人正持锁、上次保存失败三种情况下,
     * 后端会降级为只读,以实际返回的 editorConfig.mode 为准。
     */
    @Operation(summary = "签发编辑器配置")
    @GetMapping("/config")
    public ActionResult<Map<String, Object>> config(OnlyOfficeConfigParam param) {
        try {
            return ActionResult.success(onlyOfficeService.buildEditorConfig(param));
        } catch (DataException e) {
            log.warn("签发 OnlyOffice 配置失败:{}", e.getMessage());
            return ActionResult.fail(e.getMessage());
        }
    }
 
    @Operation(summary = "查询文档保存状态")
    @GetMapping("/save-status")
    public ActionResult<Map<String, Object>> saveStatus(@RequestParam String fileId) {
        try {
            return ActionResult.success(onlyOfficeService.getSaveStatus(fileId));
        } catch (DataException e) {
            return ActionResult.fail(e.getMessage());
        }
    }
 
    /**
     * DS 保存回调。**匿名可达**(路径在网关白名单内,平台补丁 #9),
     * 安全边界是 Service 内对回调 JWT 的验签。
     *
     * <p>返回值格式是 OnlyOffice 定死的 {@code {"error":0}},不能套 ActionResult。
     * 且**无论处理成功与否都返回 0**——返回非 0 会让 DS 认为保存失败并无限重试,
     * 而多数失败(会话不存在、验签不过)重试多少次都不会变好。真正的问题靠日志暴露。
     */
    @Operation(summary = "DS 保存回调")
    @PostMapping("/callback")
    public Map<String, Object> callback(@RequestBody OnlyOfficeCallbackParam param,
                                        @RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization) {
        try {
            onlyOfficeService.handleCallback(param, authorization);
        } catch (DataException e) {
            log.warn("OnlyOffice 回调处理失败,key={} status={}:{}", param.getKey(), param.getStatus(), e.getMessage());
        } catch (Exception e) {
            log.error("OnlyOffice 回调处理异常,key={} status={}", param.getKey(), param.getStatus(), e);
        }
        return Collections.singletonMap("error", 0);
    }
}