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

news2024/7/4 5:46:16

文章目录

  • 1、需求背景
  • 2、常规想法
  • 3、工厂模式 + 配置文件解耦 + 策略模式
  • 4、具体实现
  • 5、其他场景
  • 6、一点思考

1、需求背景

以gitee为例,登录验证的方式有多种:

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

在这里插入图片描述

先写一个登录接口,适配所有方式,且要符合开闭原则,方便以后再新增其他登录方式。先定义下传参、响应以及接口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的场景,都可以使用策略模式 + 工厂模式做个优化。

6、一点思考

第三方供应商需要上架自己的产品到公司的交易平台,但用户使用产品时,最后一步请求的自然是供应商自己的服务器资源和API。关于这个需求的实现思路,大致是在交易平台需要做接口有效性校验、服务实例有效性校验等,以及消费数据记录。

因为不同的第三方供应商系统有不同的认证方式,想实现打通,就要有不同的具体策略类(比如策略类A是通过appid完成认证,策略类B是通过密钥完成认证),因此考虑使用了策略模式。抽象策略类:

public interface ApiRedirectHandler {

    /**
     * @param headerMap 请求头参数Map
     * @param paramMap 对第三方接口的请求参数
     * @return 返回第三方接用调用的结果
     */
    Object redirect(Map<String, String> headerMap, Map<String, Object> paramMap);


}

前面提到,在交易平台要做一些校验和消费记录落库的操作,这些是对接所有三方系统的公共步骤,而后面请求第三方系统接口肯定要做的鉴权认证以及转发或者调用,则属于各个三方系统的定制化行为。因此,不直接写具体策略类,而是垫一个抽象类,实现这些公共步骤,只让最后定制化行为出现在具体策略类:

@Slf4j
public abstract class AbstractRedirectHandler implements ApiRedirectHandler {

    //抽象类中实现接口的方法
    @Override
    public Object redirect(Map<String, String> headerMap, Map<String, Object> paramMap) {

        //todo: 1.请求有效性验证

       //从请求参数paramMap中拿到你要调用APIId,然后查到的三方系统接口的路径、host等信息
       ApiInfo  apiDetailVo = queryApiInfo(paramMap);
       //API的ID用完了,它不是三方系统接口需要的请求参数,移除
       paramMap.remove("apiId"); 

        //todo: 2.服务实例有效性验证
        
        //request中去写不同三方系统的鉴权、转发或调用逻辑
        val responseData = request(headerMap, paramMap, apiDetailVo);

        //todo: 3.记录消费记录
   		
   		//返回第三方接口的响应结果
        return responseData;
    }

    /**
     * API转发请求,对接时,针对不同的三方系统去定制化实现
     *
     * @param headerMap 头信息
     * @param paramMap  请求参数
     * @Param apiDetailVo 接口信息,如接口路径、服务器host
     * @return 返回第三方接用调用的结果
     */
    protected abstract Object request(Map<String, String> headerMap, Map<String, Object> paramMap, ApiDetailVo apiDetailVo);



}

到此,不同的认证方式对应的不同的具体策略类只需实现这个抽象接口的request方法即可,之前用到的具体策略类:

  • AppID认证
  • AppSecret认证
  • SDK反射(第三方系统自己开发认证方式,提供jar包,公司的交易系统去加载jar包并反射调用第三方系统开发者的request方法)

详见【策略模式 + 反射加载SDK】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1858169.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使能PCIe的ASPM?

1. ASPM概述 PCIe总线的电源管理包含ASPM(Active State Power Management)和软件电源管理两方面内容。所谓的ASPM是指PCIe链路在没有系统软件参与的情况下&#xff0c;由PCIe链路自发进行的电源管理方式。如下是PCIe的ASPM的状态机&#xff0c;其L1是强制性的规定&#xff0c;…

Redis入门与应用(1)

Redis的技术全景 Redis是一个开源的基于键值对&#xff08;Key-Value&#xff09;的NoSQL数据库&#xff0c;使用ANSI C语言编写&#xff0c;支持网络&#xff0c;基于内存但支持持久化。它性能优越&#xff0c;并提供多种语言的API。我们可以将Redis视为一个巨大的Map&#x…

Linux 字符型设备 + platform总线 + sysfs设备模型

1 概述 第一部分先简单介绍下字符型设备 platform总线 sysfs设备模型的关系。 1.1 . 字符设备驱动 Linux设备驱动分三种&#xff0c;包括字符设备驱动、块设备驱动和网络设备驱动。字符设备只能按字节流先后顺序访问设备内存&#xff0c;不能随机访问。鼠标、触摸屏、LCD等…

Part 5.2 KMP

KMP 算法可以用来解决模式串匹配问题。 【模板】KMP 题目描述 给出两个字符串 s 1 s_1 s1​ 和 s 2 s_2 s2​&#xff0c;若 s 1 s_1 s1​ 的区间 [ l , r ] [l, r] [l,r] 子串与 s 2 s_2 s2​ 完全相同&#xff0c;则称 s 2 s_2 s2​ 在 s 1 s_1 s1​ 中出现了&…

MQTTX 1.10.0 发布:CLI高级文件管理与配置

MQTTX 1.10.0 版本现已发布&#xff01; 在本次更新中&#xff0c;CLI 版本在文件管理和配置功能方面进行了显著增强。主要更新包括&#xff1a;支持从文件中读取和写入消息、高级配置选项、文本输出模式、以及改进的日志记录。此外&#xff0c;桌面版本现在支持数据库重建&am…

Vue父组件mounted执行完后再执行子组件mounted

