策略模式 + 抽象工厂实现多方式登录验证

11 篇文章 0 订阅

1. 需求背景

登录验证的方式有多种:

  • 用户名密码登录
  • 短信验证码登录
  • 微信登录

先写一个登录接口,适配所有方式,且要符合开闭原则,方便以后再新增其他登录方式。先定义下传参、响应以及接口API路径。

//传参Dto
@Data
public class LoginDto {

    private String name;
    private String password;

    private String phone;
    private String validateCode;

    private String wxCode;

    /**
     * account:账户密码登录
     * sms:手机验证码登录
     * we_chat:微信登录
     */
    private String type;
}

//响应Vo
@Data
@AllArgsConstructor
public class LoginResp {
    private boolean success;
}


//接口定义
@RestController
@RequestMapping("/api/")
public class LoginController {

    @Resource
    private UserService userService;

    @PostMapping("/login")
    public LoginResp login(@RequestBody LoginDto loginDto) {
        return userService.login(loginDto);
    }
    
}

//Service层接口抽象
public interface UserService {

    LoginResp login(LoginDto dto);
}

2. 常规想法

最先想到的是用户点击不同的登录方式图标,前端传不同的type,后端根据type走不同的验证逻辑,那Service层代码的实现大概长这样:

@Service
public class UserServiceImpl implements UserService {

    @Override
    public LoginResp login(LoginDto dto) {
        if ("account".equals(dto.getType())) {
            System.out.println("用户名密码登录");
            //try执行用户名密码登录的验证逻辑
            return new LoginResp(true);
        } else if ("sms".equals(dto.getType())) {
            System.out.println("短信验证码登录");
            //try执行短信验证码登录的验证逻辑
            return new LoginResp(true);
        } else if ("we_chat".equals(dto.getType())) {
            System.out.println("微信登录");
            //try执行微信登录的验证逻辑
            return new LoginResp(true);
        } else {
            return new LoginResp(false);
        }
    }
}

如此,繁琐的IF-else且不符合开闭原则。考虑使用设计模式来优化。

3. 工厂模式 + 配置文件解耦 + 策略模式

每种登录就是实现登录这个目的的一种策略,因此先想到的应该是策略模式,所有具体策略类所需要实现的接口就是抽象策略类的login方法。其次,前端传不同的type,要调用不同的具体策略类对象,如此,再引入工厂模式。
在这里插入图片描述

这样写,以后再增加新的登录方式,工厂类还得改,为了解耦,使用配置文件,不同的登录方式的type,对应一个登录方式的具体策略类。
在这里插入图片描述
配置文件如:key为登录方式的type,value为具体策略类的Bean的名字。

login:
  types:
    account: accountGranter
    sms: smsGranter
    we_chat: weChatGranter

以后就把这个关系读到一个Map中使用。这里之所以给type和BeanName建立关系,是因为项目是Spring项目,如果不是,那我也可以给type和策略类的全类名建立映射关系存入Map,以后获取策略类对象,可通过反射,一样可以实现。、

4. 具体实现

上面提到要建立type和对应具体策略类的Bean的映射关系,这里通过实现 ApplicationContextAware 接口,去获取 ApplicationContext 对象,并通过它访问容器中的其他 bean。首先是读取yml配置,这里不要读login.types,这样以后加新的登录方式,又要改这个配置读取类,直接读login,得到一个types名字的数组。

@Data
@Configuration
@ConfigurationProperties(prefix = "login")
public class GranterConfig {
    private Map<String, String> types;
}

定义抽象策略类:

/**
 * 抽象策略类
 */
public interface UserLoginGranter {
    LoginResp login(LoginDto dto);
}


定义每种登录方式的具体策略类:

@Component
public class AccountGranter implements UserLoginGranter {

    @Override
    public LoginResp login(LoginDto dto) {
        System.out.println("用户名密码登录");
        //try执行用户名密码登录的验证逻辑
        return new LoginResp(true);
    }
}

@Component
public class SmsGranter implements UserLoginGranter {
    @Override
    public LoginResp login(LoginDto dto) {
        System.out.println("短信验证码登录");
        //try执行短信验证码登录的验证逻辑
        return new LoginResp(true);
    }
}

@Component
public class WeChatGranter implements UserLoginGranter {

    @Override
    public LoginResp login(LoginDto dto) {
        System.out.println("微信登录");
        //try执行微信登录的验证逻辑
        return new LoginResp(true);
    }
}

定义抽象工厂:这里定义一个static map,实现ApplicationContextAware接口(拿到容器上下文对象ApplicationContext去获取Bean),存入type和具体策略类Bean的映射关系:

/**
 * 操作策略的上下文环境类 工具类
 * 将策略整合起来 方便管理
 */
@Component
public class UserLoginFactory implements ApplicationContextAware {

    @Resource
    private GranterConfig granterConfig;

    private static Map<String, UserLoginGranter> granterPool = new ConcurrentHashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        granterConfig.getTypes().forEach((k, v) -> granterPool.put(k, applicationContext.getBean(v, UserLoginGranter.class)));
    }

    /**
     * 获取具体策略类的对象
     * @param type 登录方式
     * @return 具体策略类的对象Bean
     */
    public UserLoginGranter getGranter (String type) {
        return granterPool.get(type);
    }

}

修改之前繁琐的IF-else,Service层的实现类改为:

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserLoginFactory factory;

    @Override
    public LoginResp login(LoginDto dto) {
        UserLoginGranter loginGranter = factory.getGranter(dto.getType());
        if (null == loginGranter) {
            return new LoginResp(false);
        }
        return loginGranter.login(dto);
    }
}

此后,再扩展另外的登录方式,比如QQ登录认证,只需加个具体策略类以及在application.yaml加个配置

login:
  types:
    account: accountGranter
    sms: smsGranter
    we_chat: weChatGranter
    # 扩展
    qq: qqGranter

核心点:

  • 提供多种具体策略的对象,让Spring容器管理
  • 提供一个工厂,根据参数返回对应的具体策略对象

5. 其他场景

类似的,做订单支付也可以用策略模式,具体支付策略有:

支付宝
微信
银联
再比如做解析不同类型的excel,可以针对不同的格式写具体策略类,所有策略类实现抽象策略类的解析接口:

xls格式的解析具体策略类
xlsx格式的解析具体策略类
总之,涉及不同的实现方式(策略),搭配冗长的if-else或者switch的场景,都可以使用策略模式 + 工厂模式做个优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值