Explorar el Código

为后续扩展统一config更名为property

luoshi hace 2 años
padre
commit
c169773952

+ 17 - 17
src/main/java/com/qmth/ops/api/controller/admin/ConfigController.java → src/main/java/com/qmth/ops/api/controller/admin/PropertyController.java

@@ -18,14 +18,14 @@ import java.util.Arrays;
 import java.util.List;
 
 @RestController
-@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX + "/config")
-public class ConfigController {
+@RequestMapping(OpsApiConstants.ADMIN_URI_PREFIX + "/property")
+public class PropertyController {
 
     @Resource
-    private ConfigGroupService configGroupService;
+    private PropertyGroupService propertyGroupService;
 
     @Resource
-    private ConfigService configService;
+    private PropertyService propertyService;
 
     @Resource
     private AppService appService;
@@ -46,22 +46,22 @@ public class ConfigController {
 
     @RequestMapping("/groups")
     @Aac(auth = BOOL.FALSE)
-    public List<ConfigGroup> listGroups() {
-        return configGroupService.getConfigGroupList();
+    public List<PropertyGroup> listGroups() {
+        return propertyGroupService.getPropertyGroupList();
     }
 
     @RequestMapping("/modes")
     @Aac(auth = BOOL.FALSE)
     public Object listModes() {
-        return Arrays.stream(ConfigMode.values()).map(model -> new CodeNameBean(model.getCode(), model.getName()))
+        return Arrays.stream(PropertyMode.values()).map(model -> new CodeNameBean(model.getCode(), model.getName()))
                 .toArray();
     }
 
     @PostMapping("/baseline")
-    public List<ConfigItem> listBaseline(@RequestAttribute AdminSession adminSession, @RequestParam Long appId,
+    public List<PropertyItem> listBaseline(@RequestAttribute AdminSession adminSession, @RequestParam Long appId,
             @RequestParam Long versionId, @RequestParam Long moduleId) {
         adminSession.validateApp(appService.getById(appId));
-        return configService.listBaseline(appId, versionId, moduleId);
+        return propertyService.listBaseline(appId, versionId, moduleId);
     }
 
     @PostMapping("/baseline/update")
@@ -71,27 +71,27 @@ public class ConfigController {
             throws IOException {
         adminSession.validateRole(Role.DEV);
         adminSession.validateApp(appService.getById(appId));
-        return configService.updateBaseline(appService.getById(appId), versionService.getById(versionId),
+        return propertyService.updateBaseline(appService.getById(appId), versionService.getById(versionId),
                 moduleService.getById(moduleId), file.getInputStream(), extension,
                 inheritVersionId != null ? versionService.getById(inheritVersionId) : null);
     }
 
     @PostMapping("/baseline/item/update")
-    public ConfigItem updateBaselineItem(@RequestAttribute AdminSession adminSession, ConfigItem item) {
+    public PropertyItem updateBaselineItem(@RequestAttribute AdminSession adminSession, PropertyItem item) {
         adminSession.validateRole(Role.DEV);
         adminSession.validateApp(appService.getById(item.getAppId()));
-        return configService.updateBaselineItem(item);
+        return propertyService.updateBaselineItem(item);
     }
 
     @PostMapping("/list")
-    public List<ConfigItem> listConfigItem(@RequestAttribute AdminSession adminSession, @RequestParam Long appId,
+    public List<PropertyItem> listPropertyItem(@RequestAttribute AdminSession adminSession, @RequestParam Long appId,
             @RequestParam Long versionId, @RequestParam Long moduleId, @RequestParam Long envId) {
         adminSession.validateApp(appService.getById(appId));
         Env env = envService.getById(envId);
-        List<ConfigItem> list = configService.listConfigItem(appId, versionId, moduleId, env.getId());
+        List<PropertyItem> list = propertyService.listPropertyItem(appId, versionId, moduleId, env.getId());
         //非环境可编辑用户,需要隐藏机密信息
         if (!adminSession.getUser().hasRole(env.getType().getRole())) {
-            for (ConfigItem item : list) {
+            for (PropertyItem item : list) {
                 if (item.getKey().contains("secret") || item.getKey().contains("password")) {
                     //直接引用其他变量时不隐藏
                     if (!(item.getValue().startsWith("${") && item.getValue().endsWith("}"))) {
@@ -104,11 +104,11 @@ public class ConfigController {
     }
 
     @PostMapping("/item/update")
-    public ConfigItem updateConfigItem(@RequestAttribute AdminSession adminSession, ConfigItem item) {
+    public PropertyItem updatePropertyItem(@RequestAttribute AdminSession adminSession, PropertyItem item) {
         adminSession.validateRole(Role.TEST, Role.OPS);
         adminSession.validateApp(appService.getById(item.getAppId()));
         adminSession.validateEnv(envService.getById(item.getEnvId()).getType());
-        return configService.updateConfigItem(item);
+        return propertyService.updatePropertyItem(item);
     }
 
 }

+ 14 - 24
src/main/java/com/qmth/ops/api/controller/export/ConfigExportController.java → src/main/java/com/qmth/ops/api/controller/export/PropertyExportController.java

@@ -1,6 +1,5 @@
 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;
@@ -19,12 +18,11 @@ 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")
+@RequestMapping(OpsApiConstants.EXPORT_URI_PREFIX + "/property")
 @Aac(auth = BOOL.FALSE)
-public class ConfigExportController {
+public class PropertyExportController {
 
     @Resource
     private UserService userService;
@@ -42,7 +40,7 @@ public class ConfigExportController {
     private VersionService versionService;
 
     @Resource
-    private ConfigService configService;
+    private PropertyService propertyService;
 
     @InitBinder
     public void initBinder(WebDataBinder dataBinder) {
@@ -50,15 +48,15 @@ public class ConfigExportController {
     }
 
     @RequestMapping("/{appCode}/{moduleCode}/{envCode}/{branchName}")
-    public void exportVersionConfigFile(@PathVariable String appCode, @PathVariable String moduleCode,
+    public void exportPropertyFile(@PathVariable String appCode, @PathVariable String moduleCode,
             @PathVariable String envCode, @PathVariable String branchName, @RequestParam String secret,
             HttpServletResponse response) throws IOException {
-        exportConfigFile(appCode, moduleCode, envCode, new BranchName(branchName).getVersionNumber(), secret,
-                FileFormat.PROPERTY, response);
+        exportPropertyFile(appCode, moduleCode, envCode, new BranchName(branchName).getVersionNumber(), secret,
+                response);
     }
 
-    private void exportConfigFile(String appCode, String moduleCode, String envCode, VersionNumber versionNumber,
-            String exportSecret, FileFormat format, HttpServletResponse response) throws IOException {
+    private void exportPropertyFile(String appCode, String moduleCode, String envCode, VersionNumber versionNumber,
+            String exportSecret, HttpServletResponse response) throws IOException {
         User user = userService.findByExportSecret(exportSecret);
         if (user == null || !user.hasRole(Role.OPS)) {
             throw new UnauthorizedException("鉴权失败");
@@ -79,20 +77,12 @@ public class ConfigExportController {
         if (version == null) {
             throw new ParameterException("version不存在");
         }
-        List<ConfigItem> list = configService
-                .mergeConfigList(app.getId(), version.getId(), module.getId(), env.getId());
-        if (format == FileFormat.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 == FileFormat.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)));
-        }
+        List<PropertyItem> list = propertyService
+                .mergePropertyList(app.getId(), version.getId(), module.getId(), env.getId());
+        response.reset();
+        response.setContentType("application/octet-stream; charset=unicode");
+        response.setHeader("Content-Disposition", "attachment; filename=application.properties");
+        PropertyFileUtil.write(list, response.getOutputStream());
     }
 
 }

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

@@ -1,8 +0,0 @@
-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/PropertyItemDao.java

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

+ 5 - 5
src/main/java/com/qmth/ops/biz/domain/ConfigGroup.java → src/main/java/com/qmth/ops/biz/domain/PropertyGroup.java

@@ -2,13 +2,13 @@ package com.qmth.ops.biz.domain;
 
 import java.util.List;
 
-public class ConfigGroup {
+public class PropertyGroup {
 
     private String name;
 
     private String prefix;
 
-    private List<ConfigGroupItem> available;
+    private List<PropertyGroupItem> available;
 
     public String getName() {
         return name;
@@ -26,11 +26,11 @@ public class ConfigGroup {
         this.prefix = prefix;
     }
 
-    public List<ConfigGroupItem> getAvailable() {
+    public List<PropertyGroupItem> getAvailable() {
         return available;
     }
 
-    public void setAvailable(List<ConfigGroupItem> available) {
+    public void setAvailable(List<PropertyGroupItem> available) {
         this.available = available;
     }
 
@@ -38,7 +38,7 @@ public class ConfigGroup {
         if (key.startsWith(prefix)) {
             //有定义允许配置列表
             if (available != null && !available.isEmpty()) {
-                for (ConfigGroupItem groupItem : available) {
+                for (PropertyGroupItem groupItem : available) {
                     if (groupItem.accept(key, value)) {
                         return true;
                     }

+ 1 - 1
src/main/java/com/qmth/ops/biz/domain/ConfigGroupItem.java → src/main/java/com/qmth/ops/biz/domain/PropertyGroupItem.java

@@ -2,7 +2,7 @@ package com.qmth.ops.biz.domain;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 
-public class ConfigGroupItem {
+public class PropertyGroupItem {
 
     public static final String KEY_PART_SPLIT = "\\.";
 

+ 5 - 5
src/main/java/com/qmth/ops/biz/domain/ConfigItem.java → src/main/java/com/qmth/ops/biz/domain/PropertyItem.java

@@ -5,8 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableName;
 
 import java.io.Serializable;
 
-@TableName("config_item")
-public class ConfigItem implements Serializable {
+@TableName("property_item")
+public class PropertyItem implements Serializable {
 
     private static final long serialVersionUID = 1390416370953446620L;
 
@@ -25,7 +25,7 @@ public class ConfigItem implements Serializable {
 
     private String comment;
 
-    private ConfigMode mode;
+    private PropertyMode mode;
 
     private Long createTime;
 
@@ -87,11 +87,11 @@ public class ConfigItem implements Serializable {
         this.comment = comment;
     }
 
-    public ConfigMode getMode() {
+    public PropertyMode getMode() {
         return mode;
     }
 
-    public void setMode(ConfigMode mode) {
+    public void setMode(PropertyMode mode) {
         this.mode = mode;
     }
 

+ 2 - 2
src/main/java/com/qmth/ops/biz/domain/ConfigMode.java → src/main/java/com/qmth/ops/biz/domain/PropertyMode.java

@@ -1,12 +1,12 @@
 package com.qmth.ops.biz.domain;
 
-public enum ConfigMode {
+public enum PropertyMode {
 
     READONLY("只读"), MUTABLE("可变"), OVERRIDE("覆盖");
 
     private String name;
 
-    private ConfigMode(String name) {
+    PropertyMode(String name) {
         this.name = name;
     }
 

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

@@ -1,224 +0,0 @@
-package com.qmth.ops.biz.service;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-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.*;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-@Service
-public class ConfigService extends ServiceImpl<ConfigItemDao, ConfigItem> {
-
-    private static final long BASELINE_ENV_ID = 0L;
-
-    @Resource
-    private EnvService envService;
-
-    @Resource
-    private ConfigGroupService configGroupService;
-
-    @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, @NotNull FileFormat format, 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("指定模块不属于该应用");
-        }
-        if (format != FileFormat.PROPERTY) {
-            throw new ParameterException("暂不支持非properties类型文件");
-        }
-        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));
-        final 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());
-            } else {
-                item.setMode(ConfigMode.MUTABLE);
-            }
-        }
-        saveBatch(list);
-        List<ConfigItem> baseline = listBaseline(app.getId(), version.getId(), module.getId());
-        baseMap.clear();
-        baseline.forEach(item -> baseMap.put(item.getKey(), item));
-        List<Env> envList = envService.list(app.getId());
-        for (Env env : envList) {
-            resetEnvConfig(app, version, module, env, inheritVersion, baseMap);
-        }
-        return baseline;
-    }
-
-    /**
-     * 根据指定版本和最新基线,继承环境自定义配置
-     *
-     * @param app
-     * @param version
-     * @param module
-     * @param env
-     * @param inheritVersion
-     * @param baseMap
-     */
-    private void resetEnvConfig(App app, Version version, Module module, Env env, Version inheritVersion,
-            Map<String, ConfigItem> baseMap) {
-        configItemDao.delete(new LambdaUpdateWrapper<ConfigItem>().eq(ConfigItem::getAppId, app.getId())
-                .eq(ConfigItem::getVersionId, version.getId()).eq(ConfigItem::getModuleId, module.getId())
-                .eq(ConfigItem::getEnvId, env.getId()));
-        if (inheritVersion != null) {
-            long time = System.currentTimeMillis();
-            List<ConfigItem> inheritList = listConfigItem(app.getId(), inheritVersion.getId(), module.getId(),
-                    env.getId());
-            List<ConfigItem> saveList = new LinkedList<>();
-            for (ConfigItem item : inheritList) {
-                if (accept(item, baseMap)) {
-                    item.setVersionId(version.getId());
-                    item.setCreateTime(time);
-                    item.setUpdateTime(time);
-                    saveList.add(item);
-                }
-            }
-            saveBatch(saveList);
-        }
-    }
-
-    /**
-     * 继承版本的环境自定义配置,是否可以在当前版本保留
-     *
-     * @param item
-     * @return
-     */
-    private boolean accept(ConfigItem item, Map<String, ConfigItem> baseMap) {
-        //当前基线包含且非只读,可以保留
-        ConfigItem base = baseMap.get(item.getKey());
-        if (base != null && base.getMode() != ConfigMode.READONLY) {
-            return true;
-        }
-        //配置分组判断是否保留
-        if (configGroupService.accept(item.getKey(), item.getValue())) {
-            return true;
-        }
-        //应用自定义且不在基线内,不能保留
-        return false;
-    }
-
-    @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.setMode(ConfigMode.MUTABLE);
-            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;
-    }
-}
-

+ 14 - 14
src/main/java/com/qmth/ops/biz/service/ConfigGroupService.java → src/main/java/com/qmth/ops/biz/service/PropertyGroupService.java

@@ -2,7 +2,7 @@ package com.qmth.ops.biz.service;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.type.CollectionType;
-import com.qmth.ops.biz.domain.ConfigGroup;
+import com.qmth.ops.biz.domain.PropertyGroup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -12,36 +12,36 @@ import java.util.ArrayList;
 import java.util.List;
 
 @Service
-public class ConfigGroupService {
+public class PropertyGroupService {
 
-    private static final Logger log = LoggerFactory.getLogger(ConfigGroupService.class);
+    private static final Logger log = LoggerFactory.getLogger(PropertyGroupService.class);
 
-    private static String[] FILE_PATH = { "config/qmth-boot.json", "config/spring.json" };
+    private static String[] FILE_PATH = { "property/qmth-boot.json", "property/spring.json" };
 
-    private List<ConfigGroup> configGroupList = new ArrayList<>();
+    private List<PropertyGroup> propertyGroupList = new ArrayList<>();
 
-    public ConfigGroupService() throws IOException {
-        loadGroupFile(configGroupList, FILE_PATH);
+    public PropertyGroupService() throws IOException {
+        loadGroupFile(propertyGroupList, FILE_PATH);
     }
 
-    private void loadGroupFile(List<ConfigGroup> configGroupList, String... path) throws IOException {
+    private void loadGroupFile(List<PropertyGroup> propertyGroupList, String... path) throws IOException {
         ObjectMapper mapper = new ObjectMapper();
         CollectionType collectionType = mapper.getTypeFactory()
-                .constructCollectionType(ArrayList.class, ConfigGroup.class);
+                .constructCollectionType(ArrayList.class, PropertyGroup.class);
         for (String filePath : path) {
-            List<ConfigGroup> list = mapper
+            List<PropertyGroup> list = mapper
                     .readValue(this.getClass().getClassLoader().getResourceAsStream(filePath), collectionType);
             log.info(filePath + " size=" + list.size());
-            configGroupList.addAll(list);
+            propertyGroupList.addAll(list);
         }
     }
 
-    public List<ConfigGroup> getConfigGroupList() {
-        return configGroupList;
+    public List<PropertyGroup> getPropertyGroupList() {
+        return propertyGroupList;
     }
 
     public boolean accept(String key, String value) {
-        for (ConfigGroup group : configGroupList) {
+        for (PropertyGroup group : propertyGroupList) {
             if (group.accept(key, value)) {
                 return true;
             }

+ 225 - 0
src/main/java/com/qmth/ops/biz/service/PropertyService.java

@@ -0,0 +1,225 @@
+package com.qmth.ops.biz.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.boot.core.exception.ParameterException;
+import com.qmth.boot.core.exception.StatusException;
+import com.qmth.ops.biz.dao.PropertyItemDao;
+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.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Service
+public class PropertyService extends ServiceImpl<PropertyItemDao, PropertyItem> {
+
+    private static final long BASELINE_ENV_ID = 0L;
+
+    @Resource
+    private EnvService envService;
+
+    @Resource
+    private PropertyGroupService propertyGroupService;
+
+    @Resource
+    private PropertyItemDao propertyItemDao;
+
+    public List<PropertyItem> listBaseline(Long appId, Long versionId, Long moduleId) {
+        return propertyItemDao.selectList(new LambdaQueryWrapper<PropertyItem>().eq(PropertyItem::getAppId, appId)
+                .eq(PropertyItem::getVersionId, versionId).eq(PropertyItem::getModuleId, moduleId)
+                .eq(PropertyItem::getEnvId, BASELINE_ENV_ID).orderByAsc(PropertyItem::getKey));
+    }
+
+    @Transactional
+    public List<PropertyItem> updateBaseline(@NotNull App app, @NotNull Version version, @NotNull Module module,
+            @NotNull InputStream file, @NotNull FileFormat format, 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("指定模块不属于该应用");
+        }
+        if (format != FileFormat.PROPERTY) {
+            throw new ParameterException("暂不支持非properties类型文件");
+        }
+        long time = System.currentTimeMillis();
+        propertyItemDao.delete(new LambdaUpdateWrapper<PropertyItem>().eq(PropertyItem::getAppId, app.getId())
+                .eq(PropertyItem::getVersionId, version.getId()).eq(PropertyItem::getModuleId, module.getId())
+                .eq(PropertyItem::getEnvId, BASELINE_ENV_ID));
+        final Map<String, PropertyItem> baseMap = new HashMap<>();
+        if (inheritVersion != null) {
+            listBaseline(app.getId(), inheritVersion.getId(), module.getId())
+                    .forEach(item -> baseMap.put(item.getKey(), item));
+        }
+        List<PropertyItem> list = PropertyFileUtil.read(file);
+        for (PropertyItem 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);
+            PropertyItem base = baseMap.get(item.getKey());
+            if (base != null) {
+                item.setMode(base.getMode());
+                item.setComment(base.getComment());
+            } else {
+                item.setMode(PropertyMode.MUTABLE);
+            }
+        }
+        saveBatch(list);
+        List<PropertyItem> baseline = listBaseline(app.getId(), version.getId(), module.getId());
+        baseMap.clear();
+        baseline.forEach(item -> baseMap.put(item.getKey(), item));
+        List<Env> envList = envService.list(app.getId());
+        for (Env env : envList) {
+            resetEnvProperty(app, version, module, env, inheritVersion, baseMap);
+        }
+        return baseline;
+    }
+
+    /**
+     * 根据指定版本和最新基线,继承环境自定义配置
+     *
+     * @param app
+     * @param version
+     * @param module
+     * @param env
+     * @param inheritVersion
+     * @param baseMap
+     */
+    private void resetEnvProperty(App app, Version version, Module module, Env env, Version inheritVersion,
+            Map<String, PropertyItem> baseMap) {
+        propertyItemDao.delete(new LambdaUpdateWrapper<PropertyItem>().eq(PropertyItem::getAppId, app.getId())
+                .eq(PropertyItem::getVersionId, version.getId()).eq(PropertyItem::getModuleId, module.getId())
+                .eq(PropertyItem::getEnvId, env.getId()));
+        if (inheritVersion != null) {
+            long time = System.currentTimeMillis();
+            List<PropertyItem> inheritList = listPropertyItem(app.getId(), inheritVersion.getId(), module.getId(),
+                    env.getId());
+            List<PropertyItem> saveList = new LinkedList<>();
+            for (PropertyItem item : inheritList) {
+                if (accept(item, baseMap)) {
+                    item.setVersionId(version.getId());
+                    item.setCreateTime(time);
+                    item.setUpdateTime(time);
+                    saveList.add(item);
+                }
+            }
+            saveBatch(saveList);
+        }
+    }
+
+    /**
+     * 继承版本的环境自定义配置,是否可以在当前版本保留
+     *
+     * @param item
+     * @return
+     */
+    private boolean accept(PropertyItem item, Map<String, PropertyItem> baseMap) {
+        //当前基线包含且非只读,可以保留
+        PropertyItem base = baseMap.get(item.getKey());
+        if (base != null && base.getMode() != PropertyMode.READONLY) {
+            return true;
+        }
+        //配置分组判断是否保留
+        if (propertyGroupService.accept(item.getKey(), item.getValue())) {
+            return true;
+        }
+        //应用自定义且不在基线内,不能保留
+        return false;
+    }
+
+    @Transactional
+    public PropertyItem updateBaselineItem(PropertyItem item) {
+        propertyItemDao.update(item, new LambdaUpdateWrapper<PropertyItem>()
+                .set(item.getMode() != null, PropertyItem::getMode, item.getMode())
+                .set(item.getComment() != null, PropertyItem::getComment, item.getComment())
+                .eq(PropertyItem::getAppId, item.getAppId()).eq(PropertyItem::getVersionId, item.getVersionId())
+                .eq(PropertyItem::getModuleId, item.getModuleId()).eq(PropertyItem::getEnvId, BASELINE_ENV_ID)
+                .eq(PropertyItem::getKey, item.getKey()));
+        return findOne(item.getAppId(), item.getVersionId(), item.getModuleId(), BASELINE_ENV_ID, item.getKey());
+    }
+
+    public List<PropertyItem> listPropertyItem(Long appId, Long versionId, Long moduleId, Long envId) {
+        return propertyItemDao.selectList(new LambdaQueryWrapper<PropertyItem>().eq(PropertyItem::getAppId, appId)
+                .eq(PropertyItem::getVersionId, versionId).eq(PropertyItem::getModuleId, moduleId)
+                .eq(PropertyItem::getEnvId, envId).orderByAsc(PropertyItem::getKey));
+    }
+
+    @Transactional
+    public PropertyItem updatePropertyItem(PropertyItem item) {
+        PropertyItem base = findOne(item.getAppId(), item.getVersionId(), item.getModuleId(), BASELINE_ENV_ID,
+                item.getKey());
+        PropertyItem previous = findOne(item.getAppId(), item.getVersionId(), item.getModuleId(), item.getEnvId(),
+                item.getKey());
+        if (base != null && base.getMode() == PropertyMode.READONLY) {
+            throw new ParameterException("配置项只读");
+        }
+        if (item.getValue() == null) {
+            throw new ParameterException("配置值不能为空");
+        }
+        if (previous != null) {
+            propertyItemDao.update(previous,
+                    new LambdaUpdateWrapper<PropertyItem>().set(PropertyItem::getValue, item.getValue())
+                            .set(item.getComment() != null, PropertyItem::getComment, item.getComment())
+                            .set(PropertyItem::getUpdateTime, System.currentTimeMillis())
+                            .eq(PropertyItem::getAppId, item.getAppId())
+                            .eq(PropertyItem::getVersionId, item.getVersionId())
+                            .eq(PropertyItem::getModuleId, item.getModuleId())
+                            .eq(PropertyItem::getEnvId, item.getEnvId()).eq(PropertyItem::getKey, item.getKey()));
+            return findOne(item.getAppId(), item.getVersionId(), item.getModuleId(), item.getEnvId(), item.getKey());
+        } else {
+            item.setMode(PropertyMode.MUTABLE);
+            item.setCreateTime(System.currentTimeMillis());
+            item.setUpdateTime(item.getCreateTime());
+            propertyItemDao.insert(item);
+            return item;
+        }
+    }
+
+    public PropertyItem findOne(Long appId, Long versionId, Long moduleId, Long envId, String key) {
+        return propertyItemDao.selectOne(new LambdaQueryWrapper<PropertyItem>().eq(PropertyItem::getAppId, appId)
+                .eq(PropertyItem::getVersionId, versionId).eq(PropertyItem::getModuleId, moduleId)
+                .eq(PropertyItem::getEnvId, envId).eq(PropertyItem::getKey, key));
+    }
+
+    public List<PropertyItem> mergePropertyList(Long appId, Long versionId, Long moduleId, Long envId) {
+        List<PropertyItem> list = listBaseline(appId, versionId, moduleId);
+        //获取环境定义配置项
+        Map<String, PropertyItem> itemMap = listPropertyItem(appId, versionId, moduleId, envId).stream()
+                .collect(Collectors.toMap(PropertyItem::getKey, Function.identity()));
+        //遍历基线
+        for (PropertyItem item : list) {
+            PropertyItem update = itemMap.get(item.getKey());
+            //非只读配置项更新
+            if (update != null && item.getMode() != PropertyMode.READONLY) {
+                item.setValue(update.getValue());
+            }
+            //需要覆盖的配置项不能校验
+            else if (update == null && item.getMode() == PropertyMode.OVERRIDE) {
+                throw new StatusException("配置项需要覆盖新值:" + item.getKey());
+            }
+            itemMap.remove(item.getKey());
+        }
+        //合并新增配置项
+        if (!itemMap.isEmpty()) {
+            list.addAll(itemMap.values());
+            list.sort(Comparator.comparing(PropertyItem::getKey));
+        }
+        return list;
+    }
+}
+

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

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

+ 0 - 0
src/main/resources/config/qmth-boot.json → src/main/resources/property/qmth-boot.json


+ 0 - 0
src/main/resources/config/spring.json → src/main/resources/property/spring.json


+ 2 - 2
src/main/resources/script/init.sql

@@ -21,8 +21,8 @@ CREATE TABLE `app_user`
 ) ENGINE = InnoDB
   DEFAULT CHARSET = utf8mb4;
 
--- Create syntax for TABLE 'config_item'
-CREATE TABLE `config_item`
+-- Create syntax for TABLE 'property_item'
+CREATE TABLE `property_item`
 (
     `app_id`      bigint(20) unsigned NOT NULL,
     `version_id`  bigint(20) unsigned NOT NULL,