浏览代码

增加通用配置功能

zhouhao 2 年之前
父节点
当前提交
14cc4224ef

+ 5 - 0
jetlinks-components/common-component/pom.xml

@@ -55,5 +55,10 @@
             <groupId>com.h2database</groupId>
             <groupId>com.h2database</groupId>
             <artifactId>h2</artifactId>
             <artifactId>h2</artifactId>
         </dependency>
         </dependency>
+
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
     </dependencies>
     </dependencies>
 </project>
 </project>

+ 56 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigManager.java

@@ -0,0 +1,56 @@
+package org.jetlinks.community.config;
+
+import org.jetlinks.community.ValueObject;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Map;
+
+/**
+ * 配置管理器,统一管理系统相关配置信息
+ *
+ * @author zhouhao
+ * @since 2.0
+ */
+public interface ConfigManager {
+
+    /**
+     * 获取全部已经定义的配置作用域
+     *
+     * @return 配置作用域
+     */
+    Flux<ConfigScope> getScopes();
+
+    /**
+     * 获取根据作用域ID获取已经定义的配置作用域
+     *
+     * @return 配置作用域
+     */
+    Mono<ConfigScope> getScope(String scope);
+
+    /**
+     * 获取指定作用域下的属性定义信息
+     *
+     * @param scope 配置作用域
+     * @return 属性定义信息
+     */
+    Flux<ConfigPropertyDef> getPropertyDef(String scope);
+
+    /**
+     * 获取作用于下的全部配置
+     *
+     * @param scope 配置作用域
+     * @return 配置信息
+     */
+    Mono<ValueObject> getProperties(String scope);
+
+    /**
+     * 设置作用域下的配置
+     *
+     * @param scope  作用域
+     * @param values 配置信息
+     * @return void
+     */
+    Mono<Void> setProperties(String scope, Map<String, Object> values);
+
+}

+ 28 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigPropertyDef.java

@@ -0,0 +1,28 @@
+package org.jetlinks.community.config;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import org.jetlinks.core.metadata.types.StringType;
+
+@Getter
+@Setter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+@EqualsAndHashCode(of = "key")
+public class ConfigPropertyDef {
+
+    @Schema(description = "配置key")
+    private String key;
+
+    @Schema(description = "配置名称")
+    private String name;
+
+    @Schema(description = "是否只读")
+    private boolean readonly;
+
+    @Schema(description = "配置类型")
+    private String type = StringType.ID;
+
+    @Schema(description = "默认值")
+    private String defaultValue;
+}

+ 23 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScope.java

@@ -0,0 +1,23 @@
+package org.jetlinks.community.config;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+@Getter
+@Setter
+@AllArgsConstructor(staticName = "of")
+@NoArgsConstructor
+@EqualsAndHashCode(of = "id")
+public class ConfigScope {
+
+    @Schema(description = "ID")
+    private String id;
+
+    @Schema(description = "名称")
+    private String name;
+
+    @Schema(description = "是否公开访问(不需要登录)")
+    private boolean publicAccess;
+
+
+}

+ 18 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScopeCustomizer.java

@@ -0,0 +1,18 @@
+package org.jetlinks.community.config;
+
+/**
+ * 实现此接口,自定义配置域以及配置定义
+ *
+ * @author zhouhao
+ * @since 2.0
+ */
+public interface ConfigScopeCustomizer {
+
+    /**
+     * 执行自定义,通过manager来添加自定义作用域
+     *
+     * @param manager manager
+     */
+    void custom(ConfigScopeManager manager);
+
+}

+ 9 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScopeManager.java

@@ -0,0 +1,9 @@
+package org.jetlinks.community.config;
+
+import java.util.List;
+
+public interface ConfigScopeManager {
+
+    void addScope(ConfigScope scope, List<ConfigPropertyDef> properties);
+
+}

+ 31 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/ConfigScopeProperties.java

