2023-06-18
原文作者:代码有毒 mrcode 原文地址:https://mrcode.blog.csdn.net/article/details/81942345

短信登录配置及重构

重构思路:

  1. 重构不是更改已有的功能

  2. 重构是不影响已有功能的情况下,对已有代码进行抽象封装

  3. 多处使用相同代码的地方,需要抽出来

  4. 比如上章节的很多代码,

    如:图形验证码过滤器和短信验证码过滤器重复代码太多
    服务接口的url地址和过滤器中的过滤器地址重复
    等..

系统配置相关的代码结构

core项目中的重构如下:

  • 密码登录的配置代码
  • 短信登录的配置代码
  • 验证码相关的配置代码

browser项目:

  • BrowserSecurityConfig

  • 只留下浏览器特有的配置代码

    • 如记住我的功能,只有浏览器特有这样的功能

app:

  • AppSecurityConfig
  • App特有的配置代码

通过配置apply功能进行配置的引用

202306181903044231.png

感受

花了6个小时看老师重构之后的代码,然后完成了自己跟练的项目代码;

太厉害!!这个重构技巧太牛逼了;

总之:当有两处重复代码的时候 就要抽取代码了。这个需要大量的经验才能不分类好,不至于越抽越乱

这里再啰嗦下:
关于用户名密码登录和短信登录表单提交的url地址,不需要真实存在,
因为这个是提供这两个特定过滤器框架特定的拦截点。只有提交到指定的拦截点,
才会进入认证功能服务

此次重构一些知识点

  • 善用 HttpSecurity.apply 应用分离之后的配置类
  • 程序中有手动写字符串2次的就抽成 SecurityConstants 常量接口类
  • 善用 Autowired注解提供的 依赖查找功能
  • 善用 类名统一起名
  • 善用枚举类 提供相应的支持

善用 HttpSecurity.apply 应用分离之后的配置类

     HttpSecurity.apply 方法跟踪进来是父类的;这里是一个泛型,所有需要看HttpSecurity对应传递的是什么类型
    public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
            extends AbstractSecurityBuilder<O> {
          public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
            throws Exception {
            configurer.addObjectPostProcessor(objectPostProcessor);
            configurer.setBuilder((B) this);
            add(configurer);
            return configurer;
          }
    
    --------- HttpSecurity 声明
    public final class HttpSecurity extends
            AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
            implements SecurityBuilder<DefaultSecurityFilterChain>,
            HttpSecurityBuilder<HttpSecurity> {
    
    ------------ 注意看对比
    public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer
    
    AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
    
    这里的c对应的泛型就是 <O, B> ,而O,B对应到HttpSecurity的声明就是<DefaultSecurityFilterChain, HttpSecurity>
    
    HttpSecurity.apply 返回一个SecurityConfigurerAdapter<O, B>,所以这里只要继承该类,就是apply需要的对象了
    
    ------------- 如下示例
    /**
     * 验证码配置
     * @author : zhuqiang
     * @version : V1.0
     * @date : 2018/8/5 20:05
     */
    @Component
    public class ValidateCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
        /**
         * @see ValidateCodeFilter  目前融合了短信和图形验证码的验证功能
         */
        @Autowired
        private Filter validateCodeFilter;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            // 由源码得知,在最前面的是UsernamePasswordAuthenticationFilter
            http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }

SecurityConstants 常量接口类

在重构中就已经发现这样做的好处了。因为这个代码被分离写和配置的,老是忘记在哪些地方用过
在修改的时候经常忘记修改,或则找不到,所以需要抽成常量类

    public interface SecurityConstants {
    
        /**
         * 默认的处理验证码的url前缀
         */
        public static final String DEFAULT_VALIDATE_CODE_URL_PREFIX = "/code";

善用 Autowired注解提供的 依赖查找功能

把多个实现类统一管理,特别是使用模板方法抽取公用逻辑的时候,就拍上用处了
如下代码,还提供了按beanName查找指定的子类实现;
还提供了按自定义类型,下面会讲到善用命名会在某些地方起到奇效

    /**
     * 处理器持有者,用来管理所有验证码类型的处理器
     * @author : zhuqiang
     * @version : V1.0
     * @date : 2018/8/5 20:40
     */
    @Component
    public class ValidateCodeProcessorHolder {
        @Autowired
        private Map<String, ValidateCodeProcessor> validateCodeProcessors;
    
        public ValidateCodeProcessor findValidateCodeProcessor(ValidateCodeType type) {
            return findValidateCodeProcessor(type.toString().toLowerCase());
        }
    
        public ValidateCodeProcessor findValidateCodeProcessor(String type) {
            String beanName = type.toLowerCase() + ValidateCodeProcessor.class.getSimpleName();
            ValidateCodeProcessor processor = validateCodeProcessors.get(beanName);
            if (processor == null) {
                throw new ValidateCodeException("验证码处理器 " + beanName + " 不存在");
            }
            return processor;
        }
    }

善用 类名统一起名

如这里的几个类

  • ValidateCodeProcessor 验证码处理接口
  • ImageValidateCodeProcessor 图片验证码处理接口
  • SmsValidateCodeProcessor 短信验证码处理接口

这里的前缀,配合上面的技巧 善用 Autowired注解提供的 依赖查找功能,使用以下代码就能方便的获取到对应的处理器

    他们都一个共同的父类,有公用的步骤,变化的部分由子类实现;
    public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {
      /**
       * 根据请求的url获取校验码的类型:
       * ValidateCodeProcessorHolder : 中持有所有本类的子类型,获取getClass能拿到具体的实例类名
       * @return
       * @see ValidateCodeProcessorHolder
       */
      private ValidateCodeType getValidateCodeType() {
          // 处理器 命名规则:ImageValidateCodeProcessor,拿到前缀即可
          // 返回 Image
          String type = StringUtils.substringBefore(getClass().getSimpleName(), ValidateCodeProcessor.class.getSimpleName());
          return ValidateCodeType.valueOf(type.toUpperCase());
      }
    }
    
    在外部使用 type + ValidateCodeProcessor.class.getSimpleName() 就能获取到完整的类名,
    也就能使用ValidateCodeProcessorHolder动态的获取处理器了

善用枚举类提供相应的支持

枚举类的名称是 短信和图片验证功能的前缀。配合上面的几条。
在使用模板方法模式抽取公用逻辑的时候,可以使用前缀获取不同功能支持的动态常量等类容
在外部要动态使用服务的时候,也能用前缀+具体的的父类命名获取到

    public enum ValidateCodeType {
        /**
         * 短信验证码
         */
        SMS {
            @Override
            public String getParamNameOnValidate() {
                return SecurityConstants.DEFAULT_PARAMETER_NAME_CODE_SMS;
            }
        },
        /**
         * 图片验证码
         */
        IMAGE {
            @Override
            public String getParamNameOnValidate() {
                return SecurityConstants.DEFAULT_PARAMETER_NAME_CODE_IMAGE;
            }
        };
    
        /**
         * 校验时从请求中获取的参数的名字
         * @return
         */
        public abstract String getParamNameOnValidate();
    }
阅读全文