刘光辉
3 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package jnpf.bizcommon.onlyoffice.service.impl;
 
import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeConfigParam;
import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeProperties;
import jnpf.exception.DataException;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
 
import java.net.URI;
import java.net.URISyntaxException;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
 
@Component
@RequiredArgsConstructor
public class OnlyOfficePluginResolver {
 
    private static final Pattern PATH_COMPONENT_PATTERN = Pattern.compile("[A-Za-z0-9._-]+");
 
    private final OnlyOfficeProperties properties;
 
    public List<ResolvedPlugin> resolve(OnlyOfficeConfigParam param) {
        if (UploadEditSceneConfigBuilder.SCENE.equals(param.getBizScene())
                && UploadEditSceneConfigBuilder.MODULE.equals(param.getBizModule())) {
            return java.util.Collections.emptyList();
        }
        // 流程受控预览无需插件;查看权限仍由专属场景 Builder 和回源票据校验。
        if (WorkflowFilePreviewConfigBuilder.SCENE.equals(param.getBizScene())
                && WorkflowFilePreviewConfigBuilder.MODULE.equals(param.getBizModule())) {
            return java.util.Collections.emptyList();
        }
        if (ObjectUtils.isEmpty(param.getBizScene())) {
            return java.util.Collections.emptyList();
        }
 
        OnlyOfficeProperties.PluginSceneMapping mapping = properties.getPlugins().getScenes().stream()
                .filter(item -> param.getBizScene().equals(item.getBizScene()))
                .findFirst()
                .orElse(null);
        if (mapping == null) {
            if (properties.getPlugins().getNonPluginScenes().contains(param.getBizScene())) {
                return java.util.Collections.emptyList();
            }
            throw new DataException("未配置的 OnlyOffice 插件场景:" + param.getBizScene());
        }
        if (!mapping.getBizModule().equals(param.getBizModule())) {
            throw new DataException("OnlyOffice 插件场景与业务模块不匹配");
        }
        if (ObjectUtils.isEmpty(param.getBizDataId()) && !isDmsQianpiye(param)) {
            throw new DataException("启用 OnlyOffice 插件时 bizDataId 不能为空");
        }
 
        if (ObjectUtils.isEmpty(mapping.getPluginCodes())) {
            throw new DataException("OnlyOffice 插件场景未配置插件");
        }
        String autostartPluginCode = mapping.getAutostartPluginCode();
        if (!ObjectUtils.isEmpty(autostartPluginCode)
                && !mapping.getPluginCodes().contains(autostartPluginCode)) {
            throw new DataException("OnlyOffice 自动启动插件未包含在当前场景中");
        }
        List<ResolvedPlugin> resolved = new ArrayList<>();
        Set<String> guids = new HashSet<>();
        for (String pluginCode : mapping.getPluginCodes()) {
            if (ObjectUtils.isEmpty(pluginCode) || !PATH_COMPONENT_PATTERN.matcher(pluginCode).matches()) {
                throw new DataException("OnlyOffice 插件编码配置无效");
            }
            OnlyOfficeProperties.PluginDefinition definition = properties.getPlugins().getDefinitions().get(pluginCode);
            if (definition == null) {
                throw new DataException("OnlyOffice 插件定义不存在:" + pluginCode);
            }
            String guid = definition.getGuid();
            if (ObjectUtils.isEmpty(guid) || !guids.add(guid)) {
                throw new DataException("OnlyOffice 插件 GUID 配置无效或重复");
            }
            String version = definition.getVersion();
            String release = definition.getRelease();
            if (!validPathComponent(version) || !validPathComponent(release)) {
                throw new DataException("OnlyOffice 插件版本或 release 配置无效");
            }
            String baseUrl = normalizedUrl(definition.getPublicBaseUrl(), "public-base-url");
            String apiBaseUrl = normalizedUrl(definition.getApiBaseUrl(), "api-base-url");
            resolved.add(new ResolvedPlugin(pluginCode, guid, version,
                    baseUrl + "/" + release + "/" + pluginCode + "/config.json",
                    apiBaseUrl, mapping.getBizScene(), mapping.getBizModule(),
                    pluginCode.equals(autostartPluginCode), mapping.getDisabledPluginGuids()));
        }
        return resolved;
    }
 
    public List<ResolvedPlugin> resolveUploadQianpiyeAnnotator() {
        OnlyOfficeConfigParam param = new OnlyOfficeConfigParam();
        param.setBizScene("dms.qianpiye");
        param.setBizModule("dms");
        ResolvedPlugin plugin = resolve(param).stream()
                .filter(item -> "dms-qianpiye-annotator".equals(item.getPluginCode()))
                .findFirst().orElseThrow(() -> new DataException("签批页标注插件未配置"));
        return java.util.Collections.singletonList(new ResolvedPlugin(plugin.getPluginCode(), plugin.getGuid(),
                plugin.getVersion(), plugin.getManifestUrl(), plugin.getApiBaseUrl(),
                plugin.getBizScene(), plugin.getBizModule(), true, plugin.getDisabledPluginGuids()));
    }
 
    private boolean validPathComponent(String value) {
        return !ObjectUtils.isEmpty(value) && PATH_COMPONENT_PATTERN.matcher(value).matches();
    }
 
    private boolean isDmsQianpiye(OnlyOfficeConfigParam param) {
        return "dms.qianpiye".equals(param.getBizScene()) && "dms".equals(param.getBizModule());
    }
 
    private String normalizedUrl(String value, String name) {
        if (ObjectUtils.isEmpty(value)) {
            throw new DataException("OnlyOffice 插件 " + name + " 未配置");
        }
        String normalized = value.replaceAll("/+$", "");
        try {
            URI uri = new URI(normalized);
            if (!("http".equals(uri.getScheme()) || "https".equals(uri.getScheme()))
                    || ObjectUtils.isEmpty(uri.getHost()) || uri.getQuery() != null || uri.getFragment() != null) {
                throw new DataException("OnlyOffice 插件 " + name + " 配置无效");
            }
        } catch (URISyntaxException e) {
            throw new DataException("OnlyOffice 插件 " + name + " 配置无效");
        }
        return normalized;
    }
 
    @Getter
    public static class ResolvedPlugin {
 
        private final String manifestUrl;
        private final String pluginCode;
        private final String guid;
        private final String version;
        private final String apiBaseUrl;
        private final String bizScene;
        private final String bizModule;
        private final boolean autostart;
        private final List<String> disabledPluginGuids;
 
        private ResolvedPlugin(String pluginCode, String guid, String version, String manifestUrl, String apiBaseUrl,
                               String bizScene, String bizModule, boolean autostart,
                               List<String> disabledPluginGuids) {
            this.pluginCode = pluginCode;
            this.guid = guid;
            this.version = version;
            this.manifestUrl = manifestUrl;
            this.apiBaseUrl = apiBaseUrl;
            this.bizScene = bizScene;
            this.bizModule = bizModule;
            this.autostart = autostart;
            this.disabledPluginGuids = new ArrayList<>(disabledPluginGuids);
        }
 
        public Map<String, Object> runtimeContext(String documentId, String bizDataId) {
            Map<String, Object> context = new LinkedHashMap<>();
            context.put("apiBaseUrl", apiBaseUrl);
            context.put("documentId", documentId);
            if (StringUtils.hasText(bizDataId)) {
                context.put("bizDataId", bizDataId);
            }
            context.put("bizModule", bizModule);
            context.put("bizScene", bizScene);
            context.put("pluginVersion", version);
            return context;
        }
    }
}