@@ -0,0 +1,31 @@
+package org.jetlinks.community.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@ConfigurationProperties(prefix = "system.config")
+public class ConfigScopeProperties implements ConfigScopeCustomizer{
+
+    @Getter
+    @Setter
+    private List<Scope> scopes = new ArrayList<>();
+
+    @Override
+    public void custom(ConfigScopeManager manager) {
+        for (Scope scope : scopes) {
+            manager.addScope(FastBeanCopier.copy(scope,new ConfigScope()), scope.properties);
+        }
+    }
+
+    @Getter
+    @Setter
+    public static class Scope extends ConfigScope {
+        private List<ConfigPropertyDef> properties = new ArrayList<>();
+    }
+
+}

+ 89 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/SimpleConfigManager.java

@@ -0,0 +1,89 @@
+package org.jetlinks.community.config;
+
+import lombok.AllArgsConstructor;
+import org.apache.commons.collections4.MapUtils;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
+import org.jetlinks.community.ValueObject;
+import org.jetlinks.community.config.entity.ConfigEntity;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+@AllArgsConstructor
+public class SimpleConfigManager implements ConfigManager, ConfigScopeManager {
+
+    private final Map<ConfigScope, Set<ConfigPropertyDef>> scopes = new ConcurrentHashMap<>();
+
+    private final ReactiveRepository<ConfigEntity, String> repository;
+
+    @Override
+    public void addScope(ConfigScope scope,
+                         List<ConfigPropertyDef> properties) {
+        scopes.computeIfAbsent(scope, ignore -> new LinkedHashSet<>())
+              .addAll(properties);
+    }
+
+    @Override
+    public Flux<ConfigScope> getScopes() {
+        return Flux.fromIterable(scopes.keySet());
+    }
+
+    @Override
+    public Mono<ConfigScope> getScope(String scope) {
+        return this
+            .getScopes()
+            .filter(configScope -> Objects.equals(configScope.getId(), scope))
+            .take(1)
+            .singleOrEmpty();
+    }
+
+    @Override
+    public Flux<ConfigPropertyDef> getPropertyDef(String scope) {
+        return Flux.fromIterable(scopes.getOrDefault(
+            ConfigScope.of(scope, scope, false),
+            Collections.emptySet()));
+    }
+
+    @Override
+    public Mono<ValueObject> getProperties(String scope) {
+        return Mono
+            .zip(
+                //默认值
+                getPropertyDef(scope)
+                    .filter(def -> null != def.getDefaultValue())
+                    .collectMap(ConfigPropertyDef::getKey, ConfigPropertyDef::getDefaultValue),
+                //数据库配置的值
+                repository
+                    .createQuery()
+                    .where(ConfigEntity::getScope, scope)
+                    .fetch()
+                    .filter(val -> MapUtils.isNotEmpty(val.getProperties()))
+                    .<Map<String, Object>>reduce(new LinkedHashMap<>(), (l, r) -> {
+                        l.putAll(r.getProperties());
+                        return l;
+                    }),
+                (defaults, values) -> {
+                    defaults.forEach(values::putIfAbsent);
+                    return values;
+                }
+            ).map(ValueObject::of);
+    }
+
+    @Override
+    public Mono<Void> setProperties(String scope, Map<String, Object> values) {
+        return Flux
+            .fromIterable(values.entrySet())
+            .map(e -> {
+                ConfigEntity entity = new ConfigEntity();
+                entity.setProperties(values);
+                entity.setScope(scope);
+                entity.getId();
+                return entity;
+            })
+            .as(repository::save)
+            .then();
+    }
+
+}

+ 46 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/entity/ConfigEntity.java

