Răsfoiți Sursa

增加管理与导出接口

luoshi 2 ani în urmă
părinte
comite
e414c2669a
31 a modificat fișierele cu 1078 adăugiri și 152 ștergeri
  1. 0 36
      src/main/java/com/qmth/ops/api/controller/AdminUserController.java
  2. 0 11
      src/main/java/com/qmth/ops/api/controller/ExportController.java
  3. 46 0
      src/main/java/com/qmth/ops/api/controller/admin/AppController.java
  4. 66 0
      src/main/java/com/qmth/ops/api/controller/admin/ConfigController.java
  5. 46 0
      src/main/java/com/qmth/ops/api/controller/admin/EnvController.java
  6. 36 0
      src/main/java/com/qmth/ops/api/controller/admin/ModuleController.java
  7. 3 4
      src/main/java/com/qmth/ops/api/controller/admin/UserController.java
  8. 44 0
      src/main/java/com/qmth/ops/api/controller/admin/VersionController.java
  9. 20 0
      src/main/java/com/qmth/ops/api/controller/binder/ConfigFormatBinder.java
  10. 20 0
      src/main/java/com/qmth/ops/api/controller/binder/VersionNumberBinder.java
  11. 106 0
      src/main/java/com/qmth/ops/api/controller/export/ConfigExportController.java
  12. 33 0
      src/main/java/com/qmth/ops/api/dto/CodeNameBean.java
  13. 8 0
      src/main/java/com/qmth/ops/biz/dao/AppDao.java
  14. 8 0
      src/main/java/com/qmth/ops/biz/dao/ConfigItemDao.java
  15. 8 0
      src/main/java/com/qmth/ops/biz/dao/EnvDao.java
  16. 8 0
      src/main/java/com/qmth/ops/biz/dao/ModuleDao.java
  17. 8 0
      src/main/java/com/qmth/ops/biz/dao/VersionDao.java
  18. 0 81
      src/main/java/com/qmth/ops/biz/domain/BaselineItem.java
  19. 25 0
      src/main/java/com/qmth/ops/biz/domain/ConfigFormat.java
  20. 12 2
      src/main/java/com/qmth/ops/biz/domain/ConfigItem.java
  21. 45 0
      src/main/java/com/qmth/ops/biz/query/AppQuery.java
  22. 67 0
      src/main/java/com/qmth/ops/biz/query/ModuleQuery.java
  23. 47 0
      src/main/java/com/qmth/ops/biz/query/VersionQuery.java
  24. 59 0
      src/main/java/com/qmth/ops/biz/service/AppService.java
  25. 155 0
      src/main/java/com/qmth/ops/biz/service/ConfigService.java
  26. 44 0
      src/main/java/com/qmth/ops/biz/service/EnvService.java
  27. 52 0
      src/main/java/com/qmth/ops/biz/service/ModuleService.java
  28. 8 5
      src/main/java/com/qmth/ops/biz/service/UserService.java
  29. 59 0
      src/main/java/com/qmth/ops/biz/service/VersionService.java
  30. 37 0
      src/main/java/com/qmth/ops/biz/utils/PropertyFileUtil.java
  31. 8 13
      src/main/java/com/qmth/ops/biz/utils/VersionNumber.java

+ 0 - 36
src/main/java/com/qmth/ops/api/controller/AdminUserController.java

@@ -1,36 +0,0 @@
-package com.qmth.ops.api.controller;
-
-import com.qmth.boot.api.annotation.Aac;
-import com.qmth.boot.api.annotation.BOOL;
-import com.qmth.boot.core.exception.ParameterException;
-import com.qmth.ops.api.constants.OpsApiConstants;
-import com.qmth.ops.api.security.AdminSession;
-import com.qmth.ops.biz.domain.User;
-import com.qmth.ops.biz.service.UserService;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-
-@RestController
-@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX)
-@Aac(auth = BOOL.TRUE)
-public class AdminUserController {
-
-    @Resource
-    private UserService userService;
-
-    @PostMapping("/login")
-    @Aac(auth = BOOL.FALSE)
-    public AdminSession login(User request) {
-        User user = userService.findByLoginName(request.getLoginName());
-        if (user == null) {
-            throw new ParameterException("登录名错误");
-        }
-        if (!user.buildPassword(request.getPassword()).equals(user.getPassword())) {
-            throw new ParameterException("密码错误");
-        }
-        return new AdminSession(user);
-    }
-}

+ 0 - 11
src/main/java/com/qmth/ops/api/controller/ExportController.java

@@ -1,11 +0,0 @@
-package com.qmth.ops.api.controller;
-
-import com.qmth.ops.api.constants.OpsApiConstants;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping(OpsApiConstants.EXPORT_URI_PREFIX)
-public class ExportController {
-
-}

+ 46 - 0
src/main/java/com/qmth/ops/api/controller/admin/AppController.java

@@ -0,0 +1,46 @@
+package com.qmth.ops.api.controller.admin;
+
+import com.qmth.ops.api.constants.OpsApiConstants;
+import com.qmth.ops.biz.domain.App;
+import com.qmth.ops.biz.query.AppQuery;
+import com.qmth.ops.biz.service.AppService;
+import com.qmth.ops.biz.service.VersionService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+@RestController
+@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX + "/app")
+public class AppController {
+
+    @Resource
+    private AppService appService;
+
+    @Resource
+    private VersionService versionService;
+
+    @PostMapping("/query")
+    public AppQuery query(AppQuery query) {
+        return appService.query(query);
+    }
+
+    @PostMapping("/insert")
+    public App insert(App app) {
+        return appService.insert(app);
+    }
+
+    @PostMapping("/update")
+    public App update(App app) {
+        return appService.update(app);
+    }
+
+    @PostMapping("/version/master")
+    public App updateMasterVersion(@RequestParam Long id, @RequestParam Long versionId) {
+        appService.setMasterVersion(appService.findById(id), versionService.findByid(versionId));
+        return appService.findById(id);
+    }
+}
+

