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

文章目录

  • 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.mfbz.cn/a/740311.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

udp协议 服务器

1 TCP和UDP基本概念 TCP:(Transmission Control Protocol)是一种面向连接、可靠的基于字节流的传输层通信协议。并且提供了全双工通信&#xff0c;允许两个应用之间建立可靠的链接以进行数据交换 udp:(User Datagram Protocol):是一种无链接、不可靠、基于数据报文传输层协议&…

websocket服务执行playwright测试

上一篇博客从源码层面分析了playwright vscode插件实现原理&#xff0c;在上一篇博客中提到&#xff0c;backend服务是一个websocket服务。这遍博客将介绍如何封装一个websocket服务&#xff0c;通过发送消息来执行playwright测试。 初始化项目 第一步是初始化项目和安装必要的…

​【VMware】VMware Workstation的安装

目录 &#x1f31e;1. VMware Workstation是什么 &#x1f31e;2. VMware Workstation的安装详情 &#x1f33c;2.1 VMware Workstation的安装 &#x1f33c;2.2 VMware Workstation的无限使用 &#x1f31e;1. VMware Workstation是什么 VMware Workstation是一款由VMwar…

【K8s】专题六:Kubernetes 资源限制及服务质量等级

以下内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01;如果对您有帮助&#xff0c;烦请点赞、关注、转发&#xff01;欢迎扫码关注个人公众号&#xff01; 目录 一、资源限制 1、基本介绍 2、工作原理 3、限制方法 二、服务质量等级 一、资源限制 1…

【软件测试入门】测试用例经典设计方法 — 因果图法

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、因果图设计测试用例的步骤 1、分析需求 阅读需求文档&#xff0c;如果User Case很复杂&am…

DIY灯光特效:霓虹灯动画制作教程

下面我们根据这张霓虹灯案例,教大家如何用智能动物霓虹灯闪烁的效果,大家可以根据思路,实现自己想要的动效效果,一起动手来做吧。 即时设计-可实时协作的专业 UI 设计工具 设置背景 新建画板尺寸为:800PX^600PX,设置背景色#120527。 绘制主题 输入自己喜欢文案,轮廓化,具体…

PHP-CGI的漏洞(CVE-2024-4577)

通过前两篇文章的铺垫&#xff0c;现在我们可以了解 CVE-2024-4577这个漏洞的原理 漏洞原理 CVE-2024-4577是CVE-2012-1823这个老漏洞的绕过&#xff0c;php cgi的老漏洞至今已经12年&#xff0c;具体可以参考我的另一个文档 简单来说&#xff0c;就是使用cgi模式运行的PHP&…

充电桩--充电桩智能化发展趋势

聚焦光伏产业、深耕储能市场、探究充电技术 小Q下午茶 相互交流学习储能和BMS相关内容 43篇原创内容 公众号 一、背景介绍 国家提出“新基建”以来&#xff0c;充电基础设施产业跃入人们的视线成为热门话题。充电基础设施作为充电网、车联网、能源网和物联网的连接器&…

JS对象、数组、字符串超详细方法

JavaScript 对象方法 对象创建的方式 对象字面量 var dog1 {name: "大黄",age: 2,speak: function () {console.log("汪汪");}, };使用Object构造函数 var dog2 new Object(); dog2.name "大黄"; dog2.age 2; dog2.speak function () …

卷积的通俗解释

以时间和空间两个维度分别理解卷积&#xff0c;先用文字来描述&#xff1a; 时间上&#xff0c;任何当前信号状态都是迄至当前所有信号状态的叠加&#xff1b;时间上&#xff0c;任何当前记忆状态都是迄至当前所有记忆状态的叠加&#xff1b;空间上&#xff0c;任何位置状态都…

初见:AntDB智能运维“三剑客“之ADC

引言 6月15日&#xff0c;PostgreSQL数据库技术峰会广州站圆满落幕。峰会上&#xff0c;亚信安慧数据库智能运维产品负责人李志龙介绍了AntDB的6大数据库引擎和3大工具产品能力。 这里的3大工具分别指&#xff1a; AntDB数据库迁移工具包 MTK 数据库智能运维平台 ACC AntDB数据…

SwiftUI 6.0(iOS 18/macOS 15)关于颜色 Color 的新玩法

概览 WWDC 2024 重装升级的 SwiftUI 6.0 让 Apple 不同平台&#xff08;iOS 18/macOS 15&#xff09;显得愈发的冰壶玉衡、美轮美奂。 之前梦寐以求的颜色混合功能在 WWDC 24 里终于美梦成真啦&#xff01; 在本篇博文中&#xff0c;您将学到如下内容&#xff1a; 概览1. 梦想…

this.$prompt 提示框增加文本域并修改文本域高度

2024.06.24今天我学习了如何对提示框增加文本域的方法&#xff0c;效果如下&#xff1a; 代码如下&#xff1a; <script>methods:{reject_event(){this.$prompt(驳回内容, 提示, {confirmButtonText: 确定,cancelButtonText: 取消,inputType: textarea,inputPlaceholder…

精益思想在机器人开发中的应用体现

精益思想源于制造业&#xff0c;旨在通过消除浪费、优化流程、持续改进来提升企业竞争力。在机器人开发中&#xff0c;精益思想同样具有指导意义。它要求开发团队在需求分析、设计、制造、测试等各个环节中&#xff0c;不断追求精益求精&#xff0c;力求在降低成本的同时提升产…

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

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

C++并发之协程实例(四)(通过迭代器访问生成器序列)

目录 1 协程2 实例3 运行 1 协程 协程(Coroutines)是一个可以挂起执行以便稍后恢复的函数。协程是无堆栈的&#xff1a;它们通过返回到调用方来暂停执行&#xff0c;并且恢复执行所需的数据与堆栈分开存储。这允许异步执行的顺序代码&#xff08;例如&#xff0c;在没有显式回调…

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

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

8、MFC界面开发

界面开发 1、创建Ribbon样式的应用程序框架2、为Ribbon Bar添加控件2.1 下拉菜单2.2 添加消息处理函数 1、创建Ribbon样式的应用程序框架 创建MFC界面时选择样式为"Office"&#xff0c;然后再选择功能区。 2、为Ribbon Bar添加控件 Ribbon界面开发利用Ribbon Des…

lvs集群 Keepalived

Keepalived高可用集群 Keepalived概述 功能 LVS规则管理LVS集群真实服务器状态监测管理VIP Keepalived实现web高可用 安装keepalived软件 在webservers上配置 启动服务 webservers systemctl start keepalived.service ip a s | grep 192.168 #web1主机绑定vip 测试…

【gif制作】Win下视频生成GIF;工具GifCam单色保存,灰度保存,调速,编辑删除帧添加文本

下载地址 https://blog.bahraniapps.com/gifcam/#download https://gifcam.en.softonic.com/ 界面功能 GifCam 简洁、小巧的 gif 录制软件。GifCam就像照相机一样位于所有窗口的顶部&#xff0c;可以移动它并调整其大小录屏所需的区域。 如图&#xff1a;空闲状态下窗口内…