Java 一种通用配置业务设计方式

业务背景

在实际项目中,经常有关于配置的存储,将某个人的配置信息,或者某个公司的配置信息、某个系统的配置信息,存入库表记录中的需求。

不通用配置设计方式

一般的做法是建一张表,字段有:用户或系统/公司的主键id,具体的配置名称:比如是否给该用户推送消息,该用户使用的语言,该公司/系统是否采用夏令时,该公司/系统的下载文件路径等等,每种配置的类型(整型,枚举,布尔,字符串等等)不同,需要校验的方式也可能会不同。
表结构大致可能如下:

CREATE TABLE `user_config` (
    `id` VARCHAR(64) NOT NULL COMMENT '主键id',
    `account_id` VARCHAR(64) NOT NULL COMMENT '用户id',
    `push_enable` BIT(1) NULL DEFAULT b'1' COMMENT '是否推送消息',
    `language` INT(8) NOT NULL DEFAULT '0' COMMENT '语言类型,0-英语,1-中文,2-葡萄牙语',
    PRIMARY KEY (`id`),
    KEY `idx_account_id` (`account_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息配置表';

CREATE TABLE `company_config` (
    `id` VARCHAR(64) NOT NULL COMMENT '主键id',
    `company_id` VARCHAR(64) NOT NULL COMMENT '公司id',
    `summer_time_enable` BIT(1) NULL DEFAULT b'0' COMMENT '是否开启夏令时',
    `download_path` VARCHAR(256) NOT NULL DEFAULT 'D:/' COMMENT '下载路径',
    PRIMARY KEY (`id`),
    KEY `idx_company_id` (`company_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司信息配置表';

这样设计的话,优势是简洁易懂。劣势是后续再有新的配置,表中需要增加新的字段,代码也相应要修改。

通用配置设计方式

本文介绍的是通用的配置设计方式,有一张配置字典表,字典表中记录各项配置的信息,另一张是用户配置信息表,记录着某个用户或某个公司的某项配置的值。
字典表的内容可以按照实际的业务增加更通用的字段,比如记录日志,是否需要权限校验等
表结构大致如下:

CREATE TABLE `config_dic` (
  `id` varchar(64) NOT NULL COMMENT '主键id',
  `config_key` varchar(32) NOT NULL COMMENT '配置名称,不能重复,唯一确定一项配置',
  `config_type` varchar(8) NOT NULL COMMENT '配置类型',
  `config_tip` varchar(128) DEFAULT NULL COMMENT '配置说明',  
  `default_value` varchar(256) NOT NULL COMMENT '配置的默认值',
  `check_rule` varchar(256) DEFAULT NULL COMMENT '校验配置项值的规则',  
  `permission_enable` tinyint(4) DEFAULT '0' COMMENT '是否要校验权限',  
  `permission_value` varchar(256) DEFAULT NULL COMMENT '权限具体匹配的内容',  
  `operation_log` varchar(128) DEFAULT NULL COMMENT '不为空代表需要按该字段内容记录日志',
  `create_time` bigint(20) unsigned NOT NULL COMMENT '创建时间(单位:毫秒)',
  `update_time` BIGINT(20) DEFAULT NULL COMMENT '更新时间(单位:毫秒)',
  PRIMARY KEY (`id`)
 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配置字典表';


CREATE TABLE `config_info` (
    `id` varchar(64) NOT NULL COMMENT '主键id',
    `user_id` varchar(64) DEFAULT NULL COMMENT '用户或公司的id',
    `config_value` varchar(256) NOT NULL COMMENT '配置值',
    `config_id` varchar(64) NOT NULL COMMENT 'config_dic表主键id',
    `create_time` BIGINT(20) unsigned NOT NULL COMMENT '创建时间(单位:毫秒)',
    `update_time` BIGINT(20) unsigned NOT NULL COMMENT '更新时间(单位:毫秒)',
    PRIMARY KEY (`id`),
	KEY `idx_ccftenant_config_info_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户配置信息表';

这样设计的好处是,后面再拓展新的配置时,只需要将新的配置项插入到字典表中,无须改动代码。

数据示例

在这里插入图片描述
在这里插入图片描述

代码逻辑实现

数据库Entity类

@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "config_dic")
public class ConfigDic {

    @Id
    @Column(name = "id", columnDefinition = "varchar(64)")
    @GenericGenerator(name="idGenerator", strategy="uuid")
    @GeneratedValue(generator = "idGenerator")
    private String id;

    @Column(name = "config_key", nullable = false, columnDefinition = "配置名称,不能重复,唯一确定一项配置")
    private String configKey;

    @Column(name = "config_type", nullable = false, columnDefinition = "配置类型")
    private String configType;

    @Column(name = "config_tip", columnDefinition = "配置说明")
    private String configTip;

    @Column(name = "default_value", nullable = false, columnDefinition = "配置的默认值")
    private String defaultValue;

    @Column(name = "check_rule", columnDefinition = "校验配置项值的规则")
    private String checkRule;

    @Column(name = "permission_enable", nullable = false, columnDefinition = "是否要校验权限")
    private Boolean permissionEnable;

    @Column(name = "permission_value", columnDefinition = "权限具体匹配的内容")
    private String permissionValue;

    @Column(name = "operation_log", columnDefinition = "不为空代表需要按该字段内容记录日志")
    private String operationLog;

    @Column(name = "create_time", columnDefinition = "创建时间戳")
    private Long createTime;

    @Column(name = "update_time", columnDefinition = "更新时间戳")
    private Long updateTime;
}
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "config_info")
public class ConfigInfo {

    @Id
    @Column(name = "id", columnDefinition = "varchar(64)")
    @GenericGenerator(name="idGenerator", strategy="uuid")
    @GeneratedValue(generator = "idGenerator")
    private String id;

    @Column(name = "user_id", columnDefinition = "用户或公司的id")
    private String userId;

    @Column(name = "config_value", columnDefinition = "配置值")
    private String configValue;

    @Column(name = "config_id", columnDefinition = "ccftenant_config_dic表主键")
    private String configId;

    @Column(name = "create_time", columnDefinition = "创建时间")
    private Long createTime;

    @Column(name = "update_time", columnDefinition = "更新时间")
    private Long updateTime;
}

DAO层

@Repository
public interface ConfigDicRepository extends JpaRepository<ConfigDic, Serializable> {

    List<ConfigDic> findAllByConfigKeyIn(List<String> configKeys);
}
@Repository
public interface ConfigInfoRepository extends JpaRepository<ConfigInfo, Serializable> {
    List<ConfigInfo> findAllByUserIdAndConfigIdIn(String userId, List<String> configIds);
}

service层

public interface ConfigService {

    Map<String, Object> getConfigInfo(String userId, List<String> configKey);

    void setConfigInfo(String userId, Map<String, Object> map);

}
@Service
public class ConfigServiceImpl implements ConfigService {

    @Resource
    private ConfigDicRepository configDicRepository;

    @Resource
    private ConfigInfoRepository configInfoRepository;

    @Override
    public Map<String, Object> getConfigInfo(String userId, List<String> configKey) {
        Map<String, Object> res = new HashMap<>();
        if (CollectionUtils.isEmpty(configKey)) {
            return res;
        }
        // 查出指定配置字典表中数据
        List<ConfigDic> configDics = configDicRepository.findAllByConfigKeyIn(configKey);
        if (CollectionUtils.isEmpty(configDics)) {
            return res;
        }
        // 查出用户配置表中,指定配置项+userid的配置
        List<String> ids = configDics.stream().map(ConfigDic::getId).collect(Collectors.toList());
        List<ConfigInfo> configInfos = configInfoRepository.findAllByUserIdAndConfigIdIn(userId, ids);
        // 遍历配置,若用户配置表中存在,取用户配置表中值,否则使用默认值返回
        Map<String, ConfigInfo> collect = configInfos.stream().collect(Collectors.toMap(ConfigInfo::getConfigId, a -> a, (k1, k2) -> k1));
        configDics.forEach(c -> {
            String value;
            ConfigInfo configInfo = collect.get(c.getId());
            if (configInfo != null) {
                value = configInfo.getConfigValue();
            } else {
                value = c.getDefaultValue();
            }
            // 转回指定类型
            Object targetValue = this.getTargetValue(value, c.getConfigType());
            res.put(c.getConfigKey(), targetValue);
        });
        return res;
    }

    @Override
    public void setConfigInfo(String userId, Map<String, Object> map) {
        if (map == null || map.isEmpty()) {
            return;
        }
        Set<String> keySet = map.keySet();
        List<String> configKey = new ArrayList(keySet);
        // 查出指定配置字典表中数据
        List<ConfigDic> configDics = configDicRepository.findAllByConfigKeyIn(configKey);
        if (CollectionUtils.isEmpty(configDics)) {
            return;
        }
        // 查出用户配置表中,指定配置项+userid的配置
        List<String> ids = configDics.stream().map(ConfigDic::getId).collect(Collectors.toList());
        List<ConfigInfo> configInfos = configInfoRepository.findAllByUserIdAndConfigIdIn(userId, ids);
        // 遍历配置,进行校验,校验通过后,若用户配置表中存在,更新用户配置表中值,否则用户配置表中插入一条新数据
        Map<String, ConfigInfo> collect = configInfos.stream().collect(Collectors.toMap(ConfigInfo::getConfigId, a -> a, (k1, k2) -> k1));
        List<ConfigInfo> saveList = new ArrayList<>();
        configDics.forEach(c -> {
            if (c.getPermissionEnable()) {
                // 校验权限,代码略
            }
            if (c.getOperationLog() != null) {
                // 打印日志,代码略
            }
            // 数据类型校验
            if (!this.checkDataType(map.get(c.getConfigKey()), c.getConfigType())) {
                return;
            }
            // 数据规则校验
            if (!this.checkDataRule(map.get(c.getConfigKey()), c.getConfigType(), c.getCheckRule())) {
                return;
            }
            String saveValue = map.get(c.getConfigKey()).toString();
            ConfigInfo configInfo = collect.get(c.getId());
            if (configInfo != null) {
                configInfo.setConfigValue(saveValue);
                configInfo.setUpdateTime(System.currentTimeMillis());
            } else {
                configInfo = ConfigInfo.builder().userId(userId).configId(c.getId()).configValue(saveValue).createTime(System.currentTimeMillis()).updateTime(System.currentTimeMillis()).build();
            }
            saveList.add(configInfo);
        });
        configInfoRepository.saveAll(saveList);
    }

    private boolean checkDataType(Object value, String type) {
        if (ObjectUtil.isNull(value) || ObjectUtil.isNull(type)) {
            return false;
        }
        String valueType = this.getValueType(value);
        return valueType.equals(type);
    }

    private Object getTargetValue(String value, String type) {
        boolean nullOrEmpty = StringUtils.isEmpty(value);
        if (Double.class.getSimpleName().equals(type)) {
            return nullOrEmpty ? 0 : Double.parseDouble(value);
        } else if (Long.class.getSimpleName().equals(type)) {
            return nullOrEmpty ? 0 : Long.parseLong(value);
        } else if (Integer.class.getSimpleName().equals(type)) {
            return nullOrEmpty ? 0 : Integer.parseInt(value);
        } else if (Float.class.getSimpleName().equals(type)) {
            return nullOrEmpty ? 0 : Float.parseFloat(value);
        } else if (Short.class.getSimpleName().equals(type)) {
            return nullOrEmpty ? 0 : Short.parseShort(value);
        } else if (Boolean.class.getSimpleName().equals(type)) {
            return nullOrEmpty ? 0 : Boolean.parseBoolean(value);
        } else {
            return nullOrEmpty ? "" : value;
        }
    }

    public static String getValueType(Object value) {
        if (ObjectUtil.isNull(value)) {
            return String.class.getSimpleName();
        }

        if (value instanceof Double) {
            return Double.class.getSimpleName();
        } else if (value instanceof Long) {
            return Long.class.getSimpleName();
        } else if (value instanceof Integer) {
            return Integer.class.getSimpleName();
        } else if (value instanceof Float) {
            return Float.class.getSimpleName();
        } else if (value instanceof Short) {
            return Short.class.getSimpleName();
        } else if (value instanceof Boolean) {
            return Boolean.class.getSimpleName();
        } else {
            return String.class.getSimpleName();
        }
    }

    public boolean checkRule(Object value, String valueType, String rule) {
        if (StringUtils.isBlank(rule)) {
            return true;
        }
        Rule ruleObj = JSON.parseObject(rule, Rule.class);
        if (ObjectUtil.isNull(ruleObj)) {
            return false;
        }
        if (Double.class.getSimpleName().equals(valueType)) {
            Double v = (Double) value;
            if (ObjectUtil.isNotNull(ruleObj.getMin()) && v < ruleObj.getMin()) {
                return false;
            }
            if (ObjectUtil.isNotNull(ruleObj.getMax()) && v > ruleObj.getMax()) {
                return false;
            }
        } else if (Long.class.getSimpleName().equals(valueType)) {
            Long v = (Long) value;
            if (ObjectUtil.isNotNull(ruleObj.getMin()) && v < ruleObj.getMin()) {
                return false;
            }
            if (ObjectUtil.isNotNull(ruleObj.getMax()) && v > ruleObj.getMax()) {
                return false;
            }
        } else if (Integer.class.getSimpleName().equals(valueType)) {
            Integer v = (Integer) value;
            if (ObjectUtil.isNotNull(ruleObj.getMin()) && v < ruleObj.getMin()) {
                return false;
            }
            if (ObjectUtil.isNotNull(ruleObj.getMax()) && v > ruleObj.getMax()) {
                return false;
            }
            if (ObjectUtil.isNotNull(ruleObj.getRange()) && !ruleObj.getRange().contains(v)) {
                return false;
            }
        } else if (Float.class.getSimpleName().equals(valueType)) {
            Float v = (Float) value;
            if (ObjectUtil.isNotNull(ruleObj.getMin()) && v < ruleObj.getMin()) {
                return false;
            }
            if (ObjectUtil.isNotNull(ruleObj.getMax()) && v > ruleObj.getMax()) {
                return false;
            }
        } else if (Short.class.getSimpleName().equals(valueType)) {
            Short v = (Short) value;
            if (ObjectUtil.isNotNull(ruleObj.getMin()) && v < ruleObj.getMin()) {
                return false;
            }
            if (ObjectUtil.isNotNull(ruleObj.getMax()) && v > ruleObj.getMax()) {
                return false;
            }
            if (ObjectUtil.isNotNull(ruleObj.getRange()) && !ruleObj.getRange().contains(v)) {
                return false;
            }
        } else if (String.class.getSimpleName().equals(valueType)) {
            String v = (String) value;
            if (ObjectUtil.isNotNull(ruleObj.getLength()) && v.length() > ruleObj.getLength()) {
                return false;
            }
        }
        return true;
    }

    private boolean checkDataRule(Object value, String type, String rule) {
        if (ObjectUtil.isNull(value) || ObjectUtil.isNull(type)) {
            return false;
        }
        if (StringUtils.isBlank(rule)) {
            return true;
        }
        return this.checkRule(value, type, rule);
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class Rule {
        private Long min;
        private Long max;
        private List<Integer> range;
        private Integer length;
    }
}

controller层

@RestController
@RequestMapping("/config")
public class ConfigController {
    @Resource
    private ConfigService configService;

    @PostMapping(value = "/v1/get")
    public Map<String, Object> getConfigInfo(@RequestBody GetConfigVo getConfigVo, BindingResult result) {
        return configService.getConfigInfo(getConfigVo.getUserId(), getConfigVo.getList());
    }

    @PostMapping(value = "/v1/set")
    public void setConfigInfo(@RequestBody SetConfigVo setConfigVo, BindingResult result) {
        configService.setConfigInfo(setConfigVo.getUserId(), setConfigVo.getMap());
    }
}

测试结果

运行程序,使用postman调接口进行测试

获取配置

在这里插入图片描述
在这里插入图片描述

设置配置

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Spring AOP (Aspect-Oriented Programming) 是 Spring 框架中的一个重要特性。它允许你通过将横切关注点(cross-cutting concerns)与程序的核心业务逻辑分离开来,以一种模块化的方式来管理和处理它们。 AOP 提供了一种在程序运行时动态地将通用功能(例如日志记录、性能计数、事务管理等)应用到应用程序中各个模块的方法上的方法。通过使用 Spring AOP,你可以将这些通用功能从核心业务逻辑中解耦出来,以便更好地维护和理解代码。 在 Spring AOP 中,你可以通过使用切点(pointcut)和通知(advice)来定义横切关注点。切点指定了在应用程序中哪些方法需要被拦截,而通知则定义了在拦截点(join point)上执行的操作。 Spring AOP 支持多种类型的通知,包括前置通知(Before advice)、后置通知(After advice)、返回通知(After-returning advice)、异常通知(After-throwing advice)和环绕通知(Around advice)。这些通知可以在方法执行前、后或周围被触发,以便执行与横切关注点相关的行为。 通过配置 Spring AOP,你可以将切点和通知与目标对象关联起来,并在运行时自动应用这些通知。这样,你就能够以一种声明式的方式管理和组织应用程序中的横切关注点,而无需修改核心业务逻辑。 总括来说,Java Spring AOP 是 Spring 框架中的一个功能强大的特性,它通过将横切关注点与核心业务逻辑解耦,提供了一种模块化和可维护的方式来处理通用功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值