+ 66 - 0
src/main/java/com/qmth/ops/api/controller/admin/ConfigController.java

@@ -0,0 +1,66 @@
+package com.qmth.ops.api.controller.admin;
+
+import com.qmth.ops.api.constants.OpsApiConstants;
+import com.qmth.ops.biz.domain.ConfigItem;
+import com.qmth.ops.biz.service.AppService;
+import com.qmth.ops.biz.service.ConfigService;
+import com.qmth.ops.biz.service.ModuleService;
+import com.qmth.ops.biz.service.VersionService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX + "/app")
+public class ConfigController {
+
+    @Resource
+    private ConfigService configService;
+
+    @Resource
+    private AppService appService;
+
+    @Resource
+    private ModuleService moduleService;
+
+    @Resource
+    private VersionService versionService;
+
+    @PostMapping("/baseline")
+    public List<ConfigItem> listBaseline(@RequestParam Long appId, @RequestParam Long versionId,
+            @RequestParam Long moduleId) {
+        return configService.listBaseline(appId, versionId, moduleId);
+    }
+
+    @PostMapping("/baseline/update")
+    public Object updateBaseline(@RequestParam Long appId, @RequestParam Long versionId, @RequestParam Long moduleId,
+            @RequestParam MultipartFile file, @RequestParam(required = false) Long inheritVersionId)
+            throws IOException {
+        return configService.updateBaseline(appService.findById(appId), versionService.findByid(versionId),
+                moduleService.findById(moduleId), file.getInputStream(),
+                inheritVersionId != null ? versionService.findByid(inheritVersionId) : null);
+    }
+
+    @PostMapping("/baseline/item/update")
+    public ConfigItem updateBaselineItem(ConfigItem item) {
+        return configService.updateBaselineItem(item);
+    }
+
+    @PostMapping("/list")
+    public List<ConfigItem> listConfigItem(@RequestParam Long appId, @RequestParam Long versionId,
+            @RequestParam Long moduleId, @RequestParam Long envId) {
+        return configService.listConfigItem(appId, versionId, moduleId, envId);
+    }
+
+    @PostMapping("/item/update")
+    public ConfigItem updateConfigItem(ConfigItem item) {
+        return configService.updateConfigItem(item);
+    }
+}
+

+ 46 - 0
src/main/java/com/qmth/ops/api/controller/admin/EnvController.java

@@ -0,0 +1,46 @@
+package com.qmth.ops.api.controller.admin;
+
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.annotation.BOOL;
+import com.qmth.ops.api.constants.OpsApiConstants;
+import com.qmth.ops.api.dto.CodeNameBean;
+import com.qmth.ops.biz.domain.Env;
+import com.qmth.ops.biz.domain.EnvType;
+import com.qmth.ops.biz.service.EnvService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.List;
+
+@RestController
+@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX + "/env")
+public class EnvController {
+
+    @Resource
+    private EnvService envService;
+
+    @RequestMapping("/types")
+    @Aac(auth = BOOL.FALSE)
+    public Object types() {
+        return Arrays.stream(EnvType.values()).map(item -> new CodeNameBean(item.getCode(), item.getName())).toArray();
+    }
+
+    @PostMapping("/insert")
+    public Env insert(Env env) {
+        return envService.insert(env);
+    }
+
+    @PostMapping("/update")
+    public Env update(Env env) {
+        return envService.update(env);
+    }
+
+    @PostMapping("/list")
+    public List<Env> list(Long appId) {
+        return envService.list(appId);
+    }
+
+}

+ 36 - 0
src/main/java/com/qmth/ops/api/controller/admin/ModuleController.java

@@ -0,0 +1,36 @@
+package com.qmth.ops.api.controller.admin;
+
+import com.qmth.ops.api.constants.OpsApiConstants;
+import com.qmth.ops.biz.domain.Module;
+import com.qmth.ops.biz.query.ModuleQuery;
+import com.qmth.ops.biz.service.ModuleService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@RestController
+@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX + "/module")
+public class ModuleController {
+
+    @Resource
+    private ModuleService moduleService;
+
+    @PostMapping("/insert")
+    public Module insert(Module module) {
+        return moduleService.insert(module);
+    }
+
+    @PostMapping("/update")
+    public Module update(Module module) {
+        return moduleService.update(module);
+    }
+
+    @PostMapping("/list")
+    public List<Module> list(ModuleQuery query) {
+        return moduleService.list(query);
+    }
+
+}

+ 3 - 4
src/main/java/com/qmth/ops/api/controller/AdminAppController.java → src/main/java/com/qmth/ops/api/controller/admin/UserController.java

@@ -1,4 +1,4 @@
-package com.qmth.ops.api.controller;
+package com.qmth.ops.api.controller.admin;
 
 import com.qmth.boot.api.annotation.Aac;
 import com.qmth.boot.api.annotation.BOOL;
@@ -14,9 +14,8 @@ import org.springframework.web.bind.annotation.RestController;
 import javax.annotation.Resource;
 
 @RestController