@@ -0,0 +1,46 @@
+package org.jetlinks.community.config.entity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType;
+import org.hswebframework.ezorm.rdb.mapping.annotation.JsonCodec;
+import org.hswebframework.web.api.crud.entity.GenericEntity;
+import org.hswebframework.web.utils.DigestUtils;
+import org.springframework.util.StringUtils;
+
+import javax.persistence.Column;
+import javax.persistence.Index;
+import javax.persistence.Table;
+import java.sql.JDBCType;
+import java.util.Map;
+
+@Table(name = "s_config", indexes = {
+    @Index(name = "idx_conf_scope", columnList = "scope")
+})
+@Getter
+@Setter
+public class ConfigEntity extends GenericEntity<String> {
+
+    @Column(length = 64, nullable = false, updatable = false)
+    @Schema(description = "作用域")
+    private String scope;
+
+    @Column(nullable = false)
+    @Schema
+    @JsonCodec
+    @ColumnType(jdbcType = JDBCType.LONGVARCHAR)
+    private Map<String,Object> properties;
+
+    @Override
+    public String getId() {
+        if (!StringUtils.hasText(super.getId())) {
+            setId(generateId(scope));
+        }
+        return super.getId();
+    }
+
+    public static String generateId(String scope) {
+        return DigestUtils.md5Hex(String.join("|", scope));
+    }
+}

+ 130 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/config/web/SystemConfigManagerController.java

@@ -0,0 +1,130 @@
+package org.jetlinks.community.config.web;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.authorization.annotation.Authorize;
+import org.hswebframework.web.authorization.annotation.QueryAction;
+import org.hswebframework.web.authorization.annotation.Resource;
+import org.hswebframework.web.authorization.annotation.SaveAction;
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.jetlinks.community.ValueObject;
+import org.jetlinks.community.config.ConfigManager;
+import org.jetlinks.community.config.ConfigPropertyDef;
+import org.jetlinks.community.config.ConfigScope;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+
+@RestController
+@RequestMapping("/system/config")
+@Resource(id = "system_config", name = "系统配置管理")
+@AllArgsConstructor
+@Tag(name = "系统配置管理")
+public class SystemConfigManagerController {
+
+    private final ConfigManager configManager;
+
+    @GetMapping("/scopes")
+    @QueryAction
+    @Operation(description = "获取配置作用域")
+    public Flux<ConfigScope> getConfigScopes() {
+        return configManager.getScopes();
+    }
+
+    @GetMapping("/{scope}")
+    @Authorize(ignore = true)
+    @Operation(description = "获取作用域下的全部配置信息")
+    public Mono<Map<String, Object>> getConfigs(@PathVariable String scope) {
+        return Authentication
+            .currentReactive()
+            .hasElement()
+            .flatMap(hasAuth -> configManager
+                .getScope(scope)
+                //公共访问配置或者用户已登录
+                .map(conf -> conf.isPublicAccess() || hasAuth)
+                //没有定义配置,则用户登录即可访问
+                .defaultIfEmpty(hasAuth)
+                .filter(Boolean::booleanValue)
+                .flatMap(ignore -> configManager.getProperties(scope))
+                .map(ValueObject::values))
+            .defaultIfEmpty(Collections.emptyMap());
+    }
+
+    @GetMapping("/{scope}/_detail")
+    @QueryAction
+    @Operation(description = "获取作用域下的配置信息")
+    public Flux<ConfigPropertyValue> getConfigDetail(@PathVariable String scope) {
+        return configManager
+            .getProperties(scope)
+            .flatMapMany(values -> configManager
+                .getPropertyDef(scope)
+                .map(def -> ConfigPropertyValue.of(def, values.get(def.getKey()).orElse(null))));
+
+    }
+
+    @PostMapping("/scopes")
+    @QueryAction
+    @Operation(description = "获取作用域下的配置详情")
+    public Flux<Scope> getConfigDetail(@RequestBody Mono<List<String>> scopeMono) {
+        return scopeMono
+            .flatMapMany(scopes -> Flux
+                .fromIterable(scopes)
+                .flatMap(scope -> getConfigs(scope)
+                    .map(properties -> new Scope(scope, properties))));
+    }
+
+    @PostMapping("/{scope}")
+    @SaveAction
+    @Operation(description = "保存配置")
+    public Mono<Void> saveConfig(@PathVariable String scope,
+                                 @RequestBody Mono<Map<String, Object>> properties) {
+        return properties.flatMap(props -> configManager.setProperties(scope, props));
+
+    }
+
+    @PostMapping("/scope/_save")
+    @SaveAction
+    @Operation(description = "批量保存配置")
+    @Transactional
+    public Mono<Void> saveConfig(@RequestBody Flux<Scope> scope) {
+        return scope
+            .flatMap(scopeConfig -> configManager.setProperties(scopeConfig.getScope(), scopeConfig.getProperties()))
+            .then();
+    }
+
+    @Getter
+    @Setter
+    public static class ConfigPropertyValue extends ConfigPropertyDef {
+        private Object value;
+
+        public static ConfigPropertyValue of(ConfigPropertyDef def, Object value) {
+            ConfigPropertyValue val = FastBeanCopier.copy(def, new ConfigPropertyValue());
+            val.setValue(value);
+            return val;
+        }
+    }
+
+
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class Scope {
+
+        private String scope;
+
+        private Map<String, Object> properties;
+    }
+
+}

