Sfoglia il codice sorgente

基本完善了应用编排的 CRUD 接口和前端页面

reghao 5 anni fa
parent
commit
e0b7f1dd39
39 ha cambiato i file con 540 aggiunte e 314 eliminazioni
  1. 4 0
      common/src/main/java/cn/reghao/autodop/common/dagent/app/api/data/deploy/PackType.java
  2. 5 1
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/AppType.java
  3. 0 16
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/ConfigOptions.java
  4. 5 1
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/EnvType.java
  5. 23 1
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/NotifyType.java
  6. 5 1
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/StatusOps.java
  7. 5 1
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/build/CompileType.java
  8. 5 1
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/build/RepoAuthType.java
  9. 5 1
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/build/RepoType.java
  10. 2 1
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/controller/BuildDeployController.java
  11. 26 5
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/controller/config/AppConfigController.java
  12. 26 0
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/controller/config/NotifyTest.java
  13. 8 51
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/controller/view/AppConfigPageController.java
  14. 97 39
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/db/crud/config/AppCrudService.java
  15. 7 3
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/db/crud/config/ProjCrudService.java
  16. 12 12
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/db/crud/config/SharedEntityChecker.java
  17. 2 0
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/AppProperty.java
  18. 5 0
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/AppOrchestration.java
  19. 3 0
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/NotifyReceiver.java
  20. 4 0
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/ProjOrchestration.java
  21. 3 1
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/build/CompilerConfig.java
  22. 3 1
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/build/PackerConfig.java
  23. 5 3
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/build/RepoAuthConfig.java
  24. 2 6
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/deploy/RunningConfig.java
  25. 4 6
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/log/BuildLog.java
  26. 5 4
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/interceptor/AppIntegrateReinitInterceptor.java
  27. 0 2
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/repository/AppBuildingRepository.java
  28. 3 0
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/repository/AppRunningRepository.java
  29. 76 73
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/service/AppBuildService.java
  30. 64 0
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/validator/EnumValidator.java
  31. 27 0
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/validator/ValidEnum.java
  32. 3 3
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/vo/BuildConfig.java
  33. 13 0
      dmaster/src/main/java/cn/reghao/autodop/dmaster/app/vo/NewApp.java
  34. 3 1
      dmaster/src/main/java/cn/reghao/autodop/dmaster/common/orm/CrudOps.java
  35. 2 2
      dmaster/src/main/resources/static/js/main.js
  36. 59 70
      dmaster/src/main/resources/templates/app/config/app/add.html
  37. 3 3
      dmaster/src/main/resources/templates/app/config/app/copy.html
  38. 6 2
      dmaster/src/main/resources/templates/app/config/app/detail.html
  39. 10 3
      dmaster/src/main/resources/templates/app/config/app/index.html

+ 4 - 0
common/src/main/java/cn/reghao/autodop/common/dagent/app/api/data/deploy/PackType.java

@@ -8,4 +8,8 @@ package cn.reghao.autodop.common.dagent.app.api.data.deploy;
  */
 public enum PackType {
     docker, zip;
+
+    public String getName() {
+        return this.name();
+    }
 }

+ 5 - 1
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/AppType.java

@@ -7,5 +7,9 @@ package cn.reghao.autodop.dmaster.app.constant;
  * @date 2019-10-18 14:31:29
  */
 public enum AppType {
-    maven, dotnetCore, npm, cmake, gradle
+    maven, dotnetCore, npm, cmake, gradle;
+
+    public String getName() {
+        return this.name();
+    }
 }

+ 0 - 16
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/ConfigOptions.java

@@ -1,16 +0,0 @@
-package cn.reghao.autodop.dmaster.app.constant;
-
-/**
- * 需要配置的类型
- *
- * @author reghao
- * @date 2019-10-18 14:31:29
- */
-public enum ConfigOptions {
-    app, env,
-    repoConfig, compilerConfig, packerConfig, notifierConfig,
-    notifier,
-    repo, compiler, packer, buildDir,
-    repoAuth,
-    proj
-}

+ 5 - 1
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/EnvType.java

@@ -7,5 +7,9 @@ package cn.reghao.autodop.dmaster.app.constant;
  * @date 2019-10-18 14:31:29
  */
 public enum EnvType {
-    dev, test, prod
+    dev, test, prod;
+
+    public String getName() {
+        return this.name();
+    }
 }

+ 23 - 1
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/NotifyType.java

