先写道这,回去写
This commit is contained in:
parent
5a5952276a
commit
2276737efa
6
.idea/CopilotChatHistory.xml
generated
6
.idea/CopilotChatHistory.xml
generated
@ -3,6 +3,12 @@
|
||||
<component name="CopilotChatHistory">
|
||||
<option name="conversations">
|
||||
<list>
|
||||
<Conversation>
|
||||
<option name="createTime" value="1746706460096" />
|
||||
<option name="id" value="0196afd16dc0787d9f1e1f1b29555e6c" />
|
||||
<option name="title" value="新对话 2025年5月08日 20:14:20" />
|
||||
<option name="updateTime" value="1746706460096" />
|
||||
</Conversation>
|
||||
<Conversation>
|
||||
<option name="createTime" value="1745282575395" />
|
||||
<option name="id" value="01965af2ac2378d890fe0ac269ec997c" />
|
||||
|
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -35,7 +35,6 @@
|
||||
<module name="module-infra-api" />
|
||||
<module name="spring-boot-starter-test" />
|
||||
<module name="spring-boot-starter-websocket" />
|
||||
<module name="system-plugin-example-web" />
|
||||
<module name="spring-boot-starter-biz-ip" />
|
||||
<module name="module-infra-biz" />
|
||||
<module name="module-ci-commons" />
|
||||
@ -62,6 +61,7 @@
|
||||
<module name="module-ci-store-api" target="17" />
|
||||
<module name="module-ci-ticket" target="17" />
|
||||
<module name="module-ci-worker" target="17" />
|
||||
<module name="system-plugin-example-web" target="17" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
|
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -75,6 +75,7 @@
|
||||
<option value="$PROJECT_DIR$/modules/module-ci-store-api/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/modules/module-ci-ticket/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/modules/module-ci-worker/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/system-framework/system-plugin-example-web/pom.xml" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
|
@ -0,0 +1,13 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* @Classname PluginApplicationContextFactory
|
||||
* @Description 创建和刷新插件上下文
|
||||
* @Date 2025/5/8 19:54
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public interface PluginApplicationContextFactory {
|
||||
ApplicationContext create(String pluginId);
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
/**
|
||||
* @Classname PluginConstants
|
||||
* @Description 常量
|
||||
* @Date 2025/5/8 14:51
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public class PluginConstants {
|
||||
/**
|
||||
* Plugin metadata labels key.
|
||||
*/
|
||||
static String PLUGIN_NAME_LABEL_NAME = "plugin.ops/plugin-name";
|
||||
|
||||
static String SYSTEM_PLUGIN_NAME = "system";
|
||||
|
||||
static String RELOAD_ANNO = "plugin.ops/reload";
|
||||
|
||||
static String REQUEST_TO_UNLOAD_LABEL = "plugin.ops/request-to-unload";
|
||||
|
||||
static String PLUGIN_PATH = "plugin.ops/plugin-path";
|
||||
// 模式这边后面要考虑下dev\prod, 目前作为现在的情况,这两种就够了
|
||||
static String RUNTIME_MODE_ANNO = "plugin.ops/runtime-mode";
|
||||
|
||||
static String assetsRoutePrefix(String pluginName) {
|
||||
return "/plugins/" + pluginName + "/assets/";
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import cd.casic.plugin.extension.Plugin;
|
||||
|
||||
/**
|
||||
* @Classname PluginGetter
|
||||
* @Description 抽一个接口
|
||||
* @Date 2025/5/8 14:58
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface PluginGetter {
|
||||
|
||||
Plugin getPlugin(String name);
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @Classname PluginRouterFunctionRegistry
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 19:56
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public interface PluginRouterFunctionRegistry {
|
||||
void register(Collection<RouterFunction<ServerResponse>> routerFunctions);
|
||||
|
||||
void unregister(Collection<RouterFunction<ServerResponse>> routerFunctions);
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import cd.casic.plugin.extension.Plugin;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @Classname PluginService
|
||||
* @Description 插件服务类
|
||||
* @Date 2025/5/8 19:58
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public interface PluginService {
|
||||
|
||||
boolean installPresetPlugins();
|
||||
|
||||
Plugin install(Path path);
|
||||
|
||||
Plugin upgrade(String name, Path path);
|
||||
|
||||
/**
|
||||
* 重新加载插件,插件spec.enabled 设置为 true
|
||||
*/
|
||||
Mono<Plugin> reload(String name);
|
||||
|
||||
DataBuffer uglifyJsBundle();
|
||||
|
||||
DataBuffer uglifyCssBundle();
|
||||
|
||||
String generateBundleVersion();
|
||||
|
||||
Resource getJsBundle(String version);
|
||||
|
||||
Resource getCssBundle(String version);
|
||||
|
||||
Plugin changeState(String pluginName, boolean requestToEnable, boolean wait);
|
||||
|
||||
List<String> getRequiredDependencies(Plugin plugin,
|
||||
Predicate<PluginWrapper> predicate);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
/**
|
||||
* @Classname SpringPlugin
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 14:48
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public class SpringPlugin {
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package cd.casic.plugin.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @Classname BaseInformation
|
||||
* @Description 扩展的元数据
|
||||
* @Date 2025/5/8 15:07
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface BasePluginInformation {
|
||||
|
||||
String group();
|
||||
|
||||
String version();
|
||||
|
||||
String kind();
|
||||
|
||||
String plural();
|
||||
|
||||
String singular();
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @Classname PluginBeforeStopEvent
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 14:41
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@Getter
|
||||
public class PluginBeforeStopEvent extends ApplicationEvent {
|
||||
|
||||
private final PluginWrapper plugin;
|
||||
|
||||
public PluginBeforeStopEvent(Object source, PluginWrapper plugin) {
|
||||
super(source);
|
||||
Assert.notNull(plugin, "插件不能为空");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @Classname PluginStartedEvent
|
||||
* @Description 插件启动,发布事件到上下文
|
||||
* @Date 2025/5/8 14:43
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public class PluginStartedEvent extends ApplicationEvent {
|
||||
|
||||
private final PluginWrapper plugin;
|
||||
|
||||
public PluginStartedEvent(Object source, PluginWrapper plugin) {
|
||||
super(source);
|
||||
Assert.notNull(plugin, "插件不能为空");
|
||||
this.plugin = plugin;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Classname PluginStoppedEvent
|
||||
* @Description 插件停止,触发该事件,发布事件到上下文
|
||||
* @Date 2025/5/8 14:46
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@Getter
|
||||
public class PluginStoppedEvent extends ApplicationEvent {
|
||||
|
||||
private final PluginWrapper plugin;
|
||||
|
||||
public PluginStoppedEvent(Object source, PluginWrapper plugin) {
|
||||
super(source);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import cd.casic.plugin.SpringPlugin;
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Classname SpringPluginStartedEvent
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 14:48
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@Getter
|
||||
public class SpringPluginStartedEvent extends ApplicationEvent {
|
||||
private final SpringPlugin springPlugin;
|
||||
|
||||
public SpringPluginStartedEvent(Object source, SpringPlugin springPlugin) {
|
||||
super(source);
|
||||
this.springPlugin = springPlugin;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import cd.casic.plugin.SpringPlugin;
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Classname SpringPluginStartingEvent
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 14:49
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@Getter
|
||||
public class SpringPluginStartingEvent extends ApplicationEvent {
|
||||
|
||||
private final SpringPlugin springPlugin;
|
||||
|
||||
public SpringPluginStartingEvent(Object source, SpringPlugin springPlugin) {
|
||||
super(source);
|
||||
this.springPlugin = springPlugin;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import cd.casic.plugin.SpringPlugin;
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Classname SpringPluginStoppedEvent
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 14:50
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@Getter
|
||||
public class SpringPluginStoppedEvent extends ApplicationEvent {
|
||||
|
||||
private final SpringPlugin springPlugin;
|
||||
|
||||
public SpringPluginStoppedEvent(Object source, SpringPlugin springPlugin) {
|
||||
super(source);
|
||||
this.springPlugin = springPlugin;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import cd.casic.plugin.SpringPlugin;
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Classname SpringPluginStoppingEvent
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 14:50
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@Getter
|
||||
public class SpringPluginStoppingEvent extends ApplicationEvent {
|
||||
|
||||
private final SpringPlugin springPlugin;
|
||||
|
||||
public SpringPluginStoppingEvent(Object source, SpringPlugin springPlugin) {
|
||||
super(source);
|
||||
this.springPlugin = springPlugin;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package cd.casic.plugin.extension;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Classname AbstractExtension
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 19:59
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@Data
|
||||
public abstract class AbstractExtension implements Extension {
|
||||
|
||||
private String apiVersion;
|
||||
|
||||
private String kind;
|
||||
|
||||
private MetadataOperator metadata;
|
||||
|
||||
@Override
|
||||
public String getApiVersion() {
|
||||
var apiVersionFromGvk = Extension.super.getApiVersion();
|
||||
return apiVersionFromGvk != null ? apiVersionFromGvk : this.apiVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKind() {
|
||||
var kindFromGvk = Extension.super.getKind();
|
||||
return kindFromGvk != null ? kindFromGvk : this.kind;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package cd.casic.plugin.extension;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @Classname Extension
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 19:33
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public interface Extension extends ExtensionOperator, Comparable<Extension>{
|
||||
|
||||
@Override
|
||||
default int compareTo(Extension another) {
|
||||
if (another == null || another.getMetadata() == null) {
|
||||
return 1;
|
||||
}
|
||||
if (getMetadata() == null) {
|
||||
return -1;
|
||||
}
|
||||
return Objects.compare(getMetadata().getName(), another.getMetadata().getName(),
|
||||
Comparator.naturalOrder());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package cd.casic.plugin.extension;
|
||||
|
||||
import cd.casic.plugin.annotation.BasePluginInformation;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import kotlin.Metadata;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
/**
|
||||
* @Classname ExtensionOperator
|
||||
* @Description 扩展类的操作
|
||||
* @Date 2025/5/8 15:12
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public interface ExtensionOperator {
|
||||
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
@JsonProperty("apiVersion")
|
||||
default String getApiVersion() {
|
||||
final var basePluginInformation = getClass().getAnnotation(BasePluginInformation.class);
|
||||
if (basePluginInformation == null) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.hasText(basePluginInformation.group())) {
|
||||
return basePluginInformation.group() + "/" + basePluginInformation.version();
|
||||
}
|
||||
return basePluginInformation.version();
|
||||
}
|
||||
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
@JsonProperty("kind")
|
||||
default String getKind() {
|
||||
final var basePluginInformation = getClass().getAnnotation(BasePluginInformation.class);
|
||||
if (basePluginInformation == null) {
|
||||
// return null if having no GVK annotation
|
||||
return null;
|
||||
}
|
||||
return basePluginInformation.kind();
|
||||
}
|
||||
|
||||
@Schema(requiredMode = REQUIRED, implementation = Metadata.class)
|
||||
@JsonProperty("metadata")
|
||||
MetadataOperator getMetadata();
|
||||
|
||||
void setApiVersion(String apiVersion);
|
||||
|
||||
void setKind(String kind);
|
||||
|
||||
void setMetadata(MetadataOperator metadata);
|
||||
|
||||
default void groupVersionKind(GroupVersionKind gvk) {
|
||||
setApiVersion(gvk.groupVersion().toString());
|
||||
setKind(gvk.kind());
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
default GroupVersionKind groupVersionKind() {
|
||||
return GroupVersionKind.fromAPIVersionAndKind(getApiVersion(), getKind());
|
||||
}
|
||||
|
||||
static <T extends ExtensionOperator> Predicate<T> isNotDeleted() {
|
||||
return ext -> ext.getMetadata().getDeletionTimestamp() == null;
|
||||
}
|
||||
|
||||
static boolean isDeleted(ExtensionOperator extension) {
|
||||
return ExtensionUtil.isDeleted(extension);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package cd.casic.plugin.extension;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Order.asc;
|
||||
import static org.springframework.data.domain.Sort.Order.desc;
|
||||
|
||||
/**
|
||||
* @Classname ExtensionUtil
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 15:12
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public enum ExtensionUtil {
|
||||
;
|
||||
|
||||
public static boolean isDeleted(ExtensionOperator extension) {
|
||||
return extension.getMetadata() != null
|
||||
&& extension.getMetadata().getDeletionTimestamp() != null;
|
||||
}
|
||||
|
||||
public static boolean addFinalizers(MetadataOperator metadata, Set<String> finalizers) {
|
||||
var modifiableFinalizers = new HashSet<>(
|
||||
metadata.getFinalizers() == null ? Collections.emptySet() : metadata.getFinalizers());
|
||||
var added = modifiableFinalizers.addAll(finalizers);
|
||||
if (added) {
|
||||
metadata.setFinalizers(modifiableFinalizers);
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
public static boolean removeFinalizers(MetadataOperator metadata, Set<String> finalizers) {
|
||||
if (metadata.getFinalizers() == null) {
|
||||
return false;
|
||||
}
|
||||
var existingFinalizers = new HashSet<>(metadata.getFinalizers());
|
||||
var removed = existingFinalizers.removeAll(finalizers);
|
||||
if (removed) {
|
||||
metadata.setFinalizers(existingFinalizers);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for not deleting.
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public static boolean notDeleting() {
|
||||
return StringUtils.isNotBlank("metadata.deletionTimestamp");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default sort by creation timestamp desc and name asc.
|
||||
*
|
||||
* @return Sort
|
||||
*/
|
||||
public static Sort defaultSort() {
|
||||
return Sort.by(
|
||||
desc("metadata.creationTimestamp"),
|
||||
asc("metadata.name")
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package cd.casic.plugin.extension;
|
||||
|
||||
/**
|
||||
* @Classname GroupKind
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 19:33
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public record GroupKind(String group, String kind) {
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package cd.casic.plugin.extension;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @Classname GroupVersion
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 19:33
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public record GroupVersion(String group, String version) {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtils.hasText(group) ? group + "/" + version : version;
|
||||
}
|
||||
|
||||
public static GroupVersion parseAPIVersion(String apiVersion) {
|
||||
Assert.hasText(apiVersion, "API version must not be blank");
|
||||
|
||||
var groupVersion = apiVersion.split("/");
|
||||
return switch (groupVersion.length) {
|
||||
case 1 -> new GroupVersion("", apiVersion);
|
||||
case 2 -> new GroupVersion(groupVersion[0], groupVersion[1]);
|
||||
default ->
|
||||
throw new IllegalArgumentException("Unexpected APIVersion string: " + apiVersion);
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package cd.casic.plugin.extension;
|
||||
|
||||
import cd.casic.plugin.annotation.BasePluginInformation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @Classname GroupVersionKind
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 19:33
|
||||
* @Created by mianbin
|
||||
*/
|
||||
public record GroupVersionKind(String group, String version, String kind) {
|
||||
|
||||
public GroupVersionKind {
|
||||
Assert.hasText(version, "Version must not be blank");
|
||||
Assert.hasText(kind, "Kind must not be blank");
|
||||
}
|
||||
|
||||
public GroupVersion groupVersion() {
|
||||
return new GroupVersion(group, version);
|
||||
}
|
||||
|
||||
public GroupKind groupKind() {
|
||||
return new GroupKind(group, kind);
|
||||
}
|
||||
|
||||
public boolean hasGroup() {
|
||||
return StringUtils.hasText(group);
|
||||
}
|
||||
|
||||
public static GroupVersionKind fromAPIVersionAndKind(String apiVersion, String kind) {
|
||||
Assert.hasText(kind, "Kind must not be blank");
|
||||
|
||||
var gv = GroupVersion.parseAPIVersion(apiVersion);
|
||||
return new GroupVersionKind(gv.group(), gv.version(), kind);
|
||||
}
|
||||
|
||||
public static <T extends Extension> GroupVersionKind fromExtension(Class<T> extension) {
|
||||
BasePluginInformation gvk = extension.getAnnotation(BasePluginInformation.class);
|
||||
return new GroupVersionKind(gvk.group(), gvk.version(), gvk.kind());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (hasGroup()) {
|
||||
return group + "/" + version + "/" + kind;
|
||||
}
|
||||
return version + "/" + kind;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package cd.casic.plugin.extension;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @Classname Metadata
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 19:52
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(exclude = "version")
|
||||
public class Metadata implements MetadataOperator {
|
||||
|
||||
/**
|
||||
* Metadata name. 唯一的。
|
||||
*/
|
||||
private String name;
|
||||
|
||||
private String generateName;
|
||||
|
||||
private Map<String, String> labels;
|
||||
|
||||
private Map<String, String> annotations;
|
||||
|
||||
private Long version;
|
||||
|
||||
private Instant creationTimestamp;
|
||||
|
||||
private Instant deletionTimestamp;
|
||||
|
||||
private Set<String> finalizers;
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package cd.casic.plugin.extension;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import kotlin.Metadata;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
/**
|
||||
* @Classname MetadataOperator
|
||||
* @Description 元数据的处理
|
||||
* @Date 2025/5/8 15:17
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@JsonDeserialize(as = Metadata.class)
|
||||
@Schema(implementation = Metadata.class)
|
||||
public interface MetadataOperator {
|
||||
|
||||
@Schema(name = "name", description = "Metadata name", requiredMode = REQUIRED)
|
||||
@JsonProperty("name")
|
||||
String getName();
|
||||
|
||||
@Schema(name = "generateName", description = "名字获取失败就自动生成,根据generateName字段")
|
||||
String getGenerateName();
|
||||
|
||||
@Schema(name = "labels")
|
||||
@JsonProperty("labels")
|
||||
Map<String, String> getLabels();
|
||||
|
||||
@Schema(name = "annotations")
|
||||
@JsonProperty("annotations")
|
||||
Map<String, String> getAnnotations();
|
||||
|
||||
@Schema(name = "version", nullable = true)
|
||||
@JsonProperty("version")
|
||||
Long getVersion();
|
||||
|
||||
@Schema(name = "creationTimestamp", nullable = true)
|
||||
@JsonProperty("creationTimestamp")
|
||||
Instant getCreationTimestamp();
|
||||
|
||||
@Schema(name = "deletionTimestamp", nullable = true)
|
||||
@JsonProperty("deletionTimestamp")
|
||||
Instant getDeletionTimestamp();
|
||||
|
||||
@Schema(name = "finalizers", nullable = true)
|
||||
Set<String> getFinalizers();
|
||||
|
||||
void setName(String name);
|
||||
|
||||
void setGenerateName(String generateName);
|
||||
|
||||
void setLabels(Map<String, String> labels);
|
||||
|
||||
void setAnnotations(Map<String, String> annotations);
|
||||
|
||||
void setVersion(Long version);
|
||||
|
||||
void setCreationTimestamp(Instant creationTimestamp);
|
||||
|
||||
void setDeletionTimestamp(Instant deletionTimestamp);
|
||||
|
||||
void setFinalizers(Set<String> finalizers);
|
||||
|
||||
static boolean metadataDeepEquals(MetadataOperator left, MetadataOperator right) {
|
||||
if (left == null && right == null) {
|
||||
return true;
|
||||
}
|
||||
if (left == null || right == null) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(left.getName(), right.getName())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(left.getLabels(), right.getLabels())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(left.getAnnotations(), right.getAnnotations())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(left.getCreationTimestamp(), right.getCreationTimestamp())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(left.getDeletionTimestamp(), right.getDeletionTimestamp())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(left.getVersion(), right.getVersion())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(left.getFinalizers(), right.getFinalizers())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
package cd.casic.plugin.extension;
|
||||
|
||||
import cd.casic.plugin.annotation.BasePluginInformation;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.google.common.collect.EvictingQueue;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.pf4j.PluginState;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
/**
|
||||
* @Classname Plugin
|
||||
* @Description TODO
|
||||
* @Date 2025/5/8 20:03
|
||||
* @Created by mianbin
|
||||
*/
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
@BasePluginInformation(group = "plugin.ops", version = "1.0.0", kind = "Plugin", plural = "plugins",
|
||||
singular = "plugin")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Plugin extends AbstractExtension {
|
||||
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private PluginSpec spec;
|
||||
|
||||
private PluginStatus status;
|
||||
|
||||
@NonNull
|
||||
@JsonIgnore
|
||||
public PluginStatus statusNonNull() {
|
||||
if (this.status == null) {
|
||||
this.status = new PluginStatus();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PluginSpec {
|
||||
|
||||
private String displayName;
|
||||
|
||||
@Schema(requiredMode = REQUIRED,
|
||||
pattern = "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-("
|
||||
+ "(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\."
|
||||
+ "(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\"
|
||||
+ ".[0-9a-zA-Z-]+)*))?$")
|
||||
private String version;
|
||||
|
||||
private PluginAuthor author;
|
||||
|
||||
private String logo;
|
||||
|
||||
private Map<String, String> pluginDependencies = new HashMap<>(4);
|
||||
|
||||
private String homepage;
|
||||
|
||||
private String repo;
|
||||
|
||||
private String issues;
|
||||
|
||||
private String description;
|
||||
|
||||
private List<License> license;
|
||||
|
||||
/**
|
||||
* SemVer format.
|
||||
*/
|
||||
private String requires = "*";
|
||||
|
||||
@Deprecated
|
||||
private String pluginClass;
|
||||
|
||||
private Boolean enabled = false;
|
||||
|
||||
private String settingName;
|
||||
|
||||
private String configMapName;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class License {
|
||||
private String name;
|
||||
private String url;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PluginStatus {
|
||||
|
||||
private Phase phase;
|
||||
|
||||
private EvictingQueue queue;
|
||||
|
||||
private Instant lastStartTime;
|
||||
|
||||
private PluginState lastProbeState;
|
||||
|
||||
private String entry;
|
||||
|
||||
private String stylesheet;
|
||||
|
||||
private String logo;
|
||||
|
||||
@Schema(description = "Load location of the plugin, often a path.")
|
||||
private URI loadLocation;
|
||||
|
||||
// 这里实现一个先进先出的队列
|
||||
public static EvictingQueue nullSafeConditions(@NonNull PluginStatus status) {
|
||||
Assert.notNull(status, "The status must not be null.");
|
||||
if (status.getQueue() == null) {
|
||||
status.setQueue(EvictingQueue.create(20));
|
||||
}
|
||||
return status.getQueue();
|
||||
}
|
||||
}
|
||||
|
||||
public enum Phase {
|
||||
PENDING,
|
||||
STARTING,
|
||||
CREATED,
|
||||
DISABLING,
|
||||
DISABLED,
|
||||
RESOLVED,
|
||||
STARTED,
|
||||
STOPPED,
|
||||
FAILED,
|
||||
UNKNOWN,
|
||||
;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public static class PluginAuthor {
|
||||
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String name;
|
||||
|
||||
private String website;
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>system-framework</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>system-plugin-example-web</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<description>system-plugin-example-web</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-plugin</artifactId>
|
||||
<version>${revision}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -1,22 +0,0 @@
|
||||
package cd.casic.server.system.plugin.example.web;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.opsmodulepluginsexampleweb
|
||||
* @Project:ops
|
||||
* @name:ExampleController
|
||||
* @Date:2024/03/19 16:04
|
||||
* @Filename:ExampleController
|
||||
* @description:Todo
|
||||
*/
|
||||
@RestController
|
||||
public class ExampleController {
|
||||
|
||||
@GetMapping("/ok")
|
||||
public String init() {
|
||||
return "are u ok!!!";
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
package cd.casic.server.system.plugin.example.web;
|
||||
|
||||
import cd.casic.plugin.BufferedPluginBundleResource;
|
||||
import cd.casic.plugin.FrontResourceProcessor;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RestController
|
||||
public class FrontResourceController {
|
||||
|
||||
@Resource
|
||||
BufferedPluginBundleResource bufferedPluginBundleResource;
|
||||
|
||||
@Resource
|
||||
FrontResourceProcessor frontResourceProcessor;
|
||||
|
||||
private static final CacheControl MAX_CACHE_CONTROL = CacheControl.maxAge(365, TimeUnit.DAYS);
|
||||
|
||||
@GetMapping("/plugins/-/bundle.js")
|
||||
public ResponseEntity<?> fetchJsBundle(HttpServletRequest request,
|
||||
@RequestParam(required = false) String v) throws IOException {
|
||||
if (v != null) {
|
||||
FileSystemResource fsRes = bufferedPluginBundleResource.getJsBundle(v, frontResourceProcessor.uglifyJsBundle());
|
||||
|
||||
Instant lastModified = Instant.ofEpochMilli(fsRes.lastModified());
|
||||
if (isModified(request, lastModified)) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setCacheControl(MAX_CACHE_CONTROL);
|
||||
headers.setContentType(MediaType.parseMediaType("application/x-javascript"));
|
||||
headers.setLastModified(lastModified.toEpochMilli());
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.headers(headers)
|
||||
.body(new InputStreamResource(fsRes.getInputStream()));
|
||||
} else {
|
||||
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
|
||||
}
|
||||
} else {
|
||||
String newVersion = frontResourceProcessor.generateJsBundleVersion();
|
||||
String redirectUrl = buildJsBundleUri("js", newVersion).toString();
|
||||
|
||||
return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT)
|
||||
.header(HttpHeaders.LOCATION, redirectUrl)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/plugins/-/bundle.css")
|
||||
public ResponseEntity<?> fetchCssBundle(HttpServletRequest request,
|
||||
@RequestParam(required = false) String v) throws IOException {
|
||||
if (v != null) {
|
||||
FileSystemResource fsRes = bufferedPluginBundleResource.getCssBundle(v, frontResourceProcessor.uglifyCssBundle());
|
||||
|
||||
Instant lastModified = Instant.ofEpochMilli(fsRes.lastModified());
|
||||
if (isModified(request, lastModified)) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setCacheControl(MAX_CACHE_CONTROL);
|
||||
headers.setContentType(MediaType.parseMediaType("text/css"));
|
||||
headers.setLastModified(lastModified.toEpochMilli());
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.headers(headers)
|
||||
.body(new InputStreamResource(fsRes.getInputStream()));
|
||||
} else {
|
||||
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
|
||||
}
|
||||
} else {
|
||||
String newVersion = frontResourceProcessor.generateJsBundleVersion();
|
||||
String redirectUrl = buildJsBundleUri("css", newVersion).toString();
|
||||
|
||||
return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT)
|
||||
.header(HttpHeaders.LOCATION, redirectUrl)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
private boolean isModified(HttpServletRequest request, Instant lastModified) {
|
||||
long ifModifiedSince = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
|
||||
return ifModifiedSince <= 0 || !lastModified.isBefore(Instant.ofEpochMilli(ifModifiedSince));
|
||||
}
|
||||
|
||||
private URI buildJsBundleUri(String type, String version) {
|
||||
return URI.create(
|
||||
"/plugins/-/bundle." + type + "?v=" + version);
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package cd.casic.server.system.plugin.example.web;
|
||||
|
||||
import org.pf4j.ExtensionPoint;
|
||||
|
||||
public interface OperationButtonExtension extends ExtensionPoint {
|
||||
|
||||
/**
|
||||
* 按钮名称
|
||||
*
|
||||
* @return 按钮名称
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* 响应点击事件
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
String onClick();
|
||||
|
||||
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package cd.casic.server.system.plugin.example.web;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = {"cd.casic.server.system"})
|
||||
public class OpsModulePluginsExampleWebApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(OpsModulePluginsExampleWebApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
package cd.casic.server.system.plugin.example.web;
|
||||
|
||||
import cd.casic.plugin.PluginDescriptorStorage;
|
||||
import cd.casic.plugin.PluginManager;
|
||||
import cd.casic.plugin.PluginProperties;
|
||||
import cd.casic.plugin.utils.SM4EncryptUtil;
|
||||
import cd.casic.server.system.plugin.example.web.service.impl.PluginStorageServiceImpl;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginState;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.opsmodulepluginsexampleweb.controller
|
||||
* @Project:ops
|
||||
* @name:PluginManagerControoler
|
||||
* @Date:2024/03/20 15:05
|
||||
* @Filename:PluginManagerControoler
|
||||
* @description:Todo
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/plugin")
|
||||
@Slf4j
|
||||
public class PluginManagerController {
|
||||
@Resource
|
||||
private PluginProperties pluginProperties;
|
||||
@Resource
|
||||
private PluginStorageServiceImpl pluginStorageService;
|
||||
|
||||
@Resource
|
||||
private PluginManager pluginManager;
|
||||
|
||||
@PostMapping("upload")
|
||||
public boolean uploadPlugin(@RequestParam("file") MultipartFile file) throws IOException {
|
||||
InputStream is = file.getInputStream();
|
||||
Files.copy(is, Paths.get(pluginProperties.getPluginsRoot()).resolve(Objects.requireNonNull(file.getOriginalFilename())));
|
||||
is.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
@GetMapping("reload")
|
||||
public void reloadPlugins() {
|
||||
pluginManager.unloadPlugins();
|
||||
pluginManager.loadPlugins();
|
||||
}
|
||||
|
||||
@GetMapping("load")
|
||||
public String loadPlugin(@RequestParam() String pluginId) {
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginId);
|
||||
if (plugin != null) {
|
||||
return pluginManager.loadPlugin(plugin.getPluginPath());
|
||||
} else {
|
||||
return "Plugin not found";
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("unload")
|
||||
public boolean unloadPlugin(@RequestParam() String pluginId) {
|
||||
return pluginManager.unloadPlugin(pluginId);
|
||||
}
|
||||
|
||||
@GetMapping("start")
|
||||
public PluginState startPlugin(@RequestParam() String pluginId) {
|
||||
return pluginManager.startPlugin(pluginId);
|
||||
}
|
||||
|
||||
@GetMapping("stop")
|
||||
public PluginState stopPlugin(@RequestParam() String pluginId) {
|
||||
return pluginManager.stopPlugin(pluginId);
|
||||
}
|
||||
|
||||
@GetMapping("disable")
|
||||
public boolean disablePlugin(@RequestParam() String pluginId) {
|
||||
this.setPluginDisable(pluginId);
|
||||
return pluginManager.disablePlugin(pluginId);
|
||||
}
|
||||
|
||||
@GetMapping("enable")
|
||||
public boolean enablePlugin(@RequestParam() String pluginId) {
|
||||
this.setPluginEnable(pluginId);
|
||||
return pluginManager.enablePlugin(pluginId);
|
||||
}
|
||||
|
||||
@GetMapping("delete")
|
||||
public boolean deletePlugin(@RequestParam() String pluginId) {
|
||||
return pluginManager.deletePlugin(pluginId);
|
||||
}
|
||||
|
||||
@GetMapping("lists")
|
||||
public List<JSONObject> listPlugin() {
|
||||
return getPlugins();
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("list")
|
||||
public JSONArray extensions(@RequestParam String extensionClass) throws ClassNotFoundException {
|
||||
List extensions = pluginManager.getExtensions(Class.forName(extensionClass));
|
||||
JSONArray result = new JSONArray();
|
||||
for (Object extension : extensions) {
|
||||
if (extension instanceof OperationButtonExtension) {
|
||||
PluginWrapper plugin = pluginManager.whichPlugin(extension.getClass());
|
||||
result.add(new JSONObject().fluentPut("class", extension.getClass().getName())
|
||||
.fluentPut("name", ((OperationButtonExtension) extension).name()).fluentPut("plugin", new JSONObject()
|
||||
.fluentPut("id", plugin.getPluginId())
|
||||
.fluentPut("version", plugin.getDescriptor().getVersion())
|
||||
.fluentPut("path", plugin.getPluginPath().toString())
|
||||
.fluentPut("state", plugin.getPluginState())
|
||||
.fluentPut("class", plugin.getDescriptor().getPluginClass())));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@GetMapping("click")
|
||||
public String click(@RequestParam String extensionClass, @RequestParam String name) throws ClassNotFoundException {
|
||||
List extensions = pluginManager.getExtensions(Class.forName(extensionClass));
|
||||
for (Object extension : extensions) {
|
||||
if (extension instanceof OperationButtonExtension && ((OperationButtonExtension) extension).name().equals(name)) {
|
||||
return ((OperationButtonExtension) extension).onClick();
|
||||
}
|
||||
}
|
||||
return "Extension not found or unavailable";
|
||||
}
|
||||
|
||||
private List<JSONObject> getPlugins() {
|
||||
ArrayList<JSONObject> list = new ArrayList<>();
|
||||
List<PluginDescriptorStorage> pluginDescriptors = pluginStorageService.list();
|
||||
for (PluginDescriptorStorage pluginDescriptor : pluginDescriptors){
|
||||
// 校验license
|
||||
if (SM4EncryptUtil.checkLicense(pluginDescriptor.getLicense(), pluginDescriptor.getPluginId())){
|
||||
list.add(new JSONObject()
|
||||
.fluentPut("id", pluginDescriptor.getPluginId())
|
||||
.fluentPut("version", pluginDescriptor.getVersion())
|
||||
.fluentPut("path", pluginDescriptor.getPluginDirPath())
|
||||
.fluentPut("state", pluginManager.getPlugin(pluginDescriptor.getPluginId()).getPluginState())
|
||||
.fluentPut("class", pluginDescriptor.getPluginClass())
|
||||
);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
private void setPluginEnable(String pluginId) {
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginId);
|
||||
PluginDescriptorStorage descriptorStorage = pluginStorageService.convert(plugin);
|
||||
descriptorStorage.setEnable(1); // 1 是启用
|
||||
pluginStorageService.updateById(descriptorStorage);
|
||||
}
|
||||
|
||||
private void setPluginDisable(String pluginId) {
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginId);
|
||||
PluginDescriptorStorage descriptorStorage = pluginStorageService.convert(plugin);
|
||||
descriptorStorage.setEnable(0); // 0 是禁用
|
||||
pluginStorageService.updateById(descriptorStorage);
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package cd.casic.server.system.plugin.example.web.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
|
||||
@Configuration
|
||||
public class WebSocketConfig {
|
||||
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package cd.casic.server.system.plugin.example.web.mapper;
|
||||
|
||||
import cd.casic.plugin.PluginDescriptorStorage;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PluginStorageMapper extends BaseMapper<PluginDescriptorStorage>{
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package cd.casic.server.system.plugin.example.web.service;
|
||||
|
||||
import cd.casic.plugin.PluginDescriptorStorage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
public interface PluginStorageService extends IService<PluginDescriptorStorage> {
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
package cd.casic.server.system.plugin.example.web.service.impl;
|
||||
|
||||
import cd.casic.plugin.PluginDescriptorStorage;
|
||||
import cd.casic.plugin.PluginManager;
|
||||
import cd.casic.plugin.PluginProperties;
|
||||
import cd.casic.plugin.event.PluginDirCheckEvent;
|
||||
import cd.casic.plugin.event.PluginLoadedEvent;
|
||||
import cd.casic.plugin.event.PluginUnloadedEvent;
|
||||
import cd.casic.plugin.utils.SM4EncryptUtil;
|
||||
import cd.casic.server.system.plugin.example.web.mapper.PluginStorageMapper;
|
||||
import cd.casic.server.system.plugin.example.web.service.PluginStorageService;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginDescriptor;
|
||||
import org.pf4j.PluginState;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class PluginStorageServiceImpl extends ServiceImpl<PluginStorageMapper, PluginDescriptorStorage> implements PluginStorageService {
|
||||
@Resource
|
||||
private PluginManager pluginManager;
|
||||
@Resource
|
||||
private PluginProperties pluginProperties;
|
||||
|
||||
private Boolean Cleaned = false;
|
||||
|
||||
@EventListener
|
||||
public void initializePlugins(ApplicationStartedEvent event) {
|
||||
pluginManager.unloadPlugins();
|
||||
Path pluginRoot = Paths.get(pluginProperties.getPluginsRoot());
|
||||
List<PluginDescriptorStorage> list = this.list();
|
||||
if (ObjectUtil.isNotEmpty(list)) {
|
||||
if (!list.get(0).getPluginDirPath().equals(pluginProperties.getPluginsRoot())) {
|
||||
this.removeBatchByIds(list);
|
||||
}
|
||||
list.stream()
|
||||
.filter(pluginDescriptorStorage -> Paths.get(pluginDescriptorStorage.getPath()).startsWith(pluginRoot)
|
||||
&& SM4EncryptUtil.checkLicense(pluginDescriptorStorage.getLicense(), pluginDescriptorStorage.getPluginId()))
|
||||
.forEach(pluginDescriptorStorage -> {
|
||||
Path pluginPath = Paths.get(pluginDescriptorStorage.getPath());
|
||||
String pluginId = pluginManager.loadPlugin(pluginPath);
|
||||
if (PluginDescriptorStorage.EnableStatus.ENABLE.getCode().equals(pluginDescriptorStorage.getEnable())) {
|
||||
pluginManager.startPlugin(pluginId);
|
||||
}
|
||||
});
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void pluginDirCheck(PluginDirCheckEvent event) throws IOException {
|
||||
// // 跟之前工作目录路径不同就清空数据库
|
||||
// String lastDirTxtPath = pluginProperties.getLastDirTxtPath();
|
||||
// String pluginsRoot = pluginProperties.getPluginsRoot();
|
||||
// String line = null;
|
||||
//
|
||||
// FileReader fileReader = new FileReader(lastDirTxtPath);
|
||||
// BufferedReader bufferedReader = new BufferedReader(fileReader);
|
||||
// line = bufferedReader.readLine();
|
||||
// bufferedReader.close();
|
||||
// fileReader.close();
|
||||
//
|
||||
// if (!line.equals(pluginsRoot)){
|
||||
// if (!Cleaned){
|
||||
// this.remove(new LambdaQueryWrapper<PluginDescriptorStorage>());
|
||||
// Cleaned = true;
|
||||
// FileWriter fileWriter = new FileWriter(lastDirTxtPath);
|
||||
// BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
|
||||
// bufferedWriter.write(pluginsRoot);
|
||||
// bufferedWriter.flush();
|
||||
// bufferedWriter.close();
|
||||
// fileWriter.close();
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void insertPlugin(PluginLoadedEvent event) {
|
||||
PluginWrapper pluginWrapper = event.getPluginInfo().getPluginWrapper();
|
||||
PluginDescriptorStorage pluginDescriptorStorage = convert(pluginWrapper);
|
||||
if (this.getById(pluginDescriptorStorage.getPluginId()) != null
|
||||
|| !SM4EncryptUtil.checkLicense(pluginDescriptorStorage.getLicense()
|
||||
, pluginDescriptorStorage.getPluginId())) return;
|
||||
this.save(pluginDescriptorStorage);
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void deletePlugin(PluginUnloadedEvent event) {
|
||||
PluginWrapper pluginWrapper = event.getPluginInfo().getPluginWrapper();
|
||||
this.remove(new LambdaQueryWrapper<PluginDescriptorStorage>()
|
||||
.eq(PluginDescriptorStorage::getPluginId, pluginWrapper.getPluginId()));
|
||||
}
|
||||
|
||||
public PluginDescriptorStorage convert(PluginWrapper pluginWrapper) {
|
||||
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
|
||||
return PluginDescriptorStorage.builder()
|
||||
.pluginId(pluginDescriptor.getPluginId())
|
||||
.pluginClass(pluginDescriptor.getPluginClass())
|
||||
.pluginDescription(pluginDescriptor.getPluginDescription())
|
||||
.license(pluginDescriptor.getLicense())
|
||||
.provider(pluginDescriptor.getProvider())
|
||||
.version(pluginDescriptor.getVersion())
|
||||
.dependencies(pluginDescriptor.getDependencies())
|
||||
.requires(pluginDescriptor.getRequires())
|
||||
.enable(pluginWrapper.getPluginState().equals(PluginState.DISABLED) ?
|
||||
PluginDescriptorStorage.EnableStatus.DISABLE.getCode() :
|
||||
PluginDescriptorStorage.EnableStatus.ENABLE.getCode())
|
||||
.path(pluginWrapper.getPluginPath().toString())
|
||||
.pluginDirPath(pluginProperties.getPluginsRoot())
|
||||
.build();
|
||||
}
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: ops-plugin
|
||||
jmx:
|
||||
enabled: true
|
||||
|
||||
thymeleaf:
|
||||
prefix: classpath:/templates/
|
||||
encoding: UTF-8
|
||||
cache: false
|
||||
suffix: .html
|
||||
servlet:
|
||||
content-type: text/html
|
||||
profiles:
|
||||
active: dev
|
||||
|
||||
main:
|
||||
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
|
||||
# allow-bean-definition-overriding: true # Servlet 配置
|
||||
servlet:
|
||||
# 文件上传相关配置项
|
||||
multipart:
|
||||
max-file-size: 64MB # 单个文件大小
|
||||
max-request-size: 128MB # 设置总上传的文件大小
|
||||
enabled: true
|
||||
mvc:
|
||||
pathmatch:
|
||||
matching-strategy: ANT_PATH_MATCHER # 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题,参见 SpringFoxHandlerProviderBeanPostProcessor 类
|
||||
# throw-exception-if-no-handler-found: true # 404 错误时抛出异常,方便统一处理
|
||||
# static-path-pattern: /static/** # 静态资源路径; 注意:如果不配置,则 throw-exception-if-no-handler-found 不生效!!! TODO 芋艿:不能配置,会导致 swagger 不生效
|
||||
|
||||
# Jackson 配置项
|
||||
jackson:
|
||||
serialization:
|
||||
write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳
|
||||
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
|
||||
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
|
||||
fail-on-empty-beans: false # 允许序列化无属性的 Bean
|
||||
|
||||
# 数据源配置项
|
||||
autoconfigure:
|
||||
exclude:
|
||||
- com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
|
||||
datasource:
|
||||
druid: # Druid 【监控】相关的全局配置
|
||||
web-stat-filter:
|
||||
enabled: true
|
||||
stat-view-servlet:
|
||||
enabled: true
|
||||
allow: # 设置白名单,不填则允许所有访问
|
||||
url-pattern: /druid/*
|
||||
login-username: # 控制台管理用户名和密码
|
||||
login-password:
|
||||
filter:
|
||||
stat:
|
||||
enabled: true
|
||||
log-slow-sql: true # 慢 SQL 记录
|
||||
slow-sql-millis: 100
|
||||
merge-sql: true
|
||||
wall:
|
||||
config:
|
||||
multi-statement-allow: true
|
||||
dynamic: # 多数据源配置
|
||||
druid: # Druid 【连接池】相关的全局配置
|
||||
initial-size: 1 # 初始连接数
|
||||
min-idle: 1 # 最小连接池数量
|
||||
max-active: 20 # 最大连接池数量
|
||||
max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
|
||||
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
|
||||
min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
|
||||
max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
|
||||
validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
|
||||
test-while-idle: true
|
||||
test-on-borrow: false
|
||||
test-on-return: false
|
||||
primary: mysql
|
||||
datasource:
|
||||
# mysql:
|
||||
# name: ops-pro
|
||||
# url: jdbc:mysql://120.48.68.245:13306/${spring.datasource.dynamic.datasource.mysql.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
|
||||
# username: root
|
||||
# password: 1qaz!QAZ
|
||||
mysql:
|
||||
name: ops_pro
|
||||
url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.mysql.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
|
||||
username: root
|
||||
password: 1qaz!QAZ
|
||||
driver-class-name: org.sqlite.JDBC
|
||||
url: jdbc:sqlite:D:/db/sqlitest.db
|
||||
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
|
||||
|
||||
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.pf4j: debug
|
||||
|
||||
ops:
|
||||
info:
|
||||
version: 2.0.0
|
||||
base-package: cd.casic
|
||||
web:
|
||||
admin-ui:
|
||||
url: 127.0.0.1 # Admin 管理后台 UI 的地址
|
||||
security:
|
||||
permit-all_urls:
|
||||
- /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录
|
||||
websocket:
|
||||
enable: true # websocket的开关
|
||||
path: /websocket/message # 路径
|
||||
maxOnlineCount: 0 # 最大连接人数
|
||||
sessionMap: true # 保存sessionMap
|
||||
swagger:
|
||||
title: Ops平台
|
||||
description: 提供管理后台、用户 App 的所有功能
|
||||
version: ${ops.info.version}
|
||||
url: ${ops.web.admin-ui.url}
|
||||
license: MIT
|
||||
codegen:
|
||||
base-package: ${ops.info.base-package}
|
||||
db-schemas: ${spring.datasource.dynamic.datasource.master.name}
|
||||
front-type: 10 # 前端模版的类型,参见 CodegenFrontTypeEnum 枚举类
|
||||
error-code: # 错误码相关配置项
|
||||
constants-class-list:
|
||||
- cd.casic.ops.module.infra.enums.ErrorCodeConstants
|
||||
- cd.casic.ops.module.member.enums.ErrorCodeConstants
|
||||
- cd.casic.ops.module.system.enums.ErrorCodeConstants
|
||||
#- cd.casic.ops.module.mp.enums.ErrorCodeConstants
|
||||
tenant: # 多租户相关配置项
|
||||
enable: true
|
||||
ignore-urls:
|
||||
- /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号
|
||||
- /admin-api/system/captcha/get # 获取图片验证码,和租户无关
|
||||
- /admin-api/system/captcha/check # 校验图片验证码,和租户无关
|
||||
- /admin-api/infra/file/*/get/** # 获取图片,和租户无关
|
||||
- /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号
|
||||
- /admin-api/pay/notify/callback/* # 支付回调通知,不携带租户编号
|
||||
- /jmreport/* # 积木报表,无法携带租户编号
|
||||
- /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,无法携带租户编号
|
||||
- /admin-api/leafAlloc/** # id配置接口,和租户无关
|
||||
ignore-tables:
|
||||
- system_tenant
|
||||
- system_tenant_package
|
||||
- system_dict_data
|
||||
- system_dict_type
|
||||
- system_error_code
|
||||
- system_menu
|
||||
- system_sensitive_word
|
||||
- system_oauth2_client
|
||||
- system_notify_template
|
||||
- infra_codegen_column
|
||||
- infra_codegen_table
|
||||
- infra_test_demo
|
||||
- infra_config
|
||||
- infra_file_config
|
||||
- infra_file
|
||||
- infra_file_content
|
||||
- infra_job
|
||||
- infra_job_log
|
||||
- infra_job_log
|
||||
- infra_data_source_config
|
||||
- jimu_dict
|
||||
- jimu_dict_item
|
||||
- jimu_report
|
||||
- jimu_report_data_source
|
||||
- jimu_report_db
|
||||
- jimu_report_db_field
|
||||
- jimu_report_db_param
|
||||
- jimu_report_link
|
||||
- jimu_report_map
|
||||
- jimu_report_share
|
||||
- rep_demo_dxtj
|
||||
- rep_demo_employee
|
||||
- rep_demo_gongsi
|
||||
- rep_demo_jianpiao
|
||||
- tmp_report_data_1
|
||||
- tmp_report_data_income
|
||||
trade:
|
||||
order:
|
||||
app-id: 1 # 商户编号
|
||||
expire-time: 2h # 支付的过期时间
|
||||
|
||||
debug: false
|
||||
|
||||
mongo-plus:
|
||||
data:
|
||||
mongodb:
|
||||
host: 127.0.0.1 #ip
|
||||
port: 27017 #端口
|
||||
database: learn #数据库名
|
||||
connectTimeoutMS: 50000 #在超时之前等待连接打开的最长时间(以毫秒为单位)
|
||||
|
||||
cd:
|
||||
casic:
|
||||
ops:
|
||||
plugin:
|
||||
# 测试的时候写相对于工程根目录的相对路径,Paths.get就能读到
|
||||
enable: true
|
||||
mode: development # 插件运行模式,可选值:development、deployment
|
||||
rest-path-prefix: plugins
|
||||
enable-plugin-id-as-rest-prefix: true
|
||||
plugins-root: ops\ops-module-plugins\ops-module-plugins-example # 插件根目录
|
||||
#upload-path: ops-module-plugins\ops-module-plugins-example # 插件jar包上传路径
|
||||
# plugins-root: ${user.home}/.ops/plugins # 上线之后可以通过环境变量设置固定的路径
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
disable-swagger-default-url: true
|
@ -1,73 +0,0 @@
|
||||
-- MySQL dump 10.13 Distrib 8.0.36, for Win64 (x86_64)
|
||||
--
|
||||
-- Host: 127.0.0.1 Database: ops_pro
|
||||
-- ------------------------------------------------------
|
||||
-- Server version 8.0.36
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!50503 SET NAMES utf8mb4 */;
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
|
||||
--
|
||||
-- Table structure for table `sys_plugins`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `sys_plugins`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!50503 SET character_set_client = utf8mb4 */;
|
||||
CREATE TABLE `sys_plugins` (
|
||||
`plugin_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||
`plugin_description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
|
||||
`plugin_class` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||
`version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||
`requires` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
|
||||
`provider` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
|
||||
`dependencies` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
|
||||
`license` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
|
||||
`mapper_xml_dir` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
|
||||
`static_dir` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
|
||||
`plugin_dir_path` varchar(100) DEFAULT NULL,
|
||||
`enable` tinyint(3) unsigned zerofill NOT NULL COMMENT '是否启用,0是禁用,1是启用',
|
||||
`path` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '插件路径,之后要改相对路径',
|
||||
PRIMARY KEY (`plugin_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
-- Dump completed on 2024-06-14 13:55:37
|
||||
|
||||
######################mysql5.7############################
|
||||
DROP TABLE IF EXISTS `sys_plugins`;
|
||||
CREATE TABLE `sys_plugins` (
|
||||
`plugin_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
`plugin_description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
`plugin_class` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
`version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
`requires` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
`provider` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
`dependencies` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
`license` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
`mapper_xml_dir` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
`static_dir` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
`plugin_dir_path` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
`enable` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
`path` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`plugin_id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -1,116 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>index</title>
|
||||
<link rel="stylesheet" type="text/css" href="/easyui.css">
|
||||
<script src="/jquery-2.0.0.min.js"></script>
|
||||
<script type="text/javascript" src="/jquery.easyui.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<strong>Plugin Management</strong>
|
||||
<div style="margin-top: 10px;margin-bottom: 5px">
|
||||
<form id="uploadForm" method="post" enctype="multipart/form-data" class="easyui-form"
|
||||
data-options="novalidate:true">
|
||||
<input type="file" name="file" id="file"/>
|
||||
<input type="button" onclick="upload()" value="Upload"/>
|
||||
<input type="button" onclick="reload()" value="Reload"/>
|
||||
</form>
|
||||
</div>
|
||||
<div id="plugins"></div>
|
||||
<br/>
|
||||
<br/>
|
||||
<strong>Business Feature</strong>
|
||||
<div id="biz" style="margin-top: 5px">
|
||||
<button onclick="buttonClick('Default Button', 'Default msg')">Default Button</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
|
||||
function extButtonClick(name) {
|
||||
$.get('plugin/click?extensionClass=cd.casic.ops.example.OperationButtonExtension&name=' + name, function(data) {
|
||||
$.messager.alert(name, data, 'info');
|
||||
})
|
||||
}
|
||||
|
||||
function buttonClick(name,msg){
|
||||
$.messager.alert(name, msg, 'info');
|
||||
}
|
||||
function pluginOp(op, pluginId) {
|
||||
$.get('plugin/' + op + '?pluginId=' + pluginId);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function upload() {
|
||||
if (!$('#file').get(0).files[0]) {
|
||||
$.messager.alert('Info', "Please choose one plugin package", 'warning');
|
||||
return;
|
||||
}
|
||||
$('#uploadForm').form('submit', {
|
||||
url: "plugin/upload",
|
||||
success: function (result) {
|
||||
$.messager.alert('Info', "Please click 'Reload' button to load uploaded plugins", 'info');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function reload() {
|
||||
$.get('plugin/reload');
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#plugins').datagrid({
|
||||
url: 'plugin/lists',
|
||||
method: 'GET',
|
||||
striped: true,
|
||||
fitColumns: true,
|
||||
pagination: false,
|
||||
columns: [
|
||||
[
|
||||
{field: 'id', title: 'ID'},
|
||||
{field: 'version', title: 'Version'},
|
||||
{field: 'path', title: 'Path'},
|
||||
{field: 'class', title: 'Class'},
|
||||
{field: 'state', title: 'State'},
|
||||
{
|
||||
field: 'action', title: 'Operation', formatter: function (value, row, index) {
|
||||
var html;
|
||||
if (row.state === 'FAILED') {
|
||||
html = '<a href="javascript: void(0);" onclick="pluginOp(\'load\',\'' + row.id + '\')">Load</a>'
|
||||
} else {
|
||||
html = '<a href="javascript: void(0);" onclick="pluginOp(\'unload\',\'' + row.id + '\')">Unload</a>';
|
||||
}
|
||||
if (row.state === 'RESOLVED' || row.state === 'CREATED' || row.state === 'DISABLED' || row.state === 'STOPPED') {
|
||||
html += ' <a href="javascript: void(0);" onclick="pluginOp(\'start\',\'' + row.id + '\')">Start</a>'
|
||||
}
|
||||
if (row.state === 'STARTED') {
|
||||
html += ' <a href="javascript: void(0);" onclick="pluginOp(\'stop\',\'' + row.id + '\')">Stop</a>'
|
||||
}
|
||||
if (row.state === 'STARTED' || row.state === 'CREATED') {
|
||||
html += ' <a href="javascript: void(0);" onclick="pluginOp(\'disable\',\'' + row.id + '\')">Disable</a>'
|
||||
}
|
||||
if (row.state === 'DISABLED') {
|
||||
html += ' <a href="javascript: void(0);" onclick="pluginOp(\'enable\',\'' + row.id + '\')">Enable</a>'
|
||||
}
|
||||
if (row.state !== 'STARTED') {
|
||||
html += ' <a href="javascript: void(0);" onclick="pluginOp(\'delete\',\'' + row.id + '\')">Delete</a>'
|
||||
}
|
||||
return html;
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
});
|
||||
$.get('plugin/list?extensionClass=cd.casic.ops.example.OperationButtonExtension', function(data) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let ext = data[i];
|
||||
$('#biz').append(' <button onclick="extButtonClick(\'' + ext.name + '\')">' + ext.name + '</button>');
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user