Browse Source

增加初始化脚本

zhou-hao 5 years ago
parent
commit
616ac7e80d

+ 32 - 0
hsweb-starter/pom.xml

@@ -17,6 +17,12 @@
             <artifactId>jackson-databind</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.hswebframework</groupId>
+            <artifactId>hsweb-expands-script</artifactId>
+            <version>${hsweb.expands.version}</version>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-autoconfigure</artifactId>
@@ -31,5 +37,31 @@
             <groupId>org.springframework</groupId>
             <artifactId>spring-webflux</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-test-autoconfigure</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot.experimental</groupId>
+            <artifactId>spring-boot-starter-data-r2dbc</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.r2dbc</groupId>
+            <artifactId>r2dbc-h2</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>

+ 66 - 4
hsweb-starter/src/main/java/org/hswebframework/web/starter/HswebAutoConfiguration.java

@@ -1,14 +1,76 @@
 package org.hswebframework.web.starter;
 
-import org.hswebframework.web.api.crud.entity.EntityFactory;
-import org.hswebframework.web.crud.entity.factory.MapperEntityFactory;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.hswebframework.expands.script.engine.DynamicScriptEngine;
+import org.hswebframework.expands.script.engine.DynamicScriptEngineFactory;
+import org.hswebframework.ezorm.rdb.executor.SyncSqlExecutor;
+import org.hswebframework.ezorm.rdb.executor.reactive.ReactiveSqlExecutor;
+import org.hswebframework.ezorm.rdb.executor.reactive.ReactiveSyncSqlExecutor;
+import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
+import org.hswebframework.web.starter.initialize.AppProperties;
+import org.hswebframework.web.starter.initialize.SystemInitialize;
+import org.hswebframework.web.starter.initialize.SystemVersion;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import javax.annotation.PostConstruct;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
 @Configuration
