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;
|
}
|
}
|
}
|