刘光辉
昨天 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
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
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 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 (ObjectUtils.isEmpty(param.getBizScene())) {
            return java.util.Collections.emptyList();
        }
 
        OnlyOfficeProperties.PluginSceneMapping mapping = properties.getPlugins().getScenes().stream()
                .filter(item -> param.getBizScene().equals(item.getBizScene()))
                .findFirst()
                .orElseThrow(() -> new DataException("未配置的 OnlyOffice 插件场景:" + param.getBizScene()));
        if (!mapping.getBizModule().equals(param.getBizModule())) {
            throw new DataException("OnlyOffice 插件场景与业务模块不匹配");
        }
        if (ObjectUtils.isEmpty(param.getBizDataId())) {
            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;
    }
 
    private boolean validPathComponent(String value) {
        return !ObjectUtils.isEmpty(value) && PATH_COMPONENT_PATTERN.matcher(value).matches();
    }
 
    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);
            context.put("bizDataId", bizDataId);
            context.put("bizModule", bizModule);
            context.put("bizScene", bizScene);
            context.put("pluginVersion", version);
            return context;
        }
    }
}