springboot策略模式就该这么写

这篇文章是在阅读源码时,看到的策略模式实现思路,方便队友们参考学习,提升代码设计能力.

背景

验证密码的合规性,需要根据可选的密码验证策略,如:最小长度,最大长度,数字个数,大写字母,小写字母…,而且还是需要链式调用验证器来对密码进行合规性验证.这个场景的实现就联想到策略模式和责任链模式的代码设计思路来实现.

这样设计的优点

1.策略可支持扩展,只需要新增接口实现类,而不用修改模块任何代码
2.与spring的容器上下文集成,无需管理实现类的增加和删除,由容器托管.
3.无需为策略实例命名,策略名和策略类实例进行map绑定,我们通过策略名获取实例,避免给实例命名带来的错误风险.
4.策略器实现存储在Map中,可以很方便的建立责任链模式,实现链式调用,而无需自己建链.如果需要对责任链的bean实现排序,则让对应策略类实现Order接口或者增加@Order注解.

接口及实现类
//密码策略验证的名称枚举
public enum PasswordPolicyType {
    MIN_LENGTH("minLength"),
    MAX_LENGTH("maxLength"),
    DIGITS_COUNT("digitsCount"),
    LOWERCASE_COUNT("lowercaseCount"),
    UPPERCASE_COUNT("uppercaseCount"),
    SPECIALCHAR_COUNT("specialCharCount"),
    NOT_USERNAME("notUsername"),
    NOT_RECENT("notRecent"),
    REGULAR("regular"),
    MAX_ERROR_TIME("maxErrorTime"),
    ENABLE_CAPTCHA("enableCaptcha"),
    MAX_CHECK_CAPTCHA("maxCheckCaptcha"),
    ENABLE_LOCK("enableLock"),
    LOCK_EXPIRE_TIME("lockExpireTime");

    private String value;