-@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX)
-@Aac(auth = BOOL.TRUE)
-public class AdminAppController {
+@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX + "/user")
+public class UserController {
 
     @Resource
     private UserService userService;

+ 44 - 0
src/main/java/com/qmth/ops/api/controller/admin/VersionController.java

@@ -0,0 +1,44 @@
+package com.qmth.ops.api.controller.admin;
+
+import com.qmth.ops.api.constants.OpsApiConstants;
+import com.qmth.ops.api.controller.binder.VersionNumberBinder;
+import com.qmth.ops.biz.domain.Version;
+import com.qmth.ops.biz.query.VersionQuery;
+import com.qmth.ops.biz.service.AppService;
+import com.qmth.ops.biz.service.VersionService;
+import com.qmth.ops.biz.utils.VersionNumber;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+@RestController
+@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX + "/version")
+public class VersionController {
+
+    @Resource
+    private VersionService versionService;
+
+    @Resource
+    private AppService appService;
+
+    @InitBinder
+    public void initBinder(WebDataBinder dataBinder) {
+        dataBinder.addCustomFormatter(new VersionNumberBinder());
+    }
+
+    @PostMapping("/insert")
+    public Version insert(@RequestParam Long appId, @RequestParam VersionNumber name) {
+        return versionService.insert(appService.findById(appId), name);
+    }
+
+    @PostMapping("/list")
+    public VersionQuery list(VersionQuery query) {
+        return versionService.query(query);
+    }
+
+    @PostMapping("/update")
+    public Version list(Version version) {
+        return versionService.update(version);
+    }
+}

+ 20 - 0
src/main/java/com/qmth/ops/api/controller/binder/ConfigFormatBinder.java

@@ -0,0 +1,20 @@
+package com.qmth.ops.api.controller.binder;
+
+import com.qmth.ops.biz.domain.ConfigFormat;
+import org.springframework.format.Formatter;
+
+import java.text.ParseException;
+import java.util.Locale;
+
+public class ConfigFormatBinder implements Formatter<ConfigFormat> {
+
+    @Override
+    public ConfigFormat parse(String s, Locale locale) throws ParseException {
+        return ConfigFormat.findByExtension(s);
+    }
+
+    @Override
+    public String print(ConfigFormat format, Locale locale) {
+        return format.getExtension();
+    }
+}

+ 20 - 0
src/main/java/com/qmth/ops/api/controller/binder/VersionNumberBinder.java

@@ -0,0 +1,20 @@
+package com.qmth.ops.api.controller.binder;
+
+import com.qmth.ops.biz.utils.VersionNumber;
+import org.springframework.format.Formatter;
+
+import java.text.ParseException;
+import java.util.Locale;
+
+public class VersionNumberBinder implements Formatter<VersionNumber> {
+
+    @Override
+    public VersionNumber parse(String s, Locale locale) throws ParseException {
+        return new VersionNumber(s);
+    }
+
+    @Override
+    public String print(VersionNumber versionNumber, Locale locale) {
+        return versionNumber.toString();
+    }
+}

+ 106 - 0
src/main/java/com/qmth/ops/api/controller/export/ConfigExportController.java

@@ -0,0 +1,106 @@
+package com.qmth.ops.api.controller.export;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.qmth.boot.api.annotation.Aac;
+import com.qmth.boot.api.annotation.BOOL;
+import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.boot.core.exception.UnauthorizedException;
+import com.qmth.ops.api.constants.OpsApiConstants;
+import com.qmth.ops.api.controller.binder.ConfigFormatBinder;
+import com.qmth.ops.biz.domain.*;
+import com.qmth.ops.biz.service.*;
+import com.qmth.ops.biz.utils.PropertyFileUtil;
+import com.qmth.ops.biz.utils.VersionNumber;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping(OpsApiConstants.EXPORT_URI_PREFIX + "/config")
+@Aac(auth = BOOL.FALSE)
+public class ConfigExportController {
+
+    @Resource
+    private UserService userService;
+
+    @Resource
+    private AppService appService;
+
+    @Resource
+    private ModuleService moduleService;
+
+    @Resource
+    private EnvService envService;
+
+    @Resource
+    private VersionService versionService;
+
+    @Resource
+    private ConfigService configService;
+
+    @InitBinder
+    public void initBinder(WebDataBinder dataBinder) {
+        dataBinder.addCustomFormatter(new ConfigFormatBinder());
+    }
+
+    @GetMapping("/{appCode}/{moduleCode}/{envCode}/{version}.{format}")
+    public void exportVersionConfigFile(@PathVariable String appCode, @PathVariable String moduleCode,
+            @PathVariable String envCode, @PathVariable String version, @PathVariable ConfigFormat format,
+            @RequestParam String secret, HttpServletResponse response) throws IOException {
+        exportConfigFile(appCode, moduleCode, envCode, version, secret, format, response);
+    }
+
+    @GetMapping("/{appCode}/{moduleCode}/{envCode}.{format}")
+    public void exportMasterVersionConfigFile(@PathVariable String appCode, @PathVariable String moduleCode,
+            @PathVariable String envCode, @PathVariable ConfigFormat format, @RequestParam String secret,
+            HttpServletResponse response) throws IOException {
+        exportConfigFile(appCode, moduleCode, envCode, null, secret, format, response);
+    }
+
+    private void exportConfigFile(String appCode, String moduleCode, String envCode, String versionNumber,
+            String exportSecret, ConfigFormat format, HttpServletResponse response) throws IOException {
+        User user = userService.findByExportSecret(exportSecret);
+        if (user == null || user.getRole() != Role.OPS) {
+            throw new UnauthorizedException("鉴权失败");
+        }
+        App app = appService.findByCode(appCode);
+        if (app == null) {
+            throw new ParameterException("app.code不存在");
+        }
+        Module module = moduleService.findByAppAndCode(app.getId(), moduleCode);
+        if (module == null) {
+            throw new ParameterException("module.code不存在");
+        }
+        Env env = envService.findByAppAndCode(app.getId(), envCode);
+        if (env == null) {
+            throw new ParameterException("module.code不存在");
+        }
+        Version version = versionNumber != null ?
+                versionService.findByAppAndNumber(app.getId(),
+                        new VersionNumber(versionNumber.replaceAll("-", ".").replaceAll("_", "."))) :
+                versionService.findByid(app.getMasterVersionId());
+        if (version == null) {
+            throw new ParameterException("version不存在");
+        }
+        List<ConfigItem> list = configService
+                .mergeConfigList(app.getId(), version.getId(), module.getId(), env.getId());
+        if (format == ConfigFormat.PROPERTY) {
+            response.reset();
+            response.setContentType("application/octet-stream; charset=unicode");
+            response.setHeader("Content-Disposition", "attachment; filename=application." + format.getExtension());
+            PropertyFileUtil.write(list, response.getOutputStream());
+        } else if (format == ConfigFormat.JSON) {
+            response.reset();
+            response.setContentType("application/octet-stream; charset=utf-8");
+            response.setHeader("Content-Disposition", "attachment; filename=application." + format.getExtension());
+            new ObjectMapper().writeValue(response.getOutputStream(),
+                    list.stream().collect(Collectors.toMap(ConfigItem::getKey, ConfigItem::getValue)));
+        }
+    }
+
+}