@@ -7,5 +7,27 @@ package cn.reghao.autodop.dmaster.app.constant;
  * @date 2020-03-01 17:18:53
  */
 public enum NotifyType {
-    webhook, email, sms
+    webhook(0, "Webhook"),
+    email(1, "邮件"),
+    sms(2, "短信");
+
+    private final Integer code;
+    private final String desc;
+
+    NotifyType(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public String getName() {
+        return this.name();
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
 }

+ 5 - 1
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/StatusOps.java

@@ -7,5 +7,9 @@ package cn.reghao.autodop.dmaster.app.constant;
  * @date 2019-10-18 14:31:29
  */
 public enum StatusOps {
-    restart, stop, start
+    restart, stop, start;
+
+    public String getName() {
+        return this.name();
+    }
 }

+ 5 - 1
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/build/CompileType.java

@@ -7,5 +7,9 @@ package cn.reghao.autodop.dmaster.app.constant.build;
  * @date 2019-10-18 14:31:29
  */
 public enum CompileType {
-    no_need_compile, shell, maven, docker
+    no_need_compile, shell, maven, docker;
+
+    public String getName() {
+        return this.name();
+    }
 }

+ 5 - 1
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/build/RepoAuthType.java

@@ -5,5 +5,9 @@ package cn.reghao.autodop.dmaster.app.constant.build;
  * @date 2021-02-05 18:50:01
  */
 public enum RepoAuthType {
-    http, ssh
+    http, ssh;
+
+    public String getName() {
+        return this.name();
+    }
 }

+ 5 - 1
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/constant/build/RepoType.java

@@ -5,5 +5,9 @@ package cn.reghao.autodop.dmaster.app.constant.build;
  * @date 2021-02-05 22:50:41
  */
 public enum RepoType {
-    git, svn
+    git, svn;
+
+    public String getName() {
+        return this.name();
+    }
 }

+ 2 - 1
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/controller/BuildDeployController.java

@@ -66,10 +66,11 @@ public class BuildDeployController {
         return WebBody.success();
     }
 
+    @Deprecated
     @ApiOperation(value = "刷新应用构建, 部署和运行列表")
     @PostMapping(value = "/refresh", produces = MediaType.APPLICATION_JSON_VALUE)
     public String refreshAppBuilding() {
-        buildService.refreshing();
+        buildService.refresh();
         return WebBody.success();
     }
 

+ 26 - 5
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/controller/config/AppConfigController.java

@@ -1,6 +1,7 @@
 package cn.reghao.autodop.dmaster.app.controller.config;
 
 import cn.reghao.autodop.common.result.Result;
+import cn.reghao.autodop.common.result.ResultStatus;
 import cn.reghao.autodop.dmaster.app.vo.NewApp;
 import cn.reghao.autodop.dmaster.app.entity.config.AppOrchestration;
 import cn.reghao.autodop.dmaster.app.entity.config.ProjOrchestration;
@@ -29,8 +30,7 @@ public class AppConfigController {
     private AppCrudService appCrudService;
     private ProjCrudService projCrudService;
 
-    public AppConfigController(AppCrudService appCrudService,
-                               ProjCrudService projCrudService) {
+    public AppConfigController(AppCrudService appCrudService, ProjCrudService projCrudService) {
         this.appCrudService = appCrudService;
         this.projCrudService = projCrudService;
     }
@@ -39,15 +39,29 @@ public class AppConfigController {
     @ApiOperation(value = "添加/修改应用编排")
     @PostMapping(value = "/app", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<String> addAppOrchestration(@Validated AppOrchestration app) {
-        appCrudService.insertOrUpdate(app);
-        return ResponseEntity.ok().body(WebBody.success());
+        Result result;
+        try {
+            result = appCrudService.insertOrUpdate(app);
+        } catch (Exception e) {
+            String msg = e.getMessage();
+            result = Result.result(ResultStatus.ERROR, msg);
+        }
+        return ResponseEntity.ok().body(WebBody.result(result));
     }
 
+    // TODO 使用 @PathVariable 注解时会自动填充实体
     @ApiOperation(value = "复制应用编排")
     @PostMapping(value = "/app/copy/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<String> copyAppOrchestration(@PathVariable("id") AppOrchestration app,
                                                        @Validated NewApp newApp) {
-        return ResponseEntity.ok().body(WebBody.success());
+        Result result;
+        try {
+            result = appCrudService.copy(app, newApp);
+        } catch (Exception e) {
+            String msg = e.getMessage();
+            result = Result.result(ResultStatus.ERROR, msg);
+        }
+        return ResponseEntity.ok().body(WebBody.result(result));
     }
 
     @ApiOperation(value = "删除应用编排")
@@ -91,4 +105,11 @@ public class AppConfigController {
         Result result = projCrudService.delete(proj);
         return ResponseEntity.ok().body(WebBody.result(result));
     }
+
+    @Deprecated
+    @ApiOperation(value = "测试接口")
+    @PostMapping(value = "/test", produces = MediaType.APPLICATION_JSON_VALUE)
+    public ResponseEntity<String> test(@Validated NotifyTest notifyTest) {
+        return ResponseEntity.ok().body(WebBody.success());
+    }
 }

+ 26 - 0
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/controller/config/NotifyTest.java

@@ -0,0 +1,26 @@
+package cn.reghao.autodop.dmaster.app.controller.config;
+
+import cn.reghao.autodop.dmaster.app.constant.NotifyType;
+import cn.reghao.autodop.dmaster.app.entity.config.NotifyReceiver;
+import cn.reghao.autodop.dmaster.app.validator.ValidEnum;
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2021-06-21 18:08:51
+ */
+@Deprecated
+@Data
+public class NotifyTest {
+    //@ValidEnum(value = NotifyType.class, message = "请选择正确的通知类型")
+    //private String notifyType;
+    //private NotifyReceiver notifyReceiver;
+    @Valid
+    private List<NotifyReceiver> notifyReceivers;
+    private String value;
+}

+ 8 - 51
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/controller/view/AppConfigPageController.java

@@ -97,18 +97,6 @@ public class AppConfigPageController {
     }
 
     private void setAppModel(Model model) {
-        List<KeyValue> envs = new ArrayList<>();
-        envs.add(new KeyValue("请选择环境", ""));
-        for (EnvType envType : EnvType.values()) {
-            envs.add(new KeyValue(envType.name(), envType.name()));
-        }
-
-        List<KeyValue> appTypes = new ArrayList<>();
-        appTypes.add(new KeyValue("请选择应用类型", ""));
-        for (AppType appType : AppType.values()) {
-            appTypes.add(new KeyValue(appType.name(), appType.name()));
-        }
-
         List<KeyValue> notifyTypes = new ArrayList<>();
         notifyTypes.add(new KeyValue("请选择通知类型", ""));
         for (NotifyType notifyType : NotifyType.values()) {
@@ -144,69 +132,38 @@ public class AppConfigPageController {
             packTypes.add(new KeyValue(packType.name(), packType.name()));
         }
 
-        List<KeyValue> logTypes = new ArrayList<>();
-        logTypes.add(new KeyValue("请选择日志类型", ""));
-        for (LogType logType : LogType.values()) {
-            logTypes.add(new KeyValue(logType.name(), logType.name()));
-        }
-
-        List<KeyValue> logLevels = new ArrayList<>();
-        logLevels.add(new KeyValue("请选择日志级别", ""));
-        for (LogLevel logLevel : LogLevel.values()) {
-            logLevels.add(new KeyValue(logLevel.name(), logLevel.name()));
-        }
-
-        model.addAttribute("environments", envs);
-        model.addAttribute("appTypes", appTypes);
         model.addAttribute("notifyTypes", notifyTypes);
         model.addAttribute("projs", projs);
         model.addAttribute("machines", machines);
         model.addAttribute("packTypes", packTypes);
-        model.addAttribute("logTypes", logTypes);
-        model.addAttribute("logLevels", logLevels);
-        setBuildConfig(model);
+
+        setCommon(model);
     }
 
-    private void setProjModel(Model model) {
+    private void setCommon(Model model) {
         List<KeyValue> envs = new ArrayList<>();
-        envs.add(new KeyValue("请选择环境", ""));
         for (EnvType envType : EnvType.values()) {
             envs.add(new KeyValue(envType.name(), envType.name()));
         }
 
         List<KeyValue> appTypes = new ArrayList<>();
-        appTypes.add(new KeyValue("请选择应用类型", ""));
         for (AppType appType : AppType.values()) {
             appTypes.add(new KeyValue(appType.name(), appType.name()));
         }
-
         model.addAttribute("environments", envs);
         model.addAttribute("appTypes", appTypes);
-        setBuildConfig(model);
-    }
 
-    private void setBuildConfig(Model model) {
-        List<KeyValue> repoAuthList = repoAuthQuery.findAllByIsDeleteFalse().stream()
+        List<KeyValue> repoAuths = repoAuthQuery.findAllByIsDeleteFalse().stream()
                 .map(repoAuthConfig -> new KeyValue(String.valueOf(repoAuthConfig.getName()), repoAuthConfig.getName()))
                 .collect(Collectors.toList());
-        List<KeyValue> repoAuths = new ArrayList<>();
-        repoAuths.add(new KeyValue("", "请选择仓库认证"));
-        repoAuths.addAll(repoAuthList);
 
-        List<KeyValue> compilerList = compilerQuery.findAllByIsDeleteFalse().stream()
+        List<KeyValue> compilers = compilerQuery.findAllByIsDeleteFalse().stream()
                 .map(compilerConfig -> new KeyValue(String.valueOf(compilerConfig.getName()), compilerConfig.getName()))
                 .collect(Collectors.toList());
-        List<KeyValue> compilers = new ArrayList<>();
-        compilers.add(new KeyValue("", "请选择编译器"));
-        compilers.addAll(compilerList);
 
-        List<KeyValue> packerList = packerQuery.findAllByIsDeleteFalse().stream()
+        List<KeyValue> packers = packerQuery.findAllByIsDeleteFalse().stream()
                 .map(packerConfig -> new KeyValue(String.valueOf(packerConfig.getName()), packerConfig.getName()))
                 .collect(Collectors.toList());
-        List<KeyValue> packers = new ArrayList<>();
-        packers.add(new KeyValue("", "请选择打包工具"));
-        packers.addAll(packerList);
-
         model.addAttribute("repoAuths", repoAuths);
         model.addAttribute("compilers", compilers);
         model.addAttribute("packers", packers);
@@ -259,13 +216,13 @@ public class AppConfigPageController {
 
     @GetMapping("/proj/add")
     public String addProjConfigPage(Model model) {
-        setProjModel(model);
+        setCommon(model);
         return "/app/config/proj/add";
     }
 
     @GetMapping("/proj/edit/{id}")
     public String editProjConfigPage(@PathVariable("id") ProjOrchestration proj, Model model) {
-        setProjModel(model);
+        setCommon(model);
         model.addAttribute("proj", proj);
         return "/app/config/proj/add";
     }

+ 97 - 39
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/db/crud/config/AppCrudService.java

@@ -1,8 +1,14 @@
 package cn.reghao.autodop.dmaster.app.db.crud.config;
 
+import cn.reghao.autodop.common.result.Result;
+import cn.reghao.autodop.common.result.ResultStatus;
+import cn.reghao.autodop.dmaster.app.constant.AppType;
+import cn.reghao.autodop.dmaster.app.constant.EnvType;
+import cn.reghao.autodop.dmaster.app.constant.NotifyType;
+import cn.reghao.autodop.dmaster.app.entity.config.NotifyReceiver;
 import cn.reghao.autodop.dmaster.app.entity.config.deploy.DeployConfig;
 import cn.reghao.autodop.dmaster.app.service.AppBuildService;
-import cn.reghao.autodop.dmaster.common.orm.CrudOps;
+import cn.reghao.autodop.dmaster.app.vo.NewApp;
 import cn.reghao.autodop.dmaster.app.entity.config.AppOrchestration;
 import cn.reghao.autodop.dmaster.app.repository.config.AppOrchestrationRepository;
 import cn.reghao.autodop.dmaster.machine.entity.MachineInfo;
@@ -11,9 +17,13 @@ import org.springframework.cache.annotation.CacheConfig;
 import org.springframework.cache.annotation.CacheEvict;
 import org.springframework.cache.annotation.CachePut;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Arrays;
 import java.util.List;
-import java.util.Optional;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * @author reghao
@@ -21,7 +31,7 @@ import java.util.Optional;
  */
 @CacheConfig(cacheNames = {"caffeineCacheManager"})
 @Service
-public class AppCrudService implements CrudOps<AppOrchestration> {
+public class AppCrudService {
     private AppOrchestrationRepository appRepository;
     private MachineInfoRepository machineRepository;
     private SharedEntityChecker sharedEntityChecker;
@@ -38,41 +48,76 @@ public class AppCrudService implements CrudOps<AppOrchestration> {
     }
 
     @CachePut(cacheNames = {"app"}, key = "#app.appId")
-    @Override
-    public AppOrchestration insertOrUpdate(AppOrchestration app) {
-        try {
-            checkSharedEntity(app);
-            List<DeployConfig> deployConfigs = app.getDeployConfigs();
-            deployConfigs.forEach(deployConfig -> {
-                String machineId = deployConfig.getMachineId();
-                MachineInfo machineInfo = machineRepository.findByMachineId(machineId);
-                if (machineInfo != null && !machineInfo.getNetworkInfos().isEmpty()) {
-                    deployConfig.setMachineIpv4(machineInfo.getNetworkInfos().get(0).getIpv4());
-                }
-            });
-            app = appRepository.save(app);
-            appBuildService.refreshAppBuilding(app);
-            appBuildService.refreshAppDeploying(app);
-            appBuildService.refreshAppRunning(app);
-            return app;
-        } catch (Exception e) {
-            e.printStackTrace();
+    @Transactional(rollbackFor = Exception.class)
+    public Result insertOrUpdate(AppOrchestration app) throws Exception {
+        // TODO 处理所有与 app 关联的数据
+        // TODO 不能有应用处于构建中
+        Integer id = app.getId();
+        Result result = checkEnum(app);
+        if (result.getCode() != ResultStatus.SUCCESS.getCode()) {
+            return result;
         }
+        checkNotifyReceivers(app);
+        checkDeployConfigs(app);
+        checkSharedEntity(app);
+        appRepository.save(app);
+        appBuildService.refreshApp(app);
 
-        return null;
+        String msg = id != null ? "更新 " + app.getAppId() + " 成功"
+                : "添加 " + app.getAppId() + " 成功";
+        return Result.result(ResultStatus.SUCCESS, msg);
     }
 
-    public void copy(String from, String to) throws Exception {
-        AppOrchestration fromApp = appRepository.findByIsDeleteFalseAndAppId(from);
-        if (fromApp == null) {
-            throw new Exception("ID 为 " + from + " 的应用配置不存在...");
+    private Result checkEnum(AppOrchestration app) {
+        Set<String> envs = Arrays.stream(EnvType.values())
+                .map(Enum::name)
+                .collect(Collectors.toSet());
+        String env = app.getEnv();
+        if (!envs.contains(env)) {
+            String msg = env + " 环境不存在";
+            return Result.result(ResultStatus.FAIL, msg);
         }
 
-        AppOrchestration toApp = appRepository.findByIsDeleteFalseAndAppId(to);
-        if (toApp == null) {
-            toApp = (AppOrchestration) fromApp.clone();
-            appRepository.save(toApp);
+        String appType = app.getAppType();
+        Set<String> appTypes = Arrays.stream(AppType.values())
+                .map(Enum::name)
+                .collect(Collectors.toSet());
+        if (!appTypes.contains(appType)) {
+            String msg = appType + " 类型不存在";
+            return Result.result(ResultStatus.FAIL, msg);
         }
+
+        return Result.result(ResultStatus.SUCCESS);
+    }
+
+    private void checkNotifyReceivers(AppOrchestration app) {
+        Set<String> notifyTypes = Arrays.stream(NotifyType.values())
+                .map(Enum::name)
+                .collect(Collectors.toSet());
+        // TODO notifyReceivers 为 null 时会发生异常,好像不能正常返回,后面需要测试一下
+        List<NotifyReceiver> notifyReceivers = app.getNotifyReceivers().stream()
+                .filter(notifyReceiver -> notifyTypes.contains(notifyReceiver.getNotifyType()))
+                .collect(Collectors.toList());
+        app.setNotifyReceivers(notifyReceivers);
+    }
+
+    private void checkDeployConfigs(AppOrchestration app) {
+        String packType = app.getPackerConfig().getType();
+        List<DeployConfig> deployConfigs = app.getDeployConfigs().stream()
+                .map(deployConfig -> {
+                    String machineId = deployConfig.getMachineId();
+                    MachineInfo machineInfo = machineRepository.findByMachineId(machineId);
+                    if (machineInfo != null && !machineInfo.getNetworkInfos().isEmpty()) {
+                        deployConfig.setMachineIpv4(machineInfo.getNetworkInfos().get(0).getIpv4());
+                        deployConfig.setPackType(packType);
+                        return deployConfig;
+                    } else {
+                        return null;
+                    }
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        app.setDeployConfigs(deployConfigs);
     }
 
     private void checkSharedEntity(AppOrchestration app) throws Exception {
@@ -83,15 +128,28 @@ public class AppCrudService implements CrudOps<AppOrchestration> {
         }
     }
 
-    @CacheEvict(cacheNames = {"app"})
-    @Override
-    public void delete(AppOrchestration app) {
-        appRepository.delete(app);
+    public Result copy(AppOrchestration from, NewApp to) throws Exception {
+        if (from == null) {
+            String msg = "应用配置不存在...";
+            return Result.result(ResultStatus.FAIL, msg);
+        }
+
+        AppOrchestration toApp = appRepository.findByIsDeleteFalseAndAppId(to.getNewId());
+        if (toApp != null) {
+            String msg = to.getNewId() + " 已存在";
+            return Result.result(ResultStatus.FAIL, msg);
+        }
+
+        toApp = to.app((AppOrchestration) from.clone());
+        return insertOrUpdate(toApp);
     }
 
-    @Override
-    public AppOrchestration select(Integer id) {
-        Optional<AppOrchestration> optional = appRepository.findById(id);
-        return optional.orElse(null);
+    @CacheEvict(cacheNames = {"app"}, key = "#app.appId")
+    @Transactional(rollbackFor = Exception.class)
+    public Result delete(AppOrchestration app) {
+        // TODO 处理所有与 app 关联的数据
+        appRepository.delete(app);
+        appBuildService.delete(app.getAppId());
+        return Result.result(ResultStatus.SUCCESS);
     }
 }

+ 7 - 3
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/db/crud/config/ProjCrudService.java

@@ -33,6 +33,7 @@ public class ProjCrudService {
 
     public Result insertOrUpdate(ProjOrchestration proj) {
         try {
+            Integer id = proj.getId();
             sharedEntityChecker.checkAndSetBuildConfig(proj);
             ProjOrchestration projEntity = projRepository.findByIsDeleteFalseAndProjId(proj.getProjId());
             if (projEntity != null) {
@@ -41,7 +42,10 @@ public class ProjCrudService {
                 proj.setUpdateTime(LocalDateTime.now());
             }
             projRepository.save(proj);
-            return Result.result(ResultStatus.SUCCESS);
+
+            String msg = id != null ? "更新 " + proj.getProjId() + " 成功"
+                    : "添加 " + proj.getProjId() + " 成功";
+            return Result.result(ResultStatus.SUCCESS, msg);
         } catch (Exception e) {
             String msg = e.getMessage();
             return Result.result(ResultStatus.FAIL, msg);
@@ -50,7 +54,7 @@ public class ProjCrudService {
 
     public Result copy(ProjOrchestration from, NewApp to) {
         if (from == null) {
-            String msg = "ID 为 " + from + " 的项目配置不存在...";
+            String msg = "项目配置不存在...";
             return Result.result(ResultStatus.FAIL, msg);
         }
 
@@ -62,7 +66,7 @@ public class ProjCrudService {
 
         try {
             toProj = to.proj((ProjOrchestration) from.clone());
-            projRepository.save(toProj);
+            insertOrUpdate(toProj);
             return Result.result(ResultStatus.SUCCESS);
         } catch (CloneNotSupportedException e) {
             String msg = e.getMessage();

+ 12 - 12
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/db/crud/config/SharedEntityChecker.java

@@ -54,41 +54,41 @@ public class SharedEntityChecker {
         BuildConfig buildConfig =
                 new BuildConfig(app.getRepoAuthConfig(), app.getCompilerConfig(), app.getPackerConfig());
         checkAndSetBuildConfig(buildConfig);
-        app.setRepoAuthConfig(buildConfig.getRepoAuthConfig());
-        app.setCompilerConfig(buildConfig.getCompilerConfig());
-        app.setPackerConfig(buildConfig.getPackerConfig());
+        app.setRepoAuthConfig(buildConfig.getRepoAuth());
+        app.setCompilerConfig(buildConfig.getCompiler());
+        app.setPackerConfig(buildConfig.getPacker());
     }
 
     public void checkAndSetBuildConfig(ProjOrchestration proj) throws Exception {
         BuildConfig buildConfig =
                 new BuildConfig(proj.getRepoAuthConfig(), proj.getCompilerConfig(), proj.getPackerConfig());
         checkAndSetBuildConfig(buildConfig);
-        proj.setRepoAuthConfig(buildConfig.getRepoAuthConfig());
-        proj.setCompilerConfig(buildConfig.getCompilerConfig());
-        proj.setPackerConfig(buildConfig.getPackerConfig());
+        proj.setRepoAuthConfig(buildConfig.getRepoAuth());
+        proj.setCompilerConfig(buildConfig.getCompiler());
+        proj.setPackerConfig(buildConfig.getPacker());
     }
 
     public void checkAndSetBuildConfig(BuildConfig buildConfig) throws Exception {
-        RepoAuthConfig repoAuth = buildConfig.getRepoAuthConfig();
+        RepoAuthConfig repoAuth = buildConfig.getRepoAuth();
         RepoAuthConfig repoAuthConfig = repoAuthQuery.findByName(repoAuth.getName());
         if (repoAuthConfig != null) {
-            buildConfig.setRepoAuthConfig(repoAuthConfig);
+            buildConfig.setRepoAuth(repoAuthConfig);
         } else {
             throw new Exception("仓库配置 " + repoAuth.getName() + " 不存在...");
         }
 
-        CompilerConfig compiler = buildConfig.getCompilerConfig();
+        CompilerConfig compiler = buildConfig.getCompiler();
         CompilerConfig compilerConfig = compilerQuery.findByName(compiler.getName());
         if (compilerConfig != null) {
-            buildConfig.setCompilerConfig(compilerConfig);
+            buildConfig.setCompiler(compilerConfig);
         } else {
             throw new Exception("编译器配置 " + compiler.getName() + " 不存在...");
         }
 
-        PackerConfig packer = buildConfig.getPackerConfig();
+        PackerConfig packer = buildConfig.getPacker();
         PackerConfig packerConfig = packerQuery.findByName(packer.getName());
         if (packerConfig != null) {
-            buildConfig.setPackerConfig(packerConfig);
+            buildConfig.setPacker(packerConfig);
         } else {
             throw new Exception("打包配置 " + packer.getName() + " 不存在...");
         }

+ 2 - 0
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/AppProperty.java

@@ -10,6 +10,8 @@ import lombok.NoArgsConstructor;
 import javax.persistence.MappedSuperclass;
 
 /**
+ * 应用基本属性
+ *
  * @author reghao
  * @date 2021-06-10 14:40:13
  */

+ 5 - 0
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/AppOrchestration.java

@@ -13,6 +13,7 @@ import org.hibernate.annotations.LazyCollectionOption;
 import org.hibernate.validator.constraints.Length;
 
 import javax.persistence.*;
+import javax.validation.Valid;
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
 import java.util.List;
@@ -42,6 +43,7 @@ public class AppOrchestration extends BaseEntity<Integer> implements Cloneable {
     private String repoBranch;
     @ElementCollection(targetClass = NotifyReceiver.class)
     @LazyCollection(LazyCollectionOption.FALSE)
+    //@Valid
     private List<NotifyReceiver> notifyReceivers;
     private Boolean enable;
     // TODO 添加定时构建部署选项
@@ -66,12 +68,15 @@ public class AppOrchestration extends BaseEntity<Integer> implements Cloneable {
     // buildConfig
     @ManyToOne(cascade = CascadeType.REFRESH)
     @JoinColumn(name = "repo_auth_config_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
+    //@Valid
     private RepoAuthConfig repoAuthConfig;
     @ManyToOne(cascade = CascadeType.REFRESH)
     @JoinColumn(name = "compiler_config_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
+    //@Valid
     private CompilerConfig compilerConfig;
     @ManyToOne(cascade = CascadeType.REFRESH)
     @JoinColumn(name = "packer_config_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
+    //@Valid
     private PackerConfig packerConfig;
 
     /* 部署配置 */

+ 3 - 0
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/NotifyReceiver.java

@@ -1,5 +1,7 @@
 package cn.reghao.autodop.dmaster.app.entity.config;
 
+import cn.reghao.autodop.dmaster.app.constant.NotifyType;
+import cn.reghao.autodop.dmaster.app.validator.ValidEnum;
 import lombok.Data;
 
 import javax.persistence.*;
@@ -12,6 +14,7 @@ import javax.persistence.*;
 @Embeddable
 public class NotifyReceiver {
     @Column(nullable = false)
+    @ValidEnum(value = NotifyType.class, message = "请选择正确的通知类型")
     private String notifyType;
     private String receiver;
 }

+ 4 - 0
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/ProjOrchestration.java

@@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 
 import javax.persistence.*;
+import javax.validation.Valid;
 import javax.validation.constraints.NotBlank;
 
 /**
@@ -40,12 +41,15 @@ public class ProjOrchestration extends BaseEntity<Integer> implements Cloneable
     // 同一个项目内所有应用的构建策略是相同的
     @ManyToOne(cascade = CascadeType.REFRESH)
     @JoinColumn(name = "repo_auth_config_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
+    //@Valid
     private RepoAuthConfig repoAuthConfig;
     @ManyToOne(cascade = CascadeType.REFRESH)
     @JoinColumn(name = "compiler_config_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
+    //@Valid
     private CompilerConfig compilerConfig;
     @ManyToOne(cascade = CascadeType.REFRESH)
     @JoinColumn(name = "packer_config_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
+    //@Valid
     private PackerConfig packerConfig;
 
     @Override

+ 3 - 1
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/build/CompilerConfig.java

@@ -1,5 +1,7 @@
 package cn.reghao.autodop.dmaster.app.entity.config.build;
 
+import cn.reghao.autodop.dmaster.app.constant.build.CompileType;
+import cn.reghao.autodop.dmaster.app.validator.ValidEnum;
 import cn.reghao.autodop.dmaster.common.orm.BaseEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -24,7 +26,7 @@ public class CompilerConfig extends BaseEntity<Integer> {
     private String machineId;
     private String machineIpv4;
     @Column(nullable = false)
-    @NotBlank(message = "编译器类型不能为空白字符串")
+    @ValidEnum(value = CompileType.class, message = "请选择正确的编译类型")
     private String type;
     @Column(nullable = false, unique = true)
     @NotBlank(message = "编译器名字不能为空白字符串")

+ 3 - 1
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/build/PackerConfig.java

@@ -1,5 +1,7 @@
 package cn.reghao.autodop.dmaster.app.entity.config.build;
 
+import cn.reghao.autodop.common.dagent.app.api.data.deploy.PackType;
+import cn.reghao.autodop.dmaster.app.validator.ValidEnum;
 import cn.reghao.autodop.dmaster.common.orm.BaseEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -24,7 +26,7 @@ public class PackerConfig extends BaseEntity<Integer> {
     private String machineId;
     private String machineIpv4;
     @Column(nullable = false)
-    @NotBlank(message = "打包类型不能为空白字符串")
+    @ValidEnum(value = PackType.class, message = "请选择正确的打包类型")
     private String type;
     @Column(nullable = false, unique = true)
     @NotBlank(message = "打包工具名字不能为空白字符串")

+ 5 - 3
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/build/RepoAuthConfig.java

@@ -1,9 +1,11 @@
 package cn.reghao.autodop.dmaster.app.entity.config.build;
 
+import cn.reghao.autodop.dmaster.app.constant.build.RepoAuthType;
+import cn.reghao.autodop.dmaster.app.constant.build.RepoType;
+import cn.reghao.autodop.dmaster.app.validator.ValidEnum;
 import cn.reghao.autodop.dmaster.common.orm.BaseEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import lombok.NoArgsConstructor;
 
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -21,13 +23,13 @@ import javax.validation.constraints.NotBlank;
 @Entity
 public class RepoAuthConfig extends BaseEntity<Integer> {
     @Column(nullable = false)
-    @NotBlank(message = "仓库类型不能为空白字符串")
+    @ValidEnum(value = RepoType.class, message = "请选择正确的仓库类型")
     private String type;
     @Column(nullable = false, unique = true)
     @NotBlank(message = "仓库认证名字不能为空白字符串")
     private String name;
     @Column(nullable = false)
-    @NotBlank(message = "仓库认证类型不能为空白字符串")
+    @ValidEnum(value = RepoAuthType.class, message = "请选择的仓库认证类型")
     private String authType;
     // TODO 根据 authType 选择 username, password 或 rsaPrikey
     private String username;

+ 2 - 6
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/config/deploy/RunningConfig.java

@@ -1,12 +1,8 @@
 package cn.reghao.autodop.dmaster.app.entity.config.deploy;
 
 import lombok.Data;
-import org.hibernate.annotations.LazyCollection;
-import org.hibernate.annotations.LazyCollectionOption;
 
-import javax.persistence.ElementCollection;
 import javax.persistence.Embeddable;
-import java.util.List;
 
 /**
  * 应用运行时配置
@@ -19,7 +15,7 @@ import java.util.List;
 public class RunningConfig {
     private Integer httpPort;
     private String healthCheck;
-    @ElementCollection(targetClass = LogConfig.class)
+    /*@ElementCollection(targetClass = LogConfig.class)
     @LazyCollection(LazyCollectionOption.FALSE)
-    private List<LogConfig> logConfigs;
+    private List<LogConfig> logConfigs;*/
 }

+ 4 - 6
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/entity/log/BuildLog.java

@@ -3,6 +3,7 @@ package cn.reghao.autodop.dmaster.app.entity.log;
 import cn.reghao.autodop.common.result.Result;
 import cn.reghao.autodop.dmaster.app.entity.config.AppOrchestration;
 import cn.reghao.autodop.dmaster.app.service.bd.tools.repo.CommitInfo;
+import cn.reghao.autodop.dmaster.app.vo.BuildConfig;
 import cn.reghao.autodop.dmaster.auth.UserContext;
 import cn.reghao.autodop.dmaster.common.orm.BaseDocument;
 import lombok.Data;
@@ -23,9 +24,7 @@ public class BuildLog extends BaseDocument {
     private String appType;
     private String env;
     private String branch;
-    private String repo;
-    private String compiler;
-    private String packer;
+    private BuildConfig buildConfig;
     private String dockerfile;
     private String packagePath;
 
@@ -40,9 +39,8 @@ public class BuildLog extends BaseDocument {
         buildLog.setAppType(app.getAppType());
         buildLog.setEnv(app.getEnv());
         buildLog.setBranch(app.getRepoBranch());
-        buildLog.setRepo(app.getRepoAuthConfig().getName());
-        buildLog.setCompiler(app.getCompilerConfig().getName());
-        buildLog.setPacker(app.getPackerConfig().getName());
+        buildLog.setBuildConfig(
+                new BuildConfig(app.getRepoAuthConfig(), app.getCompilerConfig(), app.getPackerConfig()));
         buildLog.setDockerfile(app.getDockerfile());
 
         buildLog.setBuildTime(new BuildTime());

+ 5 - 4
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/interceptor/AppIntegrateReinitInterceptor.java

@@ -23,8 +23,8 @@ import java.util.concurrent.ConcurrentSkipListSet;
 @Aspect
 public class AppIntegrateReinitInterceptor {
     private Set<String> changedApp = new ConcurrentSkipListSet<>();
-    // 拦截 cn.reghao.autodop.dmaster.app.service.crud.orchestarte 包中的所有方法
-    private final static String POINT = "execution (* cn.reghao.autodop.dmaster.app.service.crud.orchestarte..*.*(..))";
+    // 拦截 cn.reghao.autodop.dmaster.app.db 包中的所有方法
+    private final static String POINT = "execution (* cn.reghao.autodop.dmaster.app.db..*.*(..))";
 
     @Pointcut(POINT)
     public void reinit(){
@@ -36,11 +36,12 @@ public class AppIntegrateReinitInterceptor {
         MethodSignature signature = (MethodSignature) joinPoint.getSignature();
         String methodName = signature.getName();
         Object[] args = joinPoint.getArgs();
-        if ("update".equals(methodName) && args[0] instanceof AppOrchestration) {
+        log.info("拦截 {} 方法...", methodName);
+        /*if ("update".equals(methodName) && args[0] instanceof AppOrchestration) {
             AppOrchestration app = (AppOrchestration) args[0];
             String appId = app.getAppId();
             changedApp.add(appId);
-        }
+        }*/
 
         return joinPoint.proceed(joinPoint.getArgs());
     }

+ 0 - 2
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/repository/AppBuildingRepository.java

@@ -13,6 +13,4 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 public interface AppBuildingRepository
         extends JpaRepository<AppBuilding, Integer>, JpaSpecificationExecutor<AppBuilding> {
     AppBuilding findByAppId(String appId);
-    Page<AppBuilding> findAllByEnv(String env, Pageable pageable);
-    Page<AppBuilding> findAllByEnvAndAppType(String env, String appType, Pageable pageable);
 }

+ 3 - 0
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/repository/AppRunningRepository.java

@@ -6,12 +6,15 @@ import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 
+import java.util.List;
+
 /**
  * @author reghao
  * @date 2021-05-24 15:20:24
  */
 public interface AppRunningRepository
         extends JpaRepository<AppRunning, Integer>, JpaSpecificationExecutor<AppRunning> {
+    List<AppRunning> findByAppId(String appId);
     AppRunning findByAppIdAndMachineId(String appId, String machineId);
     Page<AppRunning> findAllByEnv(String env, Pageable pageable);
 }

+ 76 - 73
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/service/AppBuildService.java

@@ -5,13 +5,11 @@ import cn.reghao.autodop.dmaster.app.entity.AppBuilding;
 import cn.reghao.autodop.dmaster.app.entity.AppDeploying;
 import cn.reghao.autodop.dmaster.app.entity.AppRunning;
 import cn.reghao.autodop.dmaster.app.entity.config.AppOrchestration;
-import cn.reghao.autodop.dmaster.app.entity.config.deploy.DeployConfig;
 import cn.reghao.autodop.dmaster.app.repository.AppBuildingRepository;
 import cn.reghao.autodop.dmaster.app.repository.AppDeployingRepository;
 import cn.reghao.autodop.dmaster.app.repository.AppRunningRepository;
 import org.springframework.stereotype.Service;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -36,27 +34,31 @@ public class AppBuildService {
         this.runningRepository = runningRepository;
     }
 
-    public void refreshing() {
+    public void refresh() {
         refreshAppBuilding();
         refreshAppDeploying();
         refreshAppRunning();
     }
 
+    public void refreshApp(AppOrchestration app) {
+        refreshAppBuilding(app);
+        refreshAppDeploying(app);
+        refreshAppRunning(app);
+    }
+
     public void refreshAppBuilding() {
         List<AppOrchestration> apps = appQuery.findAllByEnableIsTrue();
-        List<AppBuilding> list = new ArrayList<>();
-        for (AppOrchestration app : apps) {
-            AppBuilding appBuilding = buildingRepository.findByAppId(app.getAppId());
-            if (appBuilding == null) {
-                list.add(new AppBuilding(app));
-            } else {
-                list.add(appBuilding.refresh(app));
-            }
-        }
-        buildingRepository.saveAll(list);
+        apps.forEach(this::refreshAppBuilding);
     }
 
-    public void refreshAppBuilding(AppOrchestration app) {
+    /**
+     * 刷新应用构建列表
+     *
+     * @param
+     * @return
+     * @date 2021-06-18 下午5:24
+     */
+    private void refreshAppBuilding(AppOrchestration app) {
         AppBuilding appBuilding = buildingRepository.findByAppId(app.getAppId());
         if (appBuilding == null) {
             appBuilding = new AppBuilding(app);
@@ -66,83 +68,84 @@ public class AppBuildService {
         buildingRepository.save(appBuilding);
     }
 
+    /**
+     * 刷新应用部署列表
+     *
+     * @param
+     * @return
+     * @date 2021-06-18 下午5:25
+     */
     public void refreshAppDeploying() {
         List<AppOrchestration> apps = appQuery.findAllByEnableIsTrue().stream()
                 .filter(app -> !app.getDeployConfigs().isEmpty())
+                // TODO 使用 peek
+                //.peek()
                 .collect(Collectors.toList());
-
-        List<AppDeploying> list = new ArrayList<>();
-        for (AppOrchestration app : apps) {
-            String appId = app.getAppId();
-            List<DeployConfig> deployConfigs = app.getDeployConfigs();
-            for (DeployConfig deployConfig : deployConfigs) {
-                String machineId = deployConfig.getMachineId();
-                AppDeploying appDeploying = deployingRepository.findByAppIdAndMachineId(appId, machineId);
-                if (appDeploying == null) {
-                    appDeploying = new AppDeploying(app, deployConfig);
-                } else {
-                    appDeploying.refresh(app.getAppName(), deployConfig.getMachineIpv4());
-                }
-                list.add(appDeploying);
-            }
-        }
-        deployingRepository.saveAll(list);
+        apps.forEach(this::refreshAppDeploying);
     }
 
-    public void refreshAppDeploying(AppOrchestration app) {
+    private void refreshAppDeploying(AppOrchestration app) {
         String appId = app.getAppId();
-        List<DeployConfig> deployConfigs = app.getDeployConfigs();
-        List<AppDeploying> list = new ArrayList<>();
-        for (DeployConfig deployConfig : deployConfigs) {
-            String machineId = deployConfig.getMachineId();
-            AppDeploying appDeploying = deployingRepository.findByAppIdAndMachineId(appId, machineId);
-            if (appDeploying == null) {
-                appDeploying = new AppDeploying(app, deployConfig);
-            } else {
-                appDeploying.refresh(app.getAppName(), deployConfig.getMachineIpv4());
-            }
-            list.add(appDeploying);
-        }
+        List<AppDeploying> list = app.getDeployConfigs().stream()
+                .map(deployConfig -> {
+                    String machineId = deployConfig.getMachineId();
+                    AppDeploying appDeploying = deployingRepository.findByAppIdAndMachineId(appId, machineId);
+                    if (appDeploying == null) {
+                        appDeploying = new AppDeploying(app, deployConfig);
+                    } else {
+                        appDeploying.refresh(app.getAppName(), deployConfig.getMachineIpv4());
+                    }
+                    return appDeploying;
+                })
+                .collect(Collectors.toList());
         deployingRepository.saveAll(list);
     }
 
+    /**
+     * 刷新应用状态列表
+     *
+     * @param
+     * @return
+     * @date 2021-06-18 下午5:29
+     */
     public void refreshAppRunning() {
         List<AppOrchestration> apps = appQuery.findAllByEnableIsTrue().stream()
                 .filter(app -> !app.getDeployConfigs().isEmpty())
                 .collect(Collectors.toList());
+        apps.forEach(this::refreshAppRunning);
+    }
 
-        List<AppRunning> list = new ArrayList<>();
-        for (AppOrchestration app : apps) {
-            String appId = app.getAppId();
-            List<DeployConfig> deployConfigs = app.getDeployConfigs();
-            for (DeployConfig deployConfig : deployConfigs) {
-                String machineId = deployConfig.getMachineId();
-                AppRunning appRunning = runningRepository.findByAppIdAndMachineId(appId, machineId);
-                if (appRunning == null) {
-                    appRunning = new AppRunning(app, deployConfig);
-                } else {
-                    appRunning.refresh(app.getAppName(), deployConfig.getMachineIpv4());
-                }
-                list.add(appRunning);
-            }
-        }
+    private void refreshAppRunning(AppOrchestration app) {
+        String appId = app.getAppId();
+        List<AppRunning> list = app.getDeployConfigs().stream()
+                .map(deployConfig -> {
+                    String machineId = deployConfig.getMachineId();
+                    AppRunning appRunning = runningRepository.findByAppIdAndMachineId(appId, machineId);
+                    if (appRunning == null) {
+                        appRunning = new AppRunning(app, deployConfig);
+                    } else {
+                        appRunning.refresh(app.getAppName(), deployConfig.getMachineIpv4());
+                    }
+                    return appRunning;
+                })
+                .collect(Collectors.toList());
         runningRepository.saveAll(list);
     }
 
-    public void refreshAppRunning(AppOrchestration app) {
-        String appId = app.getAppId();
-        List<DeployConfig> deployConfigs = app.getDeployConfigs();
-        List<AppRunning> list = new ArrayList<>();
-        for (DeployConfig deployConfig : deployConfigs) {
-            String machineId = deployConfig.getMachineId();
-            AppRunning appRunning = runningRepository.findByAppIdAndMachineId(appId, machineId);
-            if (appRunning == null) {
-                appRunning = new AppRunning(app, deployConfig);
-            } else {
-                appRunning.refresh(app.getAppName(), deployConfig.getMachineIpv4());
-            }
-            list.add(appRunning);
+    public void delete(String appId) {
+        AppBuilding appBuilding = buildingRepository.findByAppId(appId);
+        if (appBuilding != null) {
+            buildingRepository.delete(appBuilding);
+        }
+
+        List<AppDeploying> appDeployings = deployingRepository.findByAppId(appId);
+        if (!appDeployings.isEmpty()) {
+            deployingRepository.deleteAll(appDeployings);
+        }
+
+        List<AppRunning> appRunnings = runningRepository.findByAppId(appId);
+        if (!appRunnings.isEmpty()) {
+            runningRepository.deleteAll(appRunnings);
         }
-        runningRepository.saveAll(list);
     }
 }

+ 64 - 0
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/validator/EnumValidator.java

@@ -0,0 +1,64 @@
+package cn.reghao.autodop.dmaster.app.validator;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 验证枚举值是否有效
+ *
+ * @author reghao
+ * @date 2021-06-21 17:36:03
+ */
+@Slf4j
+public class EnumValidator implements ConstraintValidator<ValidEnum, Object> {
+    private final List<Object> values = new ArrayList<>();
+
+    /**
+     * 第一次校验数据时初始化,只在初始化时调用一次
+     *
+     * @param
+     * @return
+     * @date 2021-06-21 下午7:01
+     */
+    @Override
+    public void initialize(ValidEnum constraintAnnotation) {
+        Class<?> enumClazz = constraintAnnotation.value();
+        Object[] enumConstants = enumClazz.getEnumConstants();
+        if (enumConstants == null) {
+            return;
+        }
+
+        Method method = BeanUtils.findMethod(enumClazz, constraintAnnotation.method());
+        if (method == null) {
+            log.error("枚举对象:[{}]中不存在方法:[{}],请检查.", enumClazz.getName(), constraintAnnotation.method());
+            return;
+        }
+
+        method.setAccessible(true);
+        try {
+            for (Object enumConstant : enumConstants) {
+                values.add(method.invoke(enumConstant));
+            }
+        } catch (Exception e) {
+            log.error("获取枚举类:[{}]的枚举对象的值失败.", enumClazz);
+        }
+    }
+
+    /**
+     * 每次校验数据时调用
+     *
+     * @param
+     * @return
+     * @date 2021-06-21 下午7:02
+     */
+    @Override
+    public boolean isValid(Object value, ConstraintValidatorContext context) {
+        return value == null || values.contains(value);
+    }
+}

+ 27 - 0
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/validator/ValidEnum.java

@@ -0,0 +1,27 @@
+package cn.reghao.autodop.dmaster.app.validator;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+/**
+ * 模仿 @NotNull
+ *
+ * @author reghao
+ * @date 2021-06-21 17:35:07
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+@Constraint(validatedBy = EnumValidator.class)
+public @interface ValidEnum {
+    Class<?> value();
+
+    String message() default "枚举类型的值不正确";
+
+    String method() default "getName";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}

+ 3 - 3
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/vo/BuildConfig.java

@@ -13,7 +13,7 @@ import lombok.Data;
 @AllArgsConstructor
 @Data
 public class BuildConfig {
-    private RepoAuthConfig repoAuthConfig;
-    private CompilerConfig compilerConfig;
-    private PackerConfig packerConfig;
+    private RepoAuthConfig repoAuth;
+    private CompilerConfig compiler;
+    private PackerConfig packer;
 }

+ 13 - 0
dmaster/src/main/java/cn/reghao/autodop/dmaster/app/vo/NewApp.java

@@ -5,6 +5,7 @@ import cn.reghao.autodop.dmaster.app.entity.config.ProjOrchestration;
 import lombok.Data;
 
 import javax.validation.constraints.NotBlank;
+import java.util.Collections;
 
 /**
  * @author reghao
@@ -27,6 +28,18 @@ public class NewApp {
     private String newDockerfile;
 
     public AppOrchestration app(AppOrchestration from) {
+        from.setAppId(newId);
+        from.setAppName(newName);
+        from.setDescription(newDescription);
+        from.setAppRepo(newRepo);
+        from.setRepoBranch(newRepoBranch);
+        from.setCodeDirname(newCodeDirname);
+        from.setCompileHome(newCompileHome);
+        from.setDockerfile(newDockerfile);
+        from.setNotifyReceivers(Collections.emptyList());
+        from.setDeployConfigs(Collections.emptyList());
+        from.setRunningConfig(null);
+
         from.setId(null);
         from.setCreateTime(null);
         from.setUpdateTime(null);

+ 3 - 1
dmaster/src/main/java/cn/reghao/autodop/dmaster/common/orm/CrudOps.java

@@ -15,7 +15,9 @@ public interface CrudOps<T> {
     T insertOrUpdate(T t);
     void delete(T t);
 
-    T select(Integer id);
+    default T select(Integer id) {
+        return null;
+    }
     default T selectByUniqueKey(String uniqueKey) {
         return null;
     }

+ 2 - 2
dmaster/src/main/resources/static/js/main.js

@@ -125,9 +125,9 @@ layui.use(['element', 'form', 'layer', 'upload'], function () {
                 } else {
                     window.location.reload();
                 }
-            }, 200);
+            }, 1000);
         } else {
-            layer.msg(result.msg, {offset: '15px', time: 2000, icon: 2});
+            layer.msg(result.msg, {offset: '15px', time: 3000, icon: 2});
         }
     };
 

+ 59 - 70
dmaster/src/main/resources/templates/app/config/app/add.html

@@ -9,18 +9,32 @@
         <div class="timo-detail-title">基本信息</div>
         <table class="layui-table timo-detail-table">
             <tbody>
-            <tr>
-                <th>项目(可选)</th>
-                <td>
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <select name="app.proj.projId">
-                                <option th:each="item : ${projs}" th:value="${item.key}" th:selected="${app?.proj?.projId} eq ${item.key}">[[${item.value}]]</option>
-                            </select>
+            <span th:if="${app} == null">
+                <th>项目</th>
+                    <td>
+                        <div class="layui-form-item">
+                            <div class="layui-input-inline">
+                                <select name="app.proj.projId">
+                                    <option th:each="item : ${projs}" th:value="${item.key}" >[[${item.value}]]</option>
+                                </select>
+                            </div>
                         </div>
-                    </div>
-                </td>
-            </tr>
+                    </td>
+            </span>
+            <span th:if="${app?.proj} != null">
+                <tr>
+                    <th>项目</th>
+                    <td>
+                        <div class="layui-form-item">
+                            <div class="layui-input-inline">
+                                <select name="app.proj.projId">
+                                    <option th:each="item : ${projs}" th:value="${item.key}" th:selected="${app?.proj?.projId} eq ${item.key}">[[${item.value}]]</option>
+                                </select>
+                            </div>
+                        </div>
+                    </td>
+                </tr>
+            </span>
             <tr>
                 <th>应用 ID</th>
                 <td>
@@ -157,6 +171,12 @@
                             </div>
                         </div>
                     </td>
+                    <td>
+                        <div>
+                            <a class="layui-btn layui-btn-xs addNotifyReceiver">添加</a>
+                            <a class="layui-btn layui-btn-danger layui-btn-xs delNotifyReceiver">删除</a>
+                        </div>
+                    </td>
                     </tr>
                 </span>
             </span>
@@ -180,6 +200,12 @@
                         </div>
                     </div>
                 </td>
+                <td>
+                        <div>
+                            <a class="layui-btn layui-btn-xs addNotifyReceiver">添加</a>
+                            <a class="layui-btn layui-btn-danger layui-btn-xs delNotifyReceiver">删除</a>
+                        </div>
+                    </td>
                 </tr>
             </span>
             </tbody>
@@ -213,16 +239,6 @@
                     </div>
                 </td>
             </tr>
-            <tr>
-                <th>Dockerfile</th>
-                <td>
-                    <div class="layui-form-item">
-                        <div class="layui-input-inline">
-                            <textarea class="layui-textarea" name="dockerfile" placeholder="打包类型是 docker 时才需要" th:text="${app?.dockerfile}"></textarea>
-                        </div>
-                    </div>
-                </td>
-            </tr>
             <!-- TODO 若选择了项目,则仓库、编译和打包配置不用选择 -->
             <span th:if="${app?.proj} == null">
                 <tr>
@@ -230,8 +246,8 @@
                     <td>
                         <div class="layui-form-item">
                             <div class="layui-input-inline">
-                                <select name="repoAuthConfig.id">
-                                    <option th:each="item : ${repoAuths}" th:value="${item.key}" th:selected="${app?.repoAuthConfig?.id} eq ${item.key}">[[${item.value}]]</option>
+                                <select name="repoAuthConfig.name">
+                                    <option th:each="item : ${repoAuths}" th:value="${item.key}" th:selected="${app?.repoAuthConfig?.name} eq ${item.key}">[[${item.value}]]</option>
                                 </select>
                             </div>
                         </div>
@@ -240,8 +256,8 @@
                     <td>
                         <div class="layui-form-item">
                             <div class="layui-input-inline">
-                                <select name="compilerConfig.id">
-                                    <option th:each="item : ${compilers}" th:value="${item.key}" th:selected="${app?.compilerConfig?.id} eq ${item.key}">[[${item.value}]]</option>
+                                <select name="compilerConfig.name">
+                                    <option th:each="item : ${compilers}" th:value="${item.key}" th:selected="${app?.compilerConfig?.name} eq ${item.key}">[[${item.value}]]</option>
                                 </select>
                             </div>
                         </div>
@@ -250,14 +266,24 @@
                     <td>
                         <div class="layui-form-item">
                             <div class="layui-input-inline">
-                                <select name="packerConfig.id">
-                                    <option th:each="item : ${packers}" th:value="${item.key}" th:selected="${app?.packerConfig?.id} eq ${item.key}">[[${item.value}]]</option>
+                                <select name="packerConfig.name">
+                                    <option th:each="item : ${packers}" th:value="${item.key}" th:selected="${app?.packerConfig?.name} eq ${item.key}">[[${item.value}]]</option>
                                 </select>
                             </div>
                         </div>
                     </td>
                 </tr>
             </span>
+            <tr>
+                <th>Dockerfile</th>
+                <td>
+                    <div class="layui-form-item">
+                        <div class="layui-input-inline">
+                            <textarea class="layui-textarea" name="dockerfile" placeholder="打包类型是 docker 时才需要" th:text="${app?.dockerfile}"></textarea>
+                        </div>
+                    </div>
+                </td>
+            </tr>
             </tbody>
         </table>
         <div class="timo-detail-title">部署配置(数组)</div>
@@ -276,16 +302,6 @@
                             </div>
                         </div>
                     </td>
-                    <th>打包类型</th>
-                    <td>
-                        <div class="layui-form-item">
-                            <div class="layui-input-inline">
-                                <select th:attr="name=@{'deployConfigs[' + ${stat.index} + '].packType'}">
-                                    <option th:each="item : ${packTypes}" th:value="${item.key}" th:selected="${deployConfig.packType} eq ${item.key}">[[${item.value}]]</option>
-                                </select>
-                            </div>
-                        </div>
-                    </td>
                     <th>启动脚本</th>
                     <td>
                         <div class="layui-form-item">
@@ -322,16 +338,6 @@
                             </div>
                         </div>
                     </td>
-                    <th>打包类型</th>
-                    <td>
-                        <div class="layui-form-item">
-                            <div class="layui-input-inline">
-                                <select th:attr="name=@{'deployConfigs[0].packType'}">
-                                    <option th:each="item : ${packTypes}" th:value="${item.key}">[[${item.value}]]</option>
-                                </select>
-                            </div>
-                        </div>
-                    </td>
                     <th>启动脚本</th>
                     <td>
                         <div class="layui-form-item">
@@ -367,16 +373,6 @@
                         </div>
                     </div>
                 </td>
-                    <th>打包类型</th>
-                    <td>
-                        <div class="layui-form-item">
-                            <div class="layui-input-inline">
-                                <select th:attr="name=@{'deployConfigs[0].packType'}">
-                                    <option th:each="item : ${packTypes}" th:value="${item.key}">[[${item.value}]]</option>
-                                </select>
-                            </div>
-                        </div>
-                    </td>
                 <th>启动脚本</th>
                 <td>
                     <div class="layui-form-item">
@@ -469,7 +465,6 @@
 <script type="text/javascript" th:src="@{/js/plugins/jquery-2.2.4.min.js}"></script>
 <script type="text/javascript" th:inline="javascript">
     var machineList = [[${machines}]]
-    var packTypeList = [[${packTypes}]]
     var deployConfigSize = $('#deployConfigTable tbody tr').length
     // 在表格末尾添加一行
     $('body').on('click', '.addDeployConfig', function() {
@@ -480,12 +475,6 @@
         }
         selectMachineHtml += '</select>'
 
-        var selectPackTypeHtml = '<select name="deployConfigs[' + deployConfigSize + '].packType">\n'
-        for (let i in packTypeList) {
-            selectPackTypeHtml += '<option value="' + packTypeList[i].key + '">' + packTypeList[i].value + '</option>\n'
-        }
-        selectPackTypeHtml += '</select>';
-
         // 需要添加的 html
         var html =
             '<tr>\n' +
@@ -495,12 +484,6 @@
             '                        </div>\n' +
             '                    </div>\n' +
             '                </td>\n' +
-            '                <th>打包类型</th>\n' +
-            '                <td>\n' +
-            '                    <div class="layui-form-item">\n' + selectPackTypeHtml +
-            '                        </div>\n' +
-            '                    </div>\n' +
-            '                </td>\n' +
             '                <th>启动脚本</th>\n' +
             '                <td>\n' +
             '                    <div class="layui-form-item">\n' +
@@ -576,6 +559,12 @@
             '                            </div>\n' +
             '                        </div>\n' +
             '                    </td>\n' +
+            '                    <td>\n' +
+            '                        <div>\n' +
+            '                            <a class="layui-btn layui-btn-xs addNotifyReceiver">添加</a>\n' +
+            '                            <a class="layui-btn layui-btn-danger layui-btn-xs delNotifyReceiver">删除</a>\n' +
+            '                        </div>\n' +
+            '                    </td>'
             '                    </tr>';
 
         // 添加到表格最后

+ 3 - 3
dmaster/src/main/resources/templates/app/config/app/copy.html

@@ -6,7 +6,7 @@
 <body>
 <div class="layui-form timo-compile">
     <form th:action="@{'/api/app/config/app/copy/' + ${app.id}}">
-        <input type="hidden" name="id" th:value="${app.id}"/>
+        <!--<input type="hidden" name="id" th:value="${app.id}"/>-->
         <div class="layui-form-item">
             <label class="layui-form-label required">新应用 ID</label>
             <div class="layui-input-inline">
@@ -50,9 +50,9 @@
             </div>
         </div>
         <div class="layui-form-item">
-            <label class="layui-form-label required">新应用 Dockerfile</label>
+            <label class="layui-form-label">新应用 Dockerfile</label>
             <div class="layui-input-inline">
-                <textarea class="layui-textarea" name="newDockerfile" placeholder="请输入新应用 Dockerfile"></textarea>
+                <textarea class="layui-textarea" name="newDockerfile" placeholder="打包类型是 docker 时才需要"></textarea>
             </div>
         </div>
         <div class="layui-form-item timo-finally">

+ 6 - 2
dmaster/src/main/resources/templates/app/config/app/detail.html

@@ -6,6 +6,12 @@
         <div class="timo-detail-title">基本信息</div>
         <table class="layui-table timo-detail-table">
             <tbody>
+            <span th:if="${app?.proj} != null">
+                <tr>
+                    <th>所属项目</th>
+                    <td th:text="${app.proj.projName}"></td>
+                </tr>
+            </span>
             <tr>
                 <th>应用 ID</th>
                 <td th:text="${app.appId}"></td>
@@ -128,8 +134,6 @@
                 <td th:text="${item.machineId}"></td>
                 <th>机器地址</th>
                 <td th:text="${item.machineIpv4}"></td>
-                <th>打包类型</th>
-                <td th:text="${item.packType}"></td>
                 <th>启动脚本</th>
                 <td>
                     <textarea class="layui-textarea" readonly="readonly" th:text="${item.startScript}"></textarea>

+ 10 - 3
dmaster/src/main/resources/templates/app/config/app/index.html

@@ -86,9 +86,16 @@
                     <td th:text="${item.repoBranch}">分支</td>
                     <td th:text="${item.codeDirname}">代码目录</td>
                     <td th:text="${item.appType}">应用类型</td>
-                    <td th:text="${item.repoAuthConfig?.name?: ''}">仓库认证</td>
-                    <td th:text="${item.compilerConfig?.name?: ''}">编译器</td>
-                    <td th:text="${item.packerConfig?.name?: ''}">打包工具</td>
+                    <span th:if="${item.proj} != null">
+                        <td th:text="${item.proj.repoAuthConfig.name}">仓库认证</td>
+                        <td th:text="${item.proj.compilerConfig.name}">编译器</td>
+                        <td th:text="${item.proj.packerConfig.name}">打包工具</td>
+                    </span>
+                    <span th:if="${item.proj} == null">
+                        <td th:text="${item.repoAuthConfig.name}">仓库认证</td>
+                        <td th:text="${item.compilerConfig.name}">编译器</td>
+                        <td th:text="${item.packerConfig.name}">打包工具</td>
+                    </span>
                     <td th:text="${item.runningConfig?.httpPort?: ''}">HTTP 端口</td>
                     <td>
                         <a class="open-popup" data-title="拷贝" th:attr="data-url=@{'/app/config/app/copy/'+${item.id}}"