    PasswordPolicyType(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

//策略器抽象接口
public interface PasswordStrategy {
    //验证密码
    <T> T validate(PasswordPolicyMap policyMap, BaseUser user, String password);
    //策略名称
    String getType();
}

//策略器的实现-策略名称:密码中至少需要包含的数字个数
@Component
public class DigitsCountStrategy implements PasswordStrategy {
    private static final String ERROR_MESSAGE = "hoth.warn.password.policy.digits";
    public static final String TYPE = PasswordPolicyType.DIGITS_COUNT.getValue();

    @Override
    public Object validate(PasswordPolicyMap policyMap, BaseUser user, String password) {
        Integer min = (Integer) policyMap.getPasswordConfig().get(TYPE);
        if (min != null && min != 0) {
            int count = 0;
            for (char c : password.toCharArray()) {
                if (Character.isDigit(c)) {
                    count++;
                }
            }
            if (count < min) {
                throw new CommonException(ERROR_MESSAGE, min);
            }
        }
        return null;
    }

    @Override
    public String getType() {
        return TYPE;
    }
}

/**
策略器构建工厂,容器初始化时,将策略名称与策略实现绑定
*/
@Component
public class PasswordStrategyStore {

    private final Map<String, PasswordStrategy> strategyMap = new HashMap<>();

    private ApplicationContext context;

    public PasswordStrategyStore(ApplicationContext context) {
        this.context = context;
    }

    @PostConstruct
    public void init() {
        Map<String, PasswordStrategy> passwordStrategyMap = context.getBeansOfType(PasswordStrategy.class);
        for (Map.Entry<String, PasswordStrategy> entry : passwordStrategyMap.entrySet()) {
            String type = entry.getValue().getType();
            strategyMap.put(type, entry.getValue());
        }
    }

    public Map<String, PasswordStrategy> getStrategyMap() {
        return strategyMap;
    }
    //根据策略名称获取策略实现
    public PasswordStrategy getProvider(String key) {
        return strategyMap.get(key);
    }

}

//策略器建立链路
private List<PasswordStrategy> getPasswordProviders(PasswordPolicyMap policy, PasswordStrategyStore store) {
        LinkedList<PasswordStrategy> list = new LinkedList<>();
        for (String id : policy.getPasswordPolicies()) {
            PasswordStrategy provider = store.getProvider(id);
            if (provider != null) {
                list.add(provider);
            }
        }
        return list;
}
//密码校验,责任链模式
public void passwordValidate(String password, Long tenantId, BaseUser baseUser) {
        if (password == null) {
            throw new CommonException(ERROR_EMPTY);
        }
        BasePasswordPolicy passwordPolicy = basePasswordPolicyRepository.selectPasswordPolicy(tenantId);
        baseUser = Optional.ofNullable(baseUser).orElse(new BaseUser(null, null, tenantId));
        PasswordPolicyMap passwordPolicyMap = PasswordPolicyMap.parse(passwordPolicy);
        if (passwordPolicyMap.isEnablePassword()) {
            for (PasswordStrategy p : getPasswordProviders(passwordPolicyMap, passwordStrategyStore)) {
                p.validate(passwordPolicyMap, baseUser, password);
            }
        }
    }

//密码校验策略存储
public class PasswordPolicyMap {
    private Map<String, Object> passwordConfig;
    private Map<String, Object> loginConfig;

    private Boolean enablePassword;
    private Boolean enableSecurity;

    public PasswordPolicyMap(Map<String, Object> passwordConfig, Map<String, Object> loginConfig, Boolean enablePassword, Boolean enableSecurity) {
        this.passwordConfig = passwordConfig;
        this.loginConfig = loginConfig;
        this.enablePassword = enablePassword;
        this.enableSecurity = enableSecurity;
    }

    public Map<String, Object> getPasswordConfig() {
        return passwordConfig;
    }

    public Map<String, Object> getLoginConfig() {
        return loginConfig;
    }

    public Set<String> getPasswordPolicies() {
        return passwordConfig.keySet();
    }

    public Set<String> getLoginPolicies() {
        return loginConfig.keySet();
    }

    public Boolean isEnablePassword() {
        return enablePassword;
    }

    public Boolean isEnableSecurity() {
        return enableSecurity;
    }

    public static PasswordPolicyMap parse(BasePasswordPolicy policy) {
        boolean enablePassword = policy.getEnablePassword();
        boolean enableSecurity = policy.getEnableSecurity();
        Map<String, Object> passwordMap = new LinkedHashMap<>();
        Map<String, Object> loginMap = new LinkedHashMap<>();
        passwordMap.put(PasswordPolicyType.MIN_LENGTH.getValue(), policy.getMinLength());
        passwordMap.put(PasswordPolicyType.MAX_LENGTH.getValue(), policy.getMaxLength());
        passwordMap.put(PasswordPolicyType.DIGITS_COUNT.getValue(), policy.getDigitsCount());
        passwordMap.put(PasswordPolicyType.LOWERCASE_COUNT.getValue(), policy.getLowercaseCount());
        passwordMap.put(PasswordPolicyType.UPPERCASE_COUNT.getValue(), policy.getUppercaseCount());
        passwordMap.put(PasswordPolicyType.SPECIALCHAR_COUNT.getValue(), policy.getSpecialCharCount());
        passwordMap.put(PasswordPolicyType.NOT_USERNAME.getValue(), policy.getNotUsername());
        passwordMap.put(PasswordPolicyType.NOT_RECENT.getValue(), policy.getNotRecentCount());
        passwordMap.put(PasswordPolicyType.REGULAR.getValue(), policy.getRegularExpression());

        loginMap.put(PasswordPolicyType.MAX_ERROR_TIME.getValue(), policy.getMaxErrorTime());
        loginMap.put(PasswordPolicyType.ENABLE_CAPTCHA.getValue(), policy.getEnableCaptcha());
        loginMap.put(PasswordPolicyType.MAX_CHECK_CAPTCHA.getValue(), policy.getMaxCheckCaptcha());
        loginMap.put(PasswordPolicyType.ENABLE_LOCK.getValue(), policy.getEnableLock());
        loginMap.put(PasswordPolicyType.LOCK_EXPIRE_TIME.getValue(), policy.getLockedExpireTime());

        Map<String, Object> passwordConfig = new LinkedHashMap<>();
        Map<String, Object> loginConfig = new LinkedHashMap<>();
        for (Map.Entry<String, Object> e : passwordMap.entrySet()) {
            if (e.getValue() == null) {
                continue;
            }
            passwordConfig.put(e.getKey(), e.getValue());
        }
        for (Map.Entry<String, Object> e : loginMap.entrySet()) {
            if (e.getValue() == null) {
                continue;
            }
            loginConfig.put(e.getKey(), e.getValue());
        }
        return new PasswordPolicyMap(passwordConfig, loginConfig, enablePassword, enableSecurity);
    }
}

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