// 创建地图实例 this.map new BMap.Map(‘map’) } } ... 现在这样可能会报错&#xff0c;因为父组件中的 map 还没创建成功。必须确保父组件的 map 创建完成&#xff0c;才能使用 this.$parent.map 的方法。 那么&#xff0c;现在的问题是&#xff1a;如何保证父组件 mo…

Twinkle Tray:屏幕亮度控制更智能

名人说&#xff1a;一点浩然气&#xff0c;千里快哉风。 ——苏轼 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、软件介绍1、Twinkle Tray2、核心特点 二、下载安装1、下载2、安装 三、使用方法 很高兴你打开…

【数据结构与算法】详解循环队列:基于数组实现高效存储与访问

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 一、引言 &#x1f343;队列的概念 &#x1f343;循环队列的概念 &#x1f343;为什…

【Linux】Centos升级到国产操作系统Openeuler

一、前言 迁移工具采用Openeuler官网提供的x2openEuler工具&#xff0c;是一款将源操作系统迁移到目标操作系统的迁移工具套件&#xff0c;具有批量化原地升级能力&#xff0c;当前支持将源 OS 升级至 openEuler 20.03。 官网链接&#xff1a;openEuler迁移专区 | 迁移专区首页…

陀螺仪LSM6DSV16X与AI集成(8)----MotionFX库解析空间坐标

陀螺仪LSM6DSV16X与AI集成.8--MotionFX库解析空间坐标 概述视频教学样品申请源码下载开启CRC串口设置开启X-CUBE-MEMS1设置加速度和角速度量程速率选择设置FIFO速率设置FIFO时间戳批处理速率配置过滤链初始化定义MotionFX文件卡尔曼滤波算法主程序执行流程lsm6dsv16x_motion_fx…

问题:以下哪个不是报名“天天特价“活动必须具有的条件( ) #其他#其他#媒体

问题&#xff1a;以下哪个不是报名"天天特价"活动必须具有的条件( ) A、店铺信誉达到一钻 B、开通淘金币抵扣 C、宝贝月销量达到10个 D、店铺同类产品要达到10个以上 参考答案如图所示

重学java 84.Java枚举

那些你暗自努力的时光&#xff0c;终究会照亮你前行的路 —— 24.6.24 一、枚举介绍&#xff08;开发中表示状态&#xff09; 1.概述&#xff1a; 五大引用数据类型&#xff1a;类型、数组、接口、注解、枚举 2.定义&#xff1a; public enum 枚举类名{} 所有的枚举类父类…

Excel 宏录制与VBA编程 —— 12、日期相关

代码1 - 获取当前时间日期信息 代码2 - 时间日期格式 代码3 - 时间日期计算 代码4 - 时间日期案例 关注 笔者 - jxd

数据库断言

在数据库验证断言 目的&#xff1a;不能相信接口返回结果&#xff0c;通过到数据库检验可知接口返回结果是否真的正确 如何校验&#xff1a;代码与mymql建立网络连接&#xff0c;操作数据库&#xff0c;断开连接 代码&#xff1a;java操作数据库 pom文件配置依赖 步骤&…

作为一名车载测试工程师,核心能力是什么?

最近经常有人会问我&#xff0c;说XX培训机构专门培训车载测试&#xff0c;我要去&#xff0c;而且薪资很高&#xff0c;现在是风口&#xff0c;你是否也听过这样的销售话语&#xff1f; 然后进去培训2-3个月&#xff0c;包括上车测试&#xff0c;后来进去后发现原来真实的场景…

端到端的全人体关键点检测:手把手实现从YOLOPose到YOLOWhole

目录 一、搭建yolopose平台二、迁移训练任务2.1 任务拓展数据准备训练模型测试训练模型结论To-do list: 1、数据集,COCO-whole, Halpe;下载好; 2、模型搭建,先基于yolov8来检测人体姿态,17个点; 3、迁移任务,17个点,把它拓展到133个点; 4、优化133个点的模型; 一、搭…

解释一下在React中,什么是“渲染Props”模式,以及它与使用Hooks之前的状态管理有何不同?

在React中&#xff0c;"渲染Props"模式是一种组件设计模式&#xff0c;它通过将一个函数作为prop传递给组件&#xff0c;允许父组件定义子组件的渲染逻辑。这种模式使得组件更加灵活和可复用&#xff0c;因为它们可以接受一个渲染函数来决定如何渲染自己。 渲染Prop…

同元软控智能电动汽车数字化解决方案亮相CICV 2024

2024年6月18日-20日&#xff0c;由中国汽车工程学会、国家智能网联汽车创新中心、清华大学车辆与运载学院、清华大学智能绿色车辆与交通全国重点实验室举办的第十一届国际智能网联汽车技术年会&#xff08;CICV 2024&#xff09;在北京召开。苏州同元软控信息技术有限公司&…

【zip密码】忘了zip密码,怎么办?

Zip压缩包设置了密码&#xff0c;解压的时候就需要输入正确对密码才能顺利解压出文件&#xff0c;正常当我们解压文件或者删除密码的时候&#xff0c;虽然方法多&#xff0c;但是都需要输入正确的密码才能完成。忘记密码就无法进行操作。 那么&#xff0c;忘记了zip压缩包的密…

Django教程(002):模板语法的使用

目录 1 字符串2 列表3 字典4 列表中是字典5 if语句6 案例&#xff1a;使用爬虫爬取联通新闻并在页面渲染 模板语法本质上&#xff1a;在HTML中写一些占位符&#xff0c;由数据对这些占位符进行替换和处理。模板语法主要是方便对方法返回的数据在前端进行渲染&#xff0c;这些数…