+ 33 - 0
src/main/java/com/qmth/ops/api/dto/CodeNameBean.java

@@ -0,0 +1,33 @@
+package com.qmth.ops.api.dto;
+
+public class CodeNameBean {
+
+    private String code;
+
+    private String name;
+
+    public CodeNameBean() {
+
+    }
+
+    public CodeNameBean(String code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}

+ 8 - 0
src/main/java/com/qmth/ops/biz/dao/AppDao.java

@@ -0,0 +1,8 @@
+package com.qmth.ops.biz.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.ops.biz.domain.App;
+
+public interface AppDao extends BaseMapper<App> {
+
+}

+ 8 - 0
src/main/java/com/qmth/ops/biz/dao/ConfigItemDao.java

@@ -0,0 +1,8 @@
+package com.qmth.ops.biz.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.ops.biz.domain.ConfigItem;
+
+public interface ConfigItemDao extends BaseMapper<ConfigItem> {
+
+}

+ 8 - 0
src/main/java/com/qmth/ops/biz/dao/EnvDao.java

@@ -0,0 +1,8 @@
+package com.qmth.ops.biz.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.ops.biz.domain.Env;
+
+public interface EnvDao extends BaseMapper<Env> {
+
+}

+ 8 - 0
src/main/java/com/qmth/ops/biz/dao/ModuleDao.java

@@ -0,0 +1,8 @@
+package com.qmth.ops.biz.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.ops.biz.domain.Module;
+
+public interface ModuleDao extends BaseMapper<Module> {
+
+}

+ 8 - 0
src/main/java/com/qmth/ops/biz/dao/VersionDao.java

@@ -0,0 +1,8 @@
+package com.qmth.ops.biz.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.ops.biz.domain.Version;
+
+public interface VersionDao extends BaseMapper<Version> {
+
+}

+ 0 - 81
src/main/java/com/qmth/ops/biz/domain/BaselineItem.java

@@ -1,81 +0,0 @@
-package com.qmth.ops.biz.domain;
-
-import com.baomidou.mybatisplus.annotation.TableName;
-
-import java.io.Serializable;
-
-@TableName("baseline_item")
-public class BaselineItem implements Serializable {
-
-    private static final long serialVersionUID = -3347199955021382356L;
-
-    private Long appId;
-
-    private Long versionId;
-
-    private Long moduleId;
-
-    private String key;
-
-    private String value;
-
-    private String comment;
-
-    private ConfigMode mode;
-
-    public Long getAppId() {
-        return appId;
-    }
-
-    public void setAppId(Long appId) {
-        this.appId = appId;
-    }
-
-    public Long getVersionId() {
-        return versionId;
-    }
-
-    public void setVersionId(Long versionId) {
-        this.versionId = versionId;
-    }
-
-    public Long getModuleId() {
-        return moduleId;
-    }
-
-    public void setModuleId(Long moduleId) {
-        this.moduleId = moduleId;
-    }
-
-    public String getKey() {
-        return key;
-    }
-
-    public void setKey(String key) {
-        this.key = key;
-    }
-
-    public String getValue() {
-        return value;
-    }
-
-    public void setValue(String value) {
-        this.value = value;
-    }
-
-    public String getComment() {
-        return comment;
-    }
-
-    public void setComment(String comment) {
-        this.comment = comment;
-    }
-
-    public ConfigMode getMode() {
-        return mode;
-    }
-
-    public void setMode(ConfigMode mode) {
-        this.mode = mode;
-    }
-}

+ 25 - 0
src/main/java/com/qmth/ops/biz/domain/ConfigFormat.java

@@ -0,0 +1,25 @@
+package com.qmth.ops.biz.domain;
+
+public enum ConfigFormat {
+
+    PROPERTY("properties"), JSON("json");
+
+    private String extension;
+
+    private ConfigFormat(String extension) {
+        this.extension = extension;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+
+    public static ConfigFormat findByExtension(String extension) {
+        for (ConfigFormat format : values()) {
+            if (format.getExtension().equalsIgnoreCase(extension)) {
+                return format;
+            }
+        }
+        return null;
+    }
+}

+ 12 - 2
src/main/java/com/qmth/ops/biz/domain/ConfigItem.java

@@ -13,16 +13,18 @@ public class ConfigItem implements Serializable {
 
     private Long versionId;
 
-    private Long envId;
-
     private Long moduleId;
 
+    private Long envId;
+
     private String key;
 
     private String value;
 
     private String comment;
 
+    private ConfigMode mode;
+
     private Long createTime;
 
     private Long updateTime;
@@ -83,6 +85,14 @@ public class ConfigItem implements Serializable {
         this.comment = comment;
     }
 
+    public ConfigMode getMode() {
+        return mode;
+    }
+
+    public void setMode(ConfigMode mode) {
+        this.mode = mode;
+    }
+
     public Long getCreateTime() {
         return createTime;
     }

+ 45 - 0
src/main/java/com/qmth/ops/biz/query/AppQuery.java

@@ -0,0 +1,45 @@
+package com.qmth.ops.biz.query;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.qmth.boot.mybatis.query.BaseQuery;
+import com.qmth.ops.biz.domain.App;
+
+public class AppQuery extends BaseQuery<App> {
+
+    private static final long serialVersionUID = -5738758110090463622L;
+
+    private Long id;
+
+    private String code;
+
+    private String nameStartWith;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getNameStartWith() {
+        return nameStartWith;
+    }
+
+    public void setNameStartWith(String nameStartWith) {
+        this.nameStartWith = nameStartWith;
+    }
+
+    public LambdaQueryWrapper<App> build() {
+        return new LambdaQueryWrapper<App>().eq(id != null, App::getId, id).eq(code != null, App::getCode, code)
+                .likeRight(nameStartWith != null, App::getName, nameStartWith);
+    }
+}

+ 67 - 0
src/main/java/com/qmth/ops/biz/query/ModuleQuery.java

@@ -0,0 +1,67 @@
+package com.qmth.ops.biz.query;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.qmth.boot.mybatis.query.BaseQuery;
+import com.qmth.ops.biz.domain.Module;
+
+public class ModuleQuery extends BaseQuery<Module> {
+
+    private static final long serialVersionUID = -3730883675324617848L;
+
+    private Long id;
+
+    private Long appId;
+
+    private String code;
+
+    private String name;
+
+    private Boolean enable;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getAppId() {
+        return appId;
+    }
+
+    public void setAppId(Long appId) {
+        this.appId = appId;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Boolean enable) {
+        this.enable = enable;
+    }
+
+    public LambdaQueryWrapper<Module> build() {
+        return new LambdaQueryWrapper<Module>().eq(id != null, Module::getId, id)
+                .eq(appId != null, Module::getAppId, appId).eq(code != null, Module::getCode, code)
+                .eq(enable != null, Module::getEnable, enable).eq(name != null, Module::getName, name)
+                .orderByAsc(Module::getCode);
+    }
+}

+ 47 - 0
src/main/java/com/qmth/ops/biz/query/VersionQuery.java

@@ -0,0 +1,47 @@
+package com.qmth.ops.biz.query;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.qmth.boot.mybatis.query.BaseQuery;
+import com.qmth.ops.biz.domain.Version;
+
+public class VersionQuery extends BaseQuery<Version> {
+
+    private static final long serialVersionUID = 3221770734799644830L;
+
+    private Long id;
+
+    private Long appId;
+
+    private Boolean archived;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getAppId() {
+        return appId;
+    }
+
+    public void setAppId(Long appId) {
+        this.appId = appId;
+    }
+
+    public Boolean getArchived() {
+        return archived;
+    }
+
+    public void setArchived(Boolean archived) {
+        this.archived = archived;
+    }
+
+    public LambdaQueryWrapper<Version> build() {
+        return new LambdaQueryWrapper<Version>().eq(id != null, Version::getId, id)
+                .eq(appId != null, Version::getAppId, appId).eq(archived != null, Version::getArchived, archived)
+                .orderByDesc(Version::getMainNumber).orderByDesc(Version::getMiddleNumber)
+                .orderByDesc(Version::getSubNumber);
+    }
+}

+ 59 - 0
src/main/java/com/qmth/ops/biz/service/AppService.java

@@ -0,0 +1,59 @@
+package com.qmth.ops.biz.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.ops.biz.dao.AppDao;
+import com.qmth.ops.biz.domain.App;
+import com.qmth.ops.biz.domain.Version;
+import com.qmth.ops.biz.query.AppQuery;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import javax.validation.constraints.NotNull;
+
+@Service
+public class AppService {
+
+    @Resource
+    private AppDao appDao;
+
+    @Transactional
+    public App insert(App app) {
+        app.setCreateTime(System.currentTimeMillis());
+        app.setUpdateTime(app.getCreateTime());
+        appDao.insert(app);
+        return app;
+    }
+
+    @Transactional
+    public App update(App app) {
+        appDao.update(app, new LambdaUpdateWrapper<App>().set(app.getCode() != null, App::getCode, app.getCode())
+                .set(app.getName() != null, App::getName, app.getName())
+                .set(App::getUpdateTime, System.currentTimeMillis()).eq(App::getId, app.getId()));
+        return appDao.selectById(app.getId());
+    }
+
+    public AppQuery query(AppQuery query) {
+        return appDao.selectPage(query, query.build());
+    }
+
+    public App findByCode(String code) {
+        return appDao.selectOne(new LambdaQueryWrapper<App>().eq(App::getCode, code));
+    }
+
+    public App findById(Long id) {
+        return appDao.selectById(id);
+    }
+
+    @Transactional
+    public void setMasterVersion(@NotNull App app, @NotNull Version version) {
+        if (!version.getAppId().equals(app.getId())) {
+            throw new ParameterException("指定版本不属于当前应用");
+        }
+        appDao.update(app, new LambdaUpdateWrapper<App>().set(App::getMasterVersionId, version.getId())
+                .set(App::getUpdateTime, System.currentTimeMillis()).eq(App::getId, app.getId()));
+    }
+}
+

+ 155 - 0
src/main/java/com/qmth/ops/biz/service/ConfigService.java

@@ -0,0 +1,155 @@
+package com.qmth.ops.biz.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.boot.core.exception.StatusException;
+import com.qmth.ops.biz.dao.ConfigItemDao;
+import com.qmth.ops.biz.domain.*;
+import com.qmth.ops.biz.utils.PropertyFileUtil;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import javax.validation.constraints.NotNull;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Service
+public class ConfigService {
+
+    private static final long BASELINE_ENV_ID = 0L;
+
+    @Resource
+    private ConfigItemDao configItemDao;
+
+    public List<ConfigItem> listBaseline(Long appId, Long versionId, Long moduleId) {
+        return configItemDao.selectList(new LambdaQueryWrapper<ConfigItem>().eq(ConfigItem::getAppId, appId)
+                .eq(ConfigItem::getVersionId, versionId).eq(ConfigItem::getModuleId, moduleId)
+                .eq(ConfigItem::getEnvId, BASELINE_ENV_ID).orderByAsc(ConfigItem::getKey));
+    }
+
+    @Transactional
+    public List<ConfigItem> updateBaseline(@NotNull App app, @NotNull Version version, @NotNull Module module,
+            @NotNull InputStream file, Version inheritVersion) throws IOException {
+        if (!version.getAppId().equals(app.getId())) {
+            throw new ParameterException("指定版本不属于该应用");
+        }
+        if (!version.getArchived()) {
+            throw new ParameterException("指定版本已归档不能操作");
+        }
+        if (!module.getAppId().equals(app.getId())) {
+            throw new ParameterException("指定模块不属于该应用");
+        }
+        long time = System.currentTimeMillis();
+        configItemDao.delete(new LambdaUpdateWrapper<ConfigItem>().eq(ConfigItem::getAppId, app.getId())
+                .eq(ConfigItem::getVersionId, version.getId()).eq(ConfigItem::getModuleId, module.getId())
+                .eq(ConfigItem::getEnvId, BASELINE_ENV_ID));
+        Map<String, ConfigItem> baseMap = new HashMap<>();
+        if (inheritVersion != null) {
+            listBaseline(app.getId(), inheritVersion.getId(), module.getId())
+                    .forEach(item -> baseMap.put(item.getKey(), item));
+        }
+        List<ConfigItem> list = PropertyFileUtil.read(file);
+        for (ConfigItem item : list) {
+            item.setAppId(app.getId());
+            item.setVersionId(version.getId());
+            item.setModuleId(module.getId());
+            item.setEnvId(BASELINE_ENV_ID);
+            item.setCreateTime(time);
+            item.setUpdateTime(time);
+            ConfigItem base = baseMap.get(item.getKey());
+            if (base != null) {
+                item.setMode(base.getMode());
+                item.setComment(base.getComment());
+            }
+            configItemDao.insert(item);
+        }
+        return listBaseline(app.getId(), version.getId(), module.getId());
+    }
+
+    @Transactional
+    public ConfigItem updateBaselineItem(ConfigItem item) {
+        configItemDao.update(item,
+                new LambdaUpdateWrapper<ConfigItem>().set(item.getMode() != null, ConfigItem::getMode, item.getMode())
+                        .set(item.getComment() != null, ConfigItem::getComment, item.getComment())
+                        .eq(ConfigItem::getAppId, item.getAppId()).eq(ConfigItem::getVersionId, item.getVersionId())
+                        .eq(ConfigItem::getModuleId, item.getModuleId()).eq(ConfigItem::getEnvId, BASELINE_ENV_ID)
+                        .eq(ConfigItem::getKey, item.getKey()));
+        return findOne(item.getAppId(), item.getVersionId(), item.getModuleId(), BASELINE_ENV_ID, item.getKey());
+    }
+
+    public List<ConfigItem> listConfigItem(Long appId, Long versionId, Long moduleId, Long envId) {
+        return configItemDao.selectList(new LambdaQueryWrapper<ConfigItem>().eq(ConfigItem::getAppId, appId)
+                .eq(ConfigItem::getVersionId, versionId).eq(ConfigItem::getModuleId, moduleId)
+                .eq(ConfigItem::getEnvId, envId).orderByAsc(ConfigItem::getKey));
+    }
+
+    @Transactional
+    public ConfigItem updateConfigItem(ConfigItem item) {
+        ConfigItem base = findOne(item.getAppId(), item.getVersionId(), item.getModuleId(), BASELINE_ENV_ID,
+                item.getKey());
+        ConfigItem previous = findOne(item.getAppId(), item.getVersionId(), item.getModuleId(), item.getEnvId(),
+                item.getKey());
+        if (base != null && base.getMode() == ConfigMode.READONLY) {
+            throw new ParameterException("配置项只读");
+        }
+        if (item.getValue() == null) {
+            throw new ParameterException("配置值不能为空");
+        }
+        if (previous != null) {
+            configItemDao.update(previous,
+                    new LambdaUpdateWrapper<ConfigItem>().set(ConfigItem::getValue, item.getValue())
+                            .set(item.getComment() != null, ConfigItem::getComment, item.getComment())
+                            .set(ConfigItem::getUpdateTime, System.currentTimeMillis())
+                            .eq(ConfigItem::getAppId, item.getAppId()).eq(ConfigItem::getVersionId, item.getVersionId())
+                            .eq(ConfigItem::getModuleId, item.getModuleId()).eq(ConfigItem::getEnvId, item.getEnvId())
+                            .eq(ConfigItem::getKey, item.getKey()));
+            return findOne(item.getAppId(), item.getVersionId(), item.getModuleId(), item.getEnvId(), item.getKey());
+        } else {
+            item.setCreateTime(System.currentTimeMillis());
+            item.setUpdateTime(item.getCreateTime());
+            configItemDao.insert(item);
+            return item;
+        }
+    }
+
+    public ConfigItem findOne(Long appId, Long versionId, Long moduleId, Long envId, String key) {
+        return configItemDao.selectOne(new LambdaQueryWrapper<ConfigItem>().eq(ConfigItem::getAppId, appId)
+                .eq(ConfigItem::getVersionId, versionId).eq(ConfigItem::getModuleId, moduleId)
+                .eq(ConfigItem::getEnvId, envId).eq(ConfigItem::getKey, key));
+    }
+
+    public List<ConfigItem> mergeConfigList(Long appId, Long versionId, Long moduleId, Long envId) {
+        List<ConfigItem> list = listBaseline(appId, versionId, moduleId);
+        //获取环境定义配置项
+        Map<String, ConfigItem> itemMap = listConfigItem(appId, versionId, moduleId, envId).stream()
+                .collect(Collectors.toMap(ConfigItem::getKey, Function.identity()));
+        //遍历基线
+        for (ConfigItem item : list) {
+            ConfigItem update = itemMap.get(item.getKey());
+            //非只读配置项更新
+            if (update != null && item.getMode() != ConfigMode.READONLY) {
+                item.setValue(update.getValue());
+            }
+            //需要覆盖的配置项不能校验
+            else if (update == null && item.getMode() == ConfigMode.OVERRIDE) {
+                throw new StatusException("配置项需要覆盖新值:" + item.getKey());
+            }
+            itemMap.remove(item.getKey());
+        }
+        //合并新增配置项
+        if (!itemMap.isEmpty()) {
+            list.addAll(itemMap.values());
+            list.sort(Comparator.comparing(ConfigItem::getKey));
+        }
+        return list;
+    }
+}
+

+ 44 - 0
src/main/java/com/qmth/ops/biz/service/EnvService.java

@@ -0,0 +1,44 @@
+package com.qmth.ops.biz.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.qmth.ops.biz.dao.EnvDao;
+import com.qmth.ops.biz.domain.Env;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@Service
+public class EnvService {
+
+    @Resource
+    private EnvDao envDao;
+
+    @Transactional
+    public Env insert(Env env) {
+        env.setCreateTime(System.currentTimeMillis());
+        env.setUpdateTime(env.getCreateTime());
+        envDao.insert(env);
+        return env;
+    }
+
+    @Transactional
+    public Env update(Env env) {
+        envDao.update(env, new LambdaUpdateWrapper<Env>().set(env.getCode() != null, Env::getCode, env.getCode())
+                .set(env.getName() != null, Env::getName, env.getName())
+                .set(Env::getUpdateTime, System.currentTimeMillis()).eq(Env::getId, env.getId()));
+        return envDao.selectById(env.getId());
+    }
+
+    public List<Env> list(Long appId) {
+        return envDao.selectList(new LambdaQueryWrapper<Env>().eq(Env::getAppId, appId));
+    }
+
+    public Env findByAppAndCode(Long appId, String code) {
+        return envDao.selectOne(new LambdaQueryWrapper<Env>().eq(Env::getAppId, appId).eq(Env::getCode, code));
+    }
+
+}
+

+ 52 - 0
src/main/java/com/qmth/ops/biz/service/ModuleService.java

@@ -0,0 +1,52 @@
+package com.qmth.ops.biz.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.qmth.ops.biz.dao.ModuleDao;
+import com.qmth.ops.biz.domain.Module;
+import com.qmth.ops.biz.query.ModuleQuery;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@Service
+public class ModuleService {
+
+    @Resource
+    private ModuleDao moduleDao;
+
+    @Transactional
+    public Module insert(Module module) {
+        module.setCreateTime(System.currentTimeMillis());
+        module.setUpdateTime(module.getCreateTime());
+        moduleDao.insert(module);
+        return module;
+    }
+
+    @Transactional
+    public Module update(Module module) {
+        moduleDao.update(module,
+                new LambdaUpdateWrapper<Module>().set(module.getCode() != null, Module::getCode, module.getCode())
+                        .set(module.getName() != null, Module::getName, module.getName())
+                        .set(module.getEnable() != null, Module::getEnable, module.getEnable())
+                        .set(Module::getUpdateTime, System.currentTimeMillis()).eq(Module::getId, module.getId()));
+        return moduleDao.selectById(module.getId());
+    }
+
+    public List<Module> list(ModuleQuery query) {
+        return moduleDao.selectList(query.build());
+    }
+
+    public Module findById(Long id) {
+        return moduleDao.selectById(id);
+    }
+
+    public Module findByAppAndCode(Long appId, String code) {
+        return moduleDao
+                .selectOne(new LambdaQueryWrapper<Module>().eq(Module::getAppId, appId).eq(Module::getCode, code));
+    }
+
+}
+

+ 8 - 5
src/main/java/com/qmth/ops/biz/service/UserService.java

@@ -5,6 +5,7 @@ import com.qmth.ops.biz.dao.UserDao;
 import com.qmth.ops.biz.domain.User;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 
@@ -22,15 +23,17 @@ public class UserService {
         return userDao.selectOne(new LambdaQueryWrapper<User>().eq(User::getLoginName, loginName));
     }
 
+    public User findByExportSecret(String exportSecret) {
+        return userDao.selectOne(new LambdaQueryWrapper<User>().eq(User::getExportSecret, exportSecret));
+    }
+
+    @Transactional
     public User insert(User user) {
         user.setExportSecret(RandomStringUtils.random(32, true, true));
         user.setCreateTime(System.currentTimeMillis());
         user.setUpdateTime(user.getCreateTime());
         user.setPassword(user.buildPassword(user.getPassword()));
-        if (userDao.insert(user) > 0) {
-            return user;
-        } else {
-            return null;
-        }
+        userDao.insert(user);
+        return user;
     }
 }

+ 59 - 0
src/main/java/com/qmth/ops/biz/service/VersionService.java

@@ -0,0 +1,59 @@
+package com.qmth.ops.biz.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.qmth.ops.biz.dao.VersionDao;
+import com.qmth.ops.biz.domain.App;
+import com.qmth.ops.biz.domain.Version;
+import com.qmth.ops.biz.query.VersionQuery;
+import com.qmth.ops.biz.utils.VersionNumber;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import javax.validation.constraints.NotNull;
+
+@Service
+public class VersionService {
+
+    @Resource
+    private VersionDao versionDao;
+
+    @Transactional
+    public Version insert(@NotNull App app, @NotNull VersionNumber versionNumber) {
+        Version version = new Version();
+        version.setAppId(app.getId());
+        version.setArchived(false);
+        version.setMainNumber(versionNumber.getMain());
+        version.setMiddleNumber(versionNumber.getMiddle());
+        version.setSubNumber(versionNumber.getSub());
+        version.setName(versionNumber.toString());
+        version.setCreateTime(System.currentTimeMillis());
+        version.setUpdateTime(version.getCreateTime());
+        versionDao.insert(version);
+        return version;
+    }
+
+    @Transactional
+    public Version update(@NotNull Version version) {
+        versionDao.update(version, new LambdaUpdateWrapper<Version>()
+                .set(version.getArchived() != null, Version::getArchived, version.getArchived())
+                .set(Version::getUpdateTime, System.currentTimeMillis()).eq(Version::getId, version.getId()));
+        return versionDao.selectById(version.getId());
+    }
+
+    public VersionQuery query(@NotNull VersionQuery query) {
+        return versionDao.selectPage(query, query.build());
+    }
+
+    public Version findByid(@NotNull Long id) {
+        return versionDao.selectById(id);
+    }
+
+    public Version findByAppAndNumber(@NotNull Long appId, @NotNull VersionNumber number) {
+        return versionDao.selectOne(new LambdaQueryWrapper<Version>().eq(Version::getAppId, appId)
+                .eq(Version::getMainNumber, number.getMain()).eq(Version::getMiddleNumber, number.getMiddle())
+                .eq(Version::getSubNumber, number.getSub()));
+    }
+}
+

+ 37 - 0
src/main/java/com/qmth/ops/biz/utils/PropertyFileUtil.java

@@ -0,0 +1,37 @@
+package com.qmth.ops.biz.utils;
+
+import com.qmth.ops.biz.domain.ConfigItem;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+public class PropertyFileUtil {
+
+    public static void write(List<ConfigItem> configList, OutputStream ous) throws IOException {
+        Properties p = new Properties();
+        for (ConfigItem item : configList) {
+            p.put(item.getKey(), item.getValue());
+        }
+        p.list(new PrintStream(ous));
+    }
+
+    public static List<ConfigItem> read(InputStream ins) throws IOException {
+        Properties p = new Properties();
+        p.load(ins);
+        Set<String> keys = p.stringPropertyNames();
+        List<ConfigItem> list = new LinkedList<>();
+        for (String key : keys) {
+            ConfigItem item = new ConfigItem();
+            item.setKey(key);
+            item.setValue(p.getProperty(key));
+            list.add(item);
+        }
+        return list;
+    }
+}

+ 8 - 13
src/main/java/com/qmth/ops/biz/utils/VersionNumber.java

@@ -13,19 +13,14 @@ public class VersionNumber {
 
     private int sub;
 
-    public static VersionNumber parse(String content) {
-        try {
-            Matcher m = PATTERN.matcher(content);
-            if (m.find()) {
-                VersionNumber number = new VersionNumber();
-                number.main = Integer.parseInt(m.group(1));
-                number.middle = Integer.parseInt(m.group(2));
-                number.sub = Integer.parseInt(m.group(3));
-                return number;
-            }
-            return null;
-        } catch (Exception e) {
-            return null;
+    public VersionNumber(String content) {
+        Matcher m = PATTERN.matcher(content);
+        if (m.find()) {
+            main = Integer.parseInt(m.group(1));
+            middle = Integer.parseInt(m.group(2));
+            sub = Integer.parseInt(m.group(3));
+        } else {
+            throw new IllegalArgumentException("版本号格式错误");
         }
     }