+ 21 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/configuration/CommonConfiguration.java

@@ -5,14 +5,22 @@ import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
 import io.netty.buffer.Unpooled;
 import org.apache.commons.beanutils.BeanUtilsBean;
 import org.apache.commons.beanutils.BeanUtilsBean;
 import org.apache.commons.beanutils.Converter;
 import org.apache.commons.beanutils.Converter;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
 import org.jetlinks.community.Interval;
 import org.jetlinks.community.Interval;
+import org.jetlinks.community.config.ConfigManager;
+import org.jetlinks.community.config.ConfigScopeCustomizer;
+import org.jetlinks.community.config.ConfigScopeProperties;
+import org.jetlinks.community.config.SimpleConfigManager;
+import org.jetlinks.community.config.entity.ConfigEntity;
 import org.jetlinks.community.utils.TimeUtils;
 import org.jetlinks.community.utils.TimeUtils;
 import org.jetlinks.reactor.ql.feature.Feature;
 import org.jetlinks.reactor.ql.feature.Feature;
 import org.jetlinks.reactor.ql.supports.DefaultReactorQLMetadata;
 import org.jetlinks.reactor.ql.supports.DefaultReactorQLMetadata;
 import org.jetlinks.reactor.ql.utils.CastUtils;
 import org.jetlinks.reactor.ql.utils.CastUtils;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
 import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.MediaType;
 import org.springframework.http.MediaType;
@@ -25,6 +33,7 @@ import java.util.Date;
 
 
 @Configuration
 @Configuration
 @SuppressWarnings("all")
 @SuppressWarnings("all")
+@EnableConfigurationProperties({ConfigScopeProperties.class})
 public class CommonConfiguration {
 public class CommonConfiguration {
 
 
     static {
     static {
@@ -116,4 +125,16 @@ public class CommonConfiguration {
         };
         };
     }
     }
 
 
+    @Bean
+    public ConfigManager configManager(ObjectProvider<ConfigScopeCustomizer> configScopeCustomizers,
+                                       ReactiveRepository<ConfigEntity, String> repository) {
+
+        SimpleConfigManager configManager = new SimpleConfigManager(repository);
+        for (ConfigScopeCustomizer customizer : configScopeCustomizers) {
+            customizer.custom(configManager);
+        }
+        return configManager;
+    }
+
+
 }
 }

+ 36 - 0
jetlinks-components/common-component/src/main/java/org/jetlinks/community/web/response/ValidationResult.java

@@ -0,0 +1,36 @@
+package org.jetlinks.community.web.response;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author bestfeng
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+public class ValidationResult {
+
+    private boolean passed;
+
+    private String reason;
+
+
+    public static ValidationResult error(String reason) {
+        return new ValidationResult(false, reason);
+    }
+
+    public static ValidationResult success(String reason) {
+        return new ValidationResult(true, reason);
+    }
+
+    public static ValidationResult success() {
+        return ValidationResult.success("");
+    }
+
+    public static ValidationResult of(boolean passed, String reason) {
+        return new ValidationResult(passed, reason);
+    }
+
+}