-public class HswebAutoConfiguration  {
+@EnableConfigurationProperties(AppProperties.class)
+public class HswebAutoConfiguration {
+
+    private List<DynamicScriptEngine> engines;
+
+    @Autowired
+    private ApplicationContext applicationContext;
+
+
+    @PostConstruct
+    public void init() {
+        engines = Stream.of("js", "groovy")
+                .map(DynamicScriptEngineFactory::getEngine)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        addGlobalVariable("logger", LoggerFactory.getLogger("org.hswebframework.script"));
+
+        addGlobalVariable("spring", applicationContext);
+    }
+
+    private void addGlobalVariable(String var, Object val) {
+        engines.forEach(engine -> {
+                    try {
+                        engine.addGlobalVariable(Collections.singletonMap(var, val));
+                    } catch (NullPointerException ignore) {
+                    }
+                }
+        );
+    }
+
+    @Bean
+    public CommandLineRunner systemInit(DatabaseOperator database,
+                                        AppProperties properties) {
 
+        addGlobalVariable("database", database);
+        addGlobalVariable("sqlExecutor", database.getMetadata().getFeature(SyncSqlExecutor.ID)
+                .orElseGet(() -> database.getMetadata().getFeature(ReactiveSqlExecutor.ID)
+                        .map(ReactiveSyncSqlExecutor::of).orElse(null)));
+        SystemVersion version = properties.build();
+        return args -> {
 
+            SystemInitialize initialize = new SystemInitialize(database, version);
+            initialize.setExcludeTables(properties.getInitTableExcludes());
+            initialize.install();
+        };
+    }
 
 }

+ 29 - 0
hsweb-starter/src/main/java/org/hswebframework/web/starter/initialize/AppProperties.java

@@ -0,0 +1,29 @@
+package org.hswebframework.web.starter.initialize;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.List;
+
+@ConfigurationProperties(prefix = "hsweb.app")
+@Getter
+@Setter
+public class AppProperties {
+    private boolean      autoInit = true;
+    private List<String> initTableExcludes;
+
+    private String name;
+    private String comment;
+    private String website;
+    private String version;
+
+    public SystemVersion build() {
+        SystemVersion systemVersion = new SystemVersion();
+        systemVersion.setName(name);
+        systemVersion.setComment(comment);
+        systemVersion.setWebsite(website);
+        systemVersion.setVersion(version);
+        return systemVersion;
+    }
+}

+ 10 - 0
hsweb-starter/src/main/java/org/hswebframework/web/starter/initialize/CallBack.java

@@ -0,0 +1,10 @@
+package org.hswebframework.web.starter.initialize;
+
+import java.util.Map;
+
+/**
+ * @author zhouhao
+ */
+public interface CallBack {
+    void execute(Map<String, Object> context);
+}

+ 70 - 0
hsweb-starter/src/main/java/org/hswebframework/web/starter/initialize/DefaultDependencyUpgrader.java

@@ -0,0 +1,70 @@
+package org.hswebframework.web.starter.initialize;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author zhouhao
+ */
+public class DefaultDependencyUpgrader implements DependencyUpgrader {
+    private Logger logger = LoggerFactory.getLogger(this.getClass());
+    SystemVersion.Dependency  installed;
+    SystemVersion.Dependency  dependency;
+    List<Map<String, Object>> shouldUpdateVersionList;
+    private Map<String, Object> context;
+    private boolean             firstInstall;
+
+    public DefaultDependencyUpgrader(SystemVersion.Dependency installed, SystemVersion.Dependency dependency, Map<String, Object> context) {
+        this.firstInstall = installed == null;
+        if (firstInstall) {
+            this.installed = dependency;
+        } else {
+            this.installed = installed;
+        }
+        this.context = context;
+        this.dependency = dependency;
+    }
+
+    @Override
+    public DependencyUpgrader filter(List<Map<String, Object>> versions) {
+        shouldUpdateVersionList = versions.stream()
+                .filter(map -> {
+                    String ver = (String) map.get("version");
+                    if (null == ver) {
+                        return false;
+                    }
+                    //首次安装
+                    if (firstInstall) {
+                        return true;
+                    }
+                    //相同版本
+                    if (installed.compareTo(dependency) == 0) {
+                        return false;
+                    }
+
+                    return installed.compareTo(new SystemVersion(ver)) < 0;
+                })
+                .sorted(Comparator.comparing(m -> new SystemVersion((String) m.get("version"))))
+                .collect(Collectors.toList());
+        return this;
+    }
+
+    @Override
+    public void upgrade(CallBack callBack) {
+        shouldUpdateVersionList.forEach(context -> {
+            if (this.context != null) {
+                context.putAll(context);
+            }
+            if (logger.isInfoEnabled()) {
+                logger.info("upgrade [{}/{}] to version:{} {}", dependency.getGroupId(), dependency.getArtifactId(), context.get("version"), dependency.getWebsite());
+            }
+            callBack.execute(context);
+        });
+    }
+
+}

+ 41 - 0
hsweb-starter/src/main/java/org/hswebframework/web/starter/initialize/DependencyInstaller.java

@@ -0,0 +1,41 @@
+/*
+ *
+ *  * Copyright 2019 http://www.hswebframework.org
+ *  *
+ *  * Licensed under the Apache License, Version 2.0 (the "License");
+ *  * you may not use this file except in compliance with the License.
+ *  * You may obtain a copy of the License at
+ *  *
+ *  *     http://www.apache.org/licenses/LICENSE-2.0
+ *  *
+ *  * Unless required by applicable law or agreed to in writing, software
+ *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  * See the License for the specific language governing permissions and
+ *  * limitations under the License.
+ *
+ */
+
+package org.hswebframework.web.starter.initialize;
+
+import java.util.Map;
+
+
+/**
+ * @author zhouhao
+ */
+public interface DependencyInstaller {
+    DependencyInstaller setup(SystemVersion.Dependency dependency);
+
+    default DependencyInstaller setup(Map<String, Object> mapDependency) {
+        return setup(SystemVersion.Dependency.fromMap(mapDependency));
+    }
+
+    DependencyInstaller onInstall(CallBack callBack);
+
+    DependencyInstaller onUpgrade(CallBack callBack);
+
+    DependencyInstaller onUninstall(CallBack callBack);
+
+    DependencyInstaller onInitialize(CallBack callBack);
+}

+ 32 - 0
hsweb-starter/src/main/java/org/hswebframework/web/starter/initialize/DependencyUpgrader.java

@@ -0,0 +1,32 @@
+/*
+ *
+ *  * Copyright 2019 http://www.hswebframework.org
+ *  *
+ *  * Licensed under the Apache License, Version 2.0 (the "License");
+ *  * you may not use this file except in compliance with the License.
+ *  * You may obtain a copy of the License at
+ *  *
+ *  *     http://www.apache.org/licenses/LICENSE-2.0
+ *  *
+ *  * Unless required by applicable law or agreed to in writing, software
+ *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  * See the License for the specific language governing permissions and
+ *  * limitations under the License.
+ *
+ */
+
+package org.hswebframework.web.starter.initialize;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author zhouhao
+ */
+public interface DependencyUpgrader {
+    DependencyUpgrader filter(List<Map<String, Object>> versions);
+
+    void upgrade(CallBack context);
+
+}

+ 88 - 0
hsweb-starter/src/main/java/org/hswebframework/web/starter/initialize/SimpleDependencyInstaller.java

@@ -0,0 +1,88 @@
+package org.hswebframework.web.starter.initialize;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+/**
+ * @author zhouhao
+ */
+public class SimpleDependencyInstaller implements DependencyInstaller {
+    SystemVersion.Dependency dependency;
+    CallBack installer;
+    CallBack upgrader;
+    CallBack unInstaller;
+    CallBack initializer;
+    private Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    public SimpleDependencyInstaller() {
+    }
+
+    public SystemVersion.Dependency getDependency() {
+        return dependency;
+    }
+
+    public void doInstall(Map<String, Object> context) {
+        if (installer != null) {
+            if (logger.isInfoEnabled()) {
+                logger.info("install [{}/{}]", dependency.getGroupId(), dependency.getArtifactId());
+            }
+            installer.execute(context);
+        }
+    }
+
+    public void doInitialize(Map<String, Object> context) {
+        if (initializer != null) {
+            if (logger.isInfoEnabled()) {
+                logger.info("initialize [{}/{}]", dependency.getGroupId(), dependency.getArtifactId());
+            }
+            initializer.execute(context);
+        }
+    }
+
+    public void doUnInstall(Map<String, Object> context) {
+        if (unInstaller != null) {
+            unInstaller.execute(context);
+        }
+    }
+
+    public void doUpgrade(Map<String, Object> context, SystemVersion.Dependency installed) {
+        DefaultDependencyUpgrader defaultDependencyUpgrader =
+                new DefaultDependencyUpgrader(installed, dependency, context);
+        context.put("upgrader", defaultDependencyUpgrader);
+        if (upgrader != null) {
+            upgrader.execute(context);
+        }
+    }
+
+    @Override
+    public DependencyInstaller setup(SystemVersion.Dependency dependency) {
+        this.dependency = dependency;
+        return this;
+    }
+
+    @Override
+    public DependencyInstaller onInstall(CallBack callBack) {
+        this.installer = callBack;
+        return this;
+    }
+
+    @Override
+    public DependencyInstaller onUpgrade(CallBack callBack) {
+        this.upgrader = callBack;
+        return this;
+    }
+
+    @Override
+    public DependencyInstaller onUninstall(CallBack callBack) {
+        this.unInstaller = callBack;
+        return this;
+    }
+
+    @Override
+    public DependencyInstaller onInitialize(CallBack initializeCallBack) {
+        this.initializer = initializeCallBack;
+        return this;
+    }
+}

+ 217 - 0
hsweb-starter/src/main/java/org/hswebframework/web/starter/initialize/SystemInitialize.java

@@ -0,0 +1,217 @@
+package org.hswebframework.web.starter.initialize;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.expands.script.engine.DynamicScriptEngine;
+import org.hswebframework.expands.script.engine.DynamicScriptEngineFactory;
+import org.hswebframework.ezorm.rdb.codec.ClobValueCodec;
+import org.hswebframework.ezorm.rdb.codec.CompositeValueCodec;
+import org.hswebframework.ezorm.rdb.codec.JsonValueCodec;
+import org.hswebframework.ezorm.rdb.executor.SyncSqlExecutor;
+import org.hswebframework.ezorm.rdb.executor.reactive.ReactiveSqlExecutor;
+import org.hswebframework.ezorm.rdb.executor.reactive.ReactiveSyncSqlExecutor;
+import org.hswebframework.ezorm.rdb.mapping.SyncRepository;
+import org.hswebframework.ezorm.rdb.mapping.defaults.record.Record;
+import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
+import org.hswebframework.web.bean.FastBeanCopier;
+import org.hswebframework.web.starter.initialize.SystemVersion.Dependency;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.util.StreamUtils;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author zhouhao
+ */
+public class SystemInitialize {
+    private Logger logger = LoggerFactory.getLogger(SystemInitialize.class);
+
+    private DatabaseOperator database;
+    //将要安装的信息
+    private SystemVersion targetVersion;
+
+    //已安装的信息
+    private SystemVersion installed;
+
+    private List<SimpleDependencyInstaller> readyToInstall = new ArrayList<>();
+
+    @Setter
+    @Getter
+    private List<String> excludeTables;
+
+    private String installScriptPath = "classpath*:hsweb-starter.js";
+
+    private Map<String, Object> scriptContext = new HashMap<>();
+
+    private boolean initialized = false;
+
+    private SyncRepository<Record, String> system;
+
+    public SystemInitialize(DatabaseOperator database, SystemVersion targetVersion) {
+        this.database = database;
+        this.targetVersion = targetVersion;
+    }
+
+
+    public void init() {
+        if (initialized) {
+            return;
+        }
+//        if (!CollectionUtils.isEmpty(excludeTables)) {
+//            this.database = new SkipCreateOrAlterRDBDatabase(database, excludeTables, sqlExecutor);
+//        }
+
+        scriptContext.put("database", database);
+        scriptContext.put("logger", logger);
+        initialized = true;
+    }
+
+    public void addScriptContext(String var, Object val) {
+        scriptContext.put(var, val);
+    }
+
+    protected void syncSystemVersion() {
+        Map<String, Object> mapVersion = FastBeanCopier.copy(targetVersion, HashMap::new);
+
+        if (installed == null) {
+            system.insert(Record.newRecord(mapVersion));
+        } else {
+
+            //合并已安装的依赖
+            //修复如果删掉了依赖,再重启会丢失依赖信息的问题
+            for (Dependency dependency : installed.getDependencies()) {
+                Dependency target = targetVersion.getDependency(dependency.getGroupId(), dependency.getArtifactId());
+                if (target == null) {
+                    targetVersion.getDependencies().add(dependency);
+                }
+            }
+            mapVersion = FastBeanCopier.copy(targetVersion, HashMap::new);
+            system.createUpdate().set(Record.newRecord(mapVersion))
+                    .where(dsl -> dsl.is(targetVersion::getName))
+                    .execute();
+        }
+    }
+
+    protected Map<String, Object> getScriptContext() {
+        return new HashMap<>(scriptContext);
+    }
+
+    protected void doInstall() {
+        List<SimpleDependencyInstaller> doInitializeDep = new ArrayList<>();
+        List<Dependency> installedDependencies =
+                readyToInstall.stream().map(installer -> {
+                    Dependency dependency = installer.getDependency();
+                    Dependency installed = getInstalledDependency(dependency.getGroupId(), dependency.getArtifactId());
+                    //安装依赖
+                    if (installed == null) {
+                        doInitializeDep.add(installer);
+                          installer.doInstall(getScriptContext());
+                    }
+                    //更新依赖
+                    if (installed == null || installed.compareTo(dependency) < 0) {
+                         installer.doUpgrade(getScriptContext(), installed);
+                    }
+                    return dependency;
+                }).collect(Collectors.toList());
+
+        for (SimpleDependencyInstaller installer : doInitializeDep) {
+            installer.doInitialize(getScriptContext());
+        }
+        targetVersion.setDependencies(installedDependencies);
+    }
+
+    private Dependency getInstalledDependency(String groupId, String artifactId) {
+        if (installed == null) {
+            return null;
+        }
+        return installed.getDependency(groupId, artifactId);
+    }
+
+    private SimpleDependencyInstaller getReadyToInstallDependency(String groupId, String artifactId) {
+        if (readyToInstall == null) {
+            return null;
+        }
+        return readyToInstall.stream()
+                .filter(installer -> installer.getDependency().isSameDependency(groupId, artifactId))
+                .findFirst().orElse(null);
+    }
+
+    private void initReadyToInstallDependencies() {
+        DynamicScriptEngine engine = DynamicScriptEngineFactory.getEngine("js");
+        try {
+            Resource[] resources = new PathMatchingResourcePatternResolver().getResources(installScriptPath);
+            List<SimpleDependencyInstaller> installers = new ArrayList<>();
+            for (Resource resource : resources) {
+                String script = StreamUtils.copyToString(resource.getInputStream(), Charset.forName("utf-8"));
+                SimpleDependencyInstaller installer = new SimpleDependencyInstaller();
+                engine.compile("__tmp", script);
+                Map<String, Object> context = getScriptContext();
+                context.put("dependency", installer);
+                engine.execute("__tmp", context).getIfSuccess();
+                installers.add(installer);
+            }
+            readyToInstall = installers;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            engine.remove("__tmp");
+        }
+
+    }
+
+    protected void initInstallInfo() {
+        boolean tableInstall = database.getMetadata().getTable("s_system").isPresent();
+        database.ddl().createOrAlter("s_system")
+                .addColumn().name("name").varchar(128).comment("系统名称").commit()
+                .addColumn().name("major_version").alias("majorVersion").integer().comment("主版本号").commit()
+                .addColumn().name("minor_version").alias("minorVersion").integer().comment("次版本号").commit()
+                .addColumn().name("revision_version").alias("revisionVersion").integer().comment("修订版").commit()
+                .addColumn().name("comment").varchar(2000).comment("系统说明").commit()
+                .addColumn().name("website").varchar(2000).comment("系统网址").commit()
+                .addColumn().name("framework_version").notNull().alias("frameworkVersion").clob()
+                .custom(column ->
+                        column.setValueCodec(new CompositeValueCodec()
+                                .addEncoder(JsonValueCodec.of(SystemVersion.FrameworkVersion.class))
+                                .addDecoder(ClobValueCodec.INSTANCE)
+                                .addDecoder(JsonValueCodec.of(SystemVersion.FrameworkVersion.class)))).notNull().comment("框架版本").commit()
+                .addColumn().name("dependencies").notNull().alias("dependencies").clob()
+                .custom(column -> column.setValueCodec(new CompositeValueCodec()
+                        .addEncoder(JsonValueCodec.ofCollection(List.class, Dependency.class))
+                        .addDecoder(ClobValueCodec.INSTANCE)
+                        .addDecoder(JsonValueCodec.ofCollection(List.class, Dependency.class)))).notNull().comment("依赖详情").commit()
+                .comment("系统信息")
+                .commit()
+                .sync();
+        system = database.dml().createRepository("s_system");
+
+        if (!tableInstall) {
+            installed = null;
+            return;
+        }
+
+        installed = system.createQuery()
+                .where(dsl -> dsl.is("name", targetVersion.getName()))
+                .paging(0, 1)
+                .fetchOne()
+                .map(r -> FastBeanCopier.copy(r, SystemVersion::new))
+                .orElse(null)
+        ;
+    }
+
+
+    public void install() throws Exception {
+        init();
+        initInstallInfo();
+        initReadyToInstallDependencies();
+        doInstall();
+        syncSystemVersion();
+    }
+}

+ 267 - 0
hsweb-starter/src/main/java/org/hswebframework/web/starter/initialize/SystemVersion.java

@@ -0,0 +1,267 @@
+/*
+ *
+ *  * Copyright 2019 http://www.hswebframework.org
+ *  *
+ *  * Licensed under the Apache License, Version 2.0 (the "License");
+ *  * you may not use this file except in compliance with the License.
+ *  * You may obtain a copy of the License at
+ *  *
+ *  *     http://www.apache.org/licenses/LICENSE-2.0
+ *  *
+ *  * Unless required by applicable law or agreed to in writing, software
+ *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  * See the License for the specific language governing permissions and
+ *  * limitations under the License.
+ *
+ */
+
+package org.hswebframework.web.starter.initialize;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.utils.ListUtils;
+import org.hswebframework.utils.StringUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SystemVersion extends Version {
+
+    public SystemVersion() {
+    }
+
+    public SystemVersion(String version) {
+        this.setVersion(version);
+    }
+
+    private FrameworkVersion frameworkVersion = new FrameworkVersion();
+
+    private List<Dependency> dependencies = new ArrayList<>();
+
+    public FrameworkVersion getFrameworkVersion() {
+        return frameworkVersion;
+    }
+
+    public void setFrameworkVersion(FrameworkVersion frameworkVersion) {
+        this.frameworkVersion = frameworkVersion;
+    }
+
+    public List<Dependency> getDependencies() {
+        return dependencies;
+    }
+
+    public void setDependencies(List<Dependency> dependencies) {
+        this.dependencies = dependencies;
+        initDepCache();
+    }
+
+    private Map<String, Dependency> depCache;
+
+    protected String getDepKey(String groupId, String artifactId) {
+        return StringUtils.concat(groupId, "/", artifactId);
+    }
+
+    protected void initDepCache() {
+        depCache = new HashMap<>();
+        dependencies.forEach(dependency -> depCache.put(getDepKey(dependency.groupId, dependency.artifactId), dependency));
+    }
+
+    public Dependency getDependency(String groupId, String artifactId) {
+        if (depCache == null) {
+            initDepCache();
+        }
+        return depCache.get(getDepKey(groupId, artifactId));
+    }
+
+    public static class FrameworkVersion extends Version {
+        public FrameworkVersion() {
+            setName("hsweb framework");
+            setComment("企业后台管理系统基础框架");
+            setWebsite("http://www.hsweb.me");
+            setComment("");
+            setVersion(4, 0, 0);
+        }
+    }
+
+
+    public static class Dependency extends Version {
+        protected String groupId;
+        protected String artifactId;
+        protected String author;
+
+        public String getGroupId() {
+            return groupId;
+        }
+
+        public void setGroupId(String groupId) {
+            this.groupId = groupId;
+        }
+
+        public String getArtifactId() {
+            return artifactId;
+        }
+
+        public void setArtifactId(String artifactId) {
+            this.artifactId = artifactId;
+        }
+
+        public String getAuthor() {
+            return author;
+        }
+
+        public void setAuthor(String author) {
+            this.author = author;
+        }
+
+        public static Dependency fromMap(Map<String, Object> map) {
+            Dependency dependency = new Dependency();
+            dependency.setGroupId((String) map.get("groupId"));
+            dependency.setArtifactId((String) map.get("artifactId"));
+            dependency.setName((String) map.getOrDefault("name", dependency.getArtifactId()));
+            dependency.setVersion((String) map.get("version"));
+            dependency.setWebsite((String) map.get("website"));
+            dependency.setAuthor((String) map.get("author"));
+            return dependency;
+        }
+
+        public boolean isSameDependency(Dependency dependency) {
+            return isSameDependency(dependency.getGroupId(), dependency.getArtifactId());
+        }
+
+        public boolean isSameDependency(String groupId, String artifactId) {
+            return groupId.equals(this.getGroupId()) && artifactId.equals(this.getArtifactId());
+        }
+
+        @Override
+        public String toString() {
+            return JSON.toJSONString(this, SerializerFeature.PrettyFormat);
+        }
+    }
+}
+
+@Slf4j
+class Version implements Comparable<Version> {
+    protected String  name;
+    protected String  comment;
+    protected String  website;
+    protected int     majorVersion    = 1;
+    protected int     minorVersion    = 0;
+    protected int     revisionVersion = 0;
+
+    public void setVersion(int major, int minor, int revision) {
+        this.majorVersion = major;
+        this.minorVersion = minor;
+        this.revisionVersion = revision;
+    }
+
+    public void setVersion(String version) {
+        if (null == version) {
+            return;
+        }
+        version = version.toLowerCase();
+
+        boolean snapshot = version.toLowerCase().contains("snapshot");
+
+        String[] ver = version.split("[-]")[0].split("[.]");
+        Integer[] numberVer = ListUtils.stringArr2intArr(ver);
+        if (numberVer.length == 0) {
+            numberVer = new Integer[]{1, 0, 0};
+            log.warn("解析版本号失败:{},将使用默认版本号:1.0.0,请检查hsweb-starter.js配置内容!", version);
+        }
+
+        for (int i = 0; i < numberVer.length; i++) {
+            if (numberVer[i] == null) {
+                numberVer[i] = 0;
+            }
+        }
+        setVersion(numberVer[0],
+                numberVer.length <= 1 ? 0 : numberVer[1],
+                numberVer.length <= 2 ? 0 : numberVer[2]);
+    }
+
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    public String getWebsite() {
+        if (website == null) {
+            website = "";
+        }
+        return website;
+    }
+
+    public void setWebsite(String website) {
+        this.website = website;
+    }
+
+    public int getMajorVersion() {
+        return majorVersion;
+    }
+
+    public void setMajorVersion(int majorVersion) {
+        this.majorVersion = majorVersion;
+    }
+
+    public int getMinorVersion() {
+        return minorVersion;
+    }
+
+    public void setMinorVersion(int minorVersion) {
+        this.minorVersion = minorVersion;
+    }
+
+    public int getRevisionVersion() {
+        return revisionVersion;
+    }
+
+    public void setRevisionVersion(int revisionVersion) {
+        this.revisionVersion = revisionVersion;
+    }
+
+    @Override
+    public int compareTo(Version o) {
+        if (null == o) {
+            return -1;
+        }
+        if (o.getMajorVersion() > this.getMajorVersion()) {
+            return -1;
+        }
+        if (o.getMajorVersion() == this.getMajorVersion()) {
+            if (o.getMinorVersion() > this.getMinorVersion()) {
+                return -1;
+            }
+            if (o.getMinorVersion() == this.getMinorVersion()) {
+                return Integer.compare(this.getRevisionVersion(), o.getRevisionVersion());
+            } else {
+                return 1;
+            }
+        } else {
+            return 1;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return name + " version " +
+                majorVersion + "." +
+                minorVersion;
+    }
+
+}

+ 2 - 1
hsweb-starter/src/main/resources/META-INF/spring.factories

@@ -1,3 +1,4 @@
 # Auto Configure
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-org.hswebframework.web.starter.jackson.CustomCodecsAutoConfiguration
+org.hswebframework.web.starter.jackson.CustomCodecsAutoConfiguration,\
+org.hswebframework.web.starter.HswebAutoConfiguration

+ 27 - 0
hsweb-starter/src/test/java/org/hswebframework/web/starter/initialize/SystemInitializeTest.java

@@ -0,0 +1,27 @@
+package org.hswebframework.web.starter.initialize;
+
+import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.junit.Assert.*;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = TestApplication.class)
+public class SystemInitializeTest {
+
+
+    @Autowired
+    DatabaseOperator databaseOperator;
+
+    @Test
+    public void test(){
+        Assert.assertTrue(databaseOperator.getMetadata().getTable("s_user").isPresent());
+
+    }
+
+}

+ 7 - 0
hsweb-starter/src/test/java/org/hswebframework/web/starter/initialize/TestApplication.java

@@ -0,0 +1,7 @@
+package org.hswebframework.web.starter.initialize;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class TestApplication {
+}

+ 100 - 0
hsweb-starter/src/test/resources/hsweb-starter.js

@@ -0,0 +1,100 @@
+/*
+ *
+ *  * Copyright 2019 http://www.hswebframework.org
+ *  *
+ *  * Licensed under the Apache License, Version 2.0 (the "License");
+ *  * you may not use this file except in compliance with the License.
+ *  * You may obtain a copy of the License at
+ *  *
+ *  *     http://www.apache.org/licenses/LICENSE-2.0
+ *  *
+ *  * Unless required by applicable law or agreed to in writing, software
+ *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  * See the License for the specific language governing permissions and
+ *  * limitations under the License.
+ *
+ */
+//组件信息
+var info = {
+    groupId: "org.hswebframework",
+    artifactId: "hsweb-starter-test",
+    version: "4.0.0",
+    configClass: "",
+    website: "http://github.com/hs-web",
+    comment: "测试"
+};
+
+//版本更新信息
+var versions = [
+    {
+        version: "4.0.0",
+        upgrade: function (context) {
+            java.lang.System.out.println("更新到3.0.0了");
+        }
+    },
+    {
+        version: "4.0.1",
+        upgrade: function (context) {
+            java.lang.System.out.println("更新到3.0.1了");
+        }
+    },
+    {
+        version: "4.0.2",
+        upgrade: function (context) {
+            java.lang.System.out.println("更新到3.0.2了");
+        }
+    }
+];
+
+function install(context) {
+    var database = context.database;
+    database.createOrAlter("s_user")
+        .addColumn().name("u_id").varchar(32).notNull().primaryKey().comment("uid").commit()
+        .addColumn().name("name").varchar(128).notNull().comment("姓名").commit()
+        .addColumn().name("username").varchar(128).notNull().comment("用户名").commit()
+        .addColumn().name("password").varchar(128).notNull().comment("密码").commit()
+        .addColumn().name("salt").varchar(128).notNull().comment("密码盐").commit()
+        .addColumn().name("status").number(4).notNull().comment("用户状态").commit()
+        .addColumn().name("last_login_ip").varchar(128).comment("上一次登录的ip地址").commit()
+        .addColumn().name("last_login_time").number(32).comment("上一次登录时间").commit()
+        .addColumn().name("creator_id").varchar(32).comment("创建者ID").commit()
+        .addColumn().name("create_time").number(32).notNull().comment("创建时间").commit()
+        .comment("用户表")
+        .commit()
+        .sync();
+
+    database.createOrAlter("s_user_test")
+        .addColumn().name("u_id").varchar(32).notNull().primaryKey().comment("uid").commit()
+        .addColumn().name("name").varchar(128).notNull().comment("姓名").commit()
+        .addColumn().name("username").varchar(128).notNull().comment("用户名").commit()
+        .addColumn().name("password").varchar(128).notNull().comment("密码").commit()
+        .addColumn().name("salt").varchar(128).notNull().comment("密码盐").commit()
+        .addColumn().name("status").number(4).notNull().comment("用户状态").commit()
+        .addColumn().name("last_login_ip").varchar(128).comment("上一次登录的ip地址").commit()
+        .addColumn().name("last_login_time").number(32).comment("上一次登录时间").commit()
+        .addColumn().name("creator_id").varchar(32).comment("创建者ID").commit()
+        .addColumn().name("create_time").number(32).notNull().comment("创建时间").commit()
+        .comment("测试用户表")
+        .commit()
+        .sync();
+
+    java.lang.System.out.println("安装了");
+}
+
+
+//设置依赖
+dependency.setup(info)
+    .onInstall(install)
+    .onUpgrade(function (context) { //更新时执行
+        var upgrader = context.upgrader;
+        upgrader.filter(versions)
+            .upgrade(function (newVer) {
+                newVer.upgrade(context);
+            });
+    })
+    .onUninstall(function (context) { //卸载时执行
+
+    }).onInitialize(function (context) {
+     java.lang.System.out.println("初始化啦");
+});