真香定律!我用这种模式重构了第三方登录
分享是最有效的学习方式。
博客:https://blog.ktdaddy.com/
老猫的设计模式专栏已经偷偷发车了。不甘愿做crud boy?看了好几遍的设计模式还记不住?那就不要刻意记了,跟上老猫的步伐,在一个个有趣的职场故事中领悟设计模式的精髓吧。还等什么?赶紧上车吧。
故事
办公室里,小猫托着腮帮对着电脑陷入了思考。就在刚刚,他接到了领导指派的一个任务,业务调整,登录方式要进行拓展。例如需要接入第三方的微信登录,企业微信授权登录等等。
原因大概是这样,现在大环境不好,原来面向B端企业员工的电商业务并不好做,新客拓展比较困难,业务想要有更好的起色着实比较困难,所以决策层决定要把登录的口子放开,原来支持手机密码登录以及手机验证码进行登录,现在为了更好地推广,需要支持微信扫码关注企业公众号后登录,企业微信,微博等等一些列的第三方登录模式。
说白了未来到底会有多少种登录方式不得而知,那么面对这样一个棘手的问题,小猫又该何去何从?
概述
登录问题相信后端小伙伴都有接触过,最简单的可能就是做一个权限系统就会用到登录名+密码+验证码进行登录,继而稍微复杂一些可能会涉及手机验证码登录。现在随着第三方平台的层出不穷,我们很多网站其实都提供了联合登录。用户掏出手机简单地一个扫码动作即可完成初步的注册登录功能。这种方式一定程度上能够给当前的网站带来更多的流量。
关于小猫遇到的问题,咱们尝试从下面几个点去解决。
登录演化
聊到登录,我们首先去了解一下整个登录认证的发展阶段,以及目前比较常见也相对比较复杂的微信公众号授权登录流程。
基于Cookie/Session进行验证登录
在早期,也就是可能是单体系统的时代,亦或者站在java开发者角度来说是jsp时代的时候,我们用的登录方式就是Cookie/Session验证的方式。关于Cookie以及Session相信很多后端的小伙伴都应该知道,当然若真有不清楚的,大家可以自己查阅一下相关资料。
利用这种方式登录的流程其实还是比较简单的,如下流程:
基于上述登录成功后,服务端将用户的身份信息存储在Session里,并将session ID通过cookie传递给客户端。后续的数据请求都会带上cookie,服
务端根据cookie中携带的session id来得到辨别用户身份。
简单的java伪代码如下:
...
session.setAtrrbuite("user",user);
...
session.getAttrbuite("user");
当然上述的伪代码还是基于最最原始的写法去写的,关于这种登录的框架,其实目前市面上也有比较成熟的,例如轻量级的shiro,spring本身自带的权限认证框架也有。
随着业务的发展,系统访问量级的增大,我们渐渐发现这种方式存在着一些问题:
- 由于服务端需要对接大量的客户端,也就需要存放大量的Seesion ID,这样就会导致服务器压力过大。如果服务器是个集群,为了同步登录的状态,需要将Session ID同步到每一台服务器上,无形中增加了服务器端的维护成本。
- 由于Session ID存放在Cookie中,所以无法避免CSRF攻击(跨站请求伪造)。
当然其他问题也欢迎小伙伴们进行补充。
为了解决这一些列的问题,我们渐渐演化出了另外一种登录认证方式————基于token进行认证登录。
基于TOKEN进行认证登录
现在的系统大部分都是前后端分离开发的。后端大多使用了WEB API,此时token无疑是处理认证的最好方式。
Session 方案中用户信息(以Session记录形式)存储在服务端。而Token方案中(以Token形式)存储在客户端,服务端仅验证Token合法性即可。基于Token的身份验证是无状态的,不将用户信息存在服务器中。这种概念解决了在服务端存储信息时的许多问题。NoSession意味着咱们的程序可以根据需要去增减机器,而不用去担心用户是否登录。
咱们一起来看一下如果使用TOKEN整个流程。
关于上述token机制的特点有以下几点:
-
无状态、可扩展:在客户端存储的Token是无状态的,并且能够被扩展。基于这种无状态的和不存储Session信息,所以不会对服务器端造成压力,负载均衡器能够将用户信息从一个服务器传到其他服务器上,即使是服务器集群,也不需要增加维护成本。
-
可扩展性:Tokens能够创建与其它程序共享权限的程序。(即,我们所说的第三方平台联合登录的时候,token的生成机制以及验证可以由第三方系统进行联合验证登录)
-
安全性:请求中发送Token而不是发送Cookie,能够防止CSRF(跨站请求伪造)。即客户端使用Cookie存储了Tooken,Cookie也仅仅是一个存储机制而不是用于认证。不将信息存储在Session中,让我们少了对Session的操作。Token也可以存放在前端任何地方,可以不用保存在Cookie中,提升了页面的安全性。Token是会失效的,一段时间之后用户需要重新验证。
-
多平台跨域:对应用程序和服务进行扩展的时候,需要介入各种各种的设备和应用程序。只要用户有一个通过了验证的token,数据和资源就能够在任何域上被请求到。
微信扫码跳转公众号认证登录
这也是后续小猫遇到的问题,以及需要和其他第三方Api主要对接的。其实关于扫码认证登录也是基于token机制的一种拓展。只不过第三方的平台在token机制上新增了获取二维码进行二次确认的过程。咱们以微信扫码跳转公众号登录为例来看一下整个流程。其他的第三方登录流程其实也是大同小异,咱们了解一个流程即可,不同的平台只是对接不同的api而已。流程图如下:
从上面这幅图看到,扫码登录其实复杂就复杂在获取token这个步骤上,当获取完毕token之后,其后续的业务逻辑其实基本也是一样的。
其实其他第三方的登录其实也是大同小异,最主要的难点是在如何获取token上,我们只要认真看完对接的api,其实问题也基本都能迎刃而解。
说明一下,老猫这里绘图用了drawio工具,如果想要知道老猫的绘图思路,大家可以看看这里《绘图思路》
如何兼容多套?
看完上述之后,相信大家会对认证登录心里有杆秤了。细节方面其实只要去查询相关平台的api,然后去撸代码就好了。但是实现一套倒是还好,但是现在小猫遇到的问题是需要在原逻辑上去丰富登录的代码。如果在老的代码上通过if else的方式去实现多套登录逻辑,那估计后面又是屎山。
这里,其实我们可以引入“适配器设计模式”去解决这样的问题。
什么是适配器模式?
适配器模式(英文名:Adapter Pattern)是指将一个类的接口转换成用户期望的另一个接口,使得原本接口不兼容的类可以一起工作。
适配器模式可以分为两类:对象适配器模式和类适配器模式。对象适配器模式通过组合实现适配,而类适配器模式则通过继承实现适配。
此外,还有一种特殊的适配器模式——缺省适配器模式它由一个抽象类实现,并在其中实现目标接口中所规定的所有方法,但这些方法的实现通常是空方法,由具体的子类来实现具体的功能。适配器模式的应用可以提高代码的复用性和可维护性,同时帮助解决不同接口之间的兼容性问题。
上面的概念比较抽象,其实在咱们的日常生活中也有这样的例子,例如手机充电转换头,显示器转接头等等。
适配器模式重构第三方登录
话不多说,直接开干,我们就针对小猫的遇到这个第三方登录的场景,咱们用代码重构一把。(当然,这里我们侧重的还是伪代码)。跟着老猫,咱们一步步走好代码的演化。
咱们先看一下老的业务代码,如下:
public class UserLoginService {public ApiResponse<String> regist(String userName,String password) {//...dosomethingreturn ApiResponse.success("success");}public ApiResponse login(String userName, String password) {return null;}
}
接下来由于小猫的业务会发生变更,新的登录方式会层出不穷,所以,我们得遵循之前提到的软件设计原则去更好地写一下业务代码。我们遵循之前提到的开闭原则,于是我们迈出了重构代码的第一步,我们将创建一个新的第三方登录的类来专门处理第三方的登录对接。如下:
public class ThirdPartyUserLoginService extends UserLoginService {public ApiResponse loginForQQ(String openId) {/*** openid 全局唯一,咱们直接作为用户名* 默认密码QQ_EMPTY* 注册(原来父类中有注册实现)* 调用原来的登录*/return loginForRegist(openId, null);}public ApiResponse loginForWechat(String openId) {return null;}public ApiResponse loginForToken(String token) {return null;}public ApiResponse loginForTel(String tel, String code) {return null;}public ApiResponse<String> loginForRegist(String userName, String password) {super.login(userName, password);return super.login(userName, password);}
}
写到这里,其实咱们已经集成了多种登录方式的代码兼容,但是这种实现方式显然是不太优雅的,看起来比较死板,在登录的时候我们甚至还得去判断客户到底是用什么去做登录的,然后去分别调用不同第三方平台的认证方式。
我们接下来演化开始用适配器。如下代码:
首先我们定义出一个标准的适配接口:
public interface LoginAdapter {boolean support(Object adapter);ApiResponse login(String id,Object adapter);
}
根据上面我们看到,我们有QQ方式登录,有微信方式登录,有电话验证码方式登录。所以我们对应的就应该有相关的这些方式的适配器的实现。由于代码重复,所以在此老猫就写QQ和微信这两种伪代码,其他的暂时先偷个懒。
/*** @author 公众号:程序员老猫* @date 2024/3/3 22:47*/
public class LoginForQQAdapter implements LoginAdapter {@Overridepublic boolean support(Object adapter) {return adapter instanceof LoginForQQAdapter;}@Overridepublic ApiResponse login(String id, Object adapter) {return null;}
}public class LoginForWeChatAdapter implements LoginAdapter {@Overridepublic boolean support(Object adapter) {return adapter instanceof LoginForWeChatAdapter;}@Overridepublic ApiResponse login(String id, Object adapter) {return null;}
}
有了这些适配器之后,我们就统一对外给出去接口:
public interface IPassportForThird {ApiResponse loginForQQ(String openId);ApiResponse loginForWechat(String openId);ApiResponse<String> loginForRegist(String userName, String password);
}
最后创建统一适配器。
@Slf4j
public class PassportForThirdAdapter extends UserLoginService implements IPassportForThird{@Overridepublic ApiResponse loginForQQ(String openId) {return doLogin(openId,LoginForQQAdapter.class);}@Overridepublic ApiResponse loginForWechat(String openId) {return doLogin(openId,LoginForWeChatAdapter.class);}@Overridepublic ApiResponse<String> loginForRegist(String userName, String password) {super.login(userName, password);return super.login(userName, password);}//用到简单工厂模式以及策略模式private ApiResponse doLogin(String openId,Class<? extends LoginAdapter> clazz) {try {LoginAdapter adapter = clazz.newInstance();if(adapter.support(adapter)){return adapter.login(openId,adapter);}}catch (Exception e) {log.error("exception is",e);}return null;}
}
最终我们看一下实现的类图:
上述我们就用了适配器的模式简单重构了现有的第三方登录的代码,当然上述可能还存在一些代码的缺陷,大家也不要太过较真,在此给大家在日常开发中多点思路。
大家可能会对每个适配器的support()方法有点疑问,用来决断兼容。这里support()方法的参数也是Object类型的,而support()方法来自接口。适配器的实现并不依赖接口,其实我们也可以直接将LoginAdapter移除。
在上述重构的例子中,其实咱们不仅仅用到了适配器模式,其实还用到了简单工厂模式的特性。
总结
其实在我们日常的开发中,适配器模式是比较常用的一种设计模式,不仅仅使用上述场景,其实在很多其他api的对接的场景也有适用。例如,在电商业务场景中会涉及到各种对接,说到买卖就会牵扯到供应商的对接,第三方分销渠道客户的对接,其中必然涉及模型不一致需要适配转换的场景,比如供应商商品信息和标准商城商品信息等等。当然老猫在此也只是做了一下简单罗列。希望大家在后面的工作中可以参考用到。
相关文章:

真香定律!我用这种模式重构了第三方登录
分享是最有效的学习方式。 博客:https://blog.ktdaddy.com/ 老猫的设计模式专栏已经偷偷发车了。不甘愿做crud boy?看了好几遍的设计模式还记不住?那就不要刻意记了,跟上老猫的步伐,在一个个有趣的职场故事中领悟设计模…...

Linux入门到入土
Linxu Linux 简介 Linux 内核最初只是由芬兰人林纳斯托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的。 Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX(可移植操作系统接口)…...

基础真空技术外国文献Fundamentals of Vacuum Technology
基础真空技术外国文献Fundamentals of Vacuum Technology...
LeetCode每日一题【c++版】- 用队列实现栈与用栈实现队列
用队列实现栈 题目描述 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。 实现 MyStack 类: void push(int x) 将元素 x 压入栈顶。int pop() 移除…...

深入理解快速排序算法:从原理到实现
目录 1. 引言 2. 快速排序算法原理 3. 快速排序的时间复杂度分析 4. 快速排序的应用场景 5. 快速排序的优缺点分析 5.1 优点: 5.2 缺点: 6. Java、JavaScript 和 Python 实现快速排序算法 6.1 Java 实现: 6.2 JavaScript 实现&#…...

设计模式----装饰器模式
在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现。 装饰器模式 允许向一个现有的对象添加新的功能,同时又不改变他的…...

Golang pprof 分析程序的使用内存和执行时间
一、分析程序执行的内存情况 package mainimport ("os""runtime/pprof" )func main() {// ... 你的程序逻辑 ...// 将 HeapProfile 写入文件f, err : os.Create("heap.prof")if err ! nil {panic(err)}defer f.Close()pprof.WriteHeapProfile(f…...

C/C++平方和问题(蓝桥杯)
题目描述: 小明对数位中含有2、0、1、9 的数字很感兴趣,在1 到40 中这样的数包 括1、2、9、10 至32、39 和40,共28 个,他们的和是574,平方和是14362。 注意,平方和是指将每个数分别平方后求和。 请问&#…...

(libusb) usb口自动刷新
文章目录 libusb自动刷新程序Code目录结构Code项目文件usb包code包 效果描述重置reset热拔插使用 END libusb 在操作USB相关内容时,有一个比较著名的库就是libusb。 官方网址:libusb 下载: 下载源码官方编好的库github:Release…...

NLP(一)——概述
参考书: 《speech and language processing》《统计自然语言处理》 宗成庆 语言是思维的载体,自然语言处理相比其他信号较为特别 word2vec用到c语言 Question 预训练语言模型和其他模型的区别? 预训练模型是指在大规模数据上进行预训练的模型,通常…...

智慧公厕:打造智慧城市的环卫明珠
在城市建设中,公共卫生设施的完善和智能化一直是重要环节。而智慧公厕作为智慧城市建设的重要组成部分,发挥着不可替代的作用。本文以智慧公厕源头实力厂家广州中期科技有限公司,大量精品案例现场实景实图,解读智慧公厕如何助力打…...

[LeetBook]【学习日记】寻找链表相交节点
来源于「Krahets」的《图解算法数据结构》 https://leetcode.cn/leetbook/detail/illustration-of-algorithm/ 本题与主站 160 题相同:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/ 训练计划 V 某教练同时带教两位学员,分别以…...

【Python】OpenCV-使用ResNet50进行图像分类
使用ResNet50进行图像分类 如何使用ResNet50模型对图像进行分类。 import os import cv2 import numpy as np from tensorflow.keras.applications.resnet50 import ResNet50, preprocess_input, decode_predictions from tensorflow.keras.preprocessing import image# 设置…...
TypeError: `dumps_kwargs` keyword arguments are no longer supported
TypeError: dumps_kwargs keyword arguments are no longer supported 1. 问题描述2. 解决方法 1. 问题描述 使用 FastChat 启动私有大语言模型,通过一些 UI 工具进行访问时,报以下错误。 略 2024-02-29 09:26:14 | ERROR | stderr | yield f"…...
设计模式学习笔记 - 设计原则 - 3.里氏替换原则,它和多态的区别是什么?
前言 今天来学习 SOLID 中的 L:里氏替换原则。它的英文翻译是 Liskov Substitution Principle,缩写为 LSP。 英文原话是: Functions that use points of references of base classes must be able to use objects of derived classes withou…...

java实现图片转pdf,并通过流的方式进行下载(前后端分离)
首先需要导入相关依赖,由于具体依赖本人也不是记得很清楚了,所以简短的说一下。 iText:PDF 操作库,用于创建和操作 PDF 文件。可通过 Maven 或 Gradle 引入 iText 依赖。 MultipartFile:Spring 框架中处理文件上传的类…...
如何系统的学习Python——Python的基本语法
学习Python的基本语法是入门的第一步,以下是一些常见的基本语法概念: 注释: 用#符号来添加单行注释,或使用三引号(或""")来添加多行注释。 # 这是一个单行注释 这是 多行 注释 变量和数据类型: 变量用…...

相机,棱镜和光场
一、成像方法 Imaging Synthesis Capture 1.Synthesis(图形学上)合成:比如之前学过的光线追踪或者光栅化 2.Capture(捕捉):把真实世界存在的东西捕捉成为照片 二、相机 1.小孔成像 利用小孔成像的相…...

【图像版权】论文阅读:CRMW 图像隐写术+压缩算法
不可见水印 前言背景介绍ai大模型水印生成产物不可见水印CRMW 在保护深度神经网络模型知识产权方面与现有防御机制有何不同?使用图像隐写术和压缩算法为神经网络模型生成水印数据集有哪些优势?特征一致性训练如何发挥作用,将水印数据集嵌入到…...
代码随想录算法训练营第31天—贪心算法05 | ● 435. 无重叠区间 ● *763.划分字母区间 ● *56. 合并区间
435. 无重叠区间 https://programmercarl.com/0435.%E6%97%A0%E9%87%8D%E5%8F%A0%E5%8C%BA%E9%97%B4.html 考点 贪心算法重叠区间 我的思路 先按照区间左坐标进行排序,方便后续处理进行for循环,循环范围是0到倒数第二个元素如果当前区间和下一区间重叠…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...

内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献
Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译: ### 胃肠道癌症的发病率呈上升趋势,且有年轻化倾向(Bray等人,2018&#x…...
【java】【服务器】线程上下文丢失 是指什么
目录 ■前言 ■正文开始 线程上下文的核心组成部分 为什么会出现上下文丢失? 直观示例说明 为什么上下文如此重要? 解决上下文丢失的关键 总结 ■如果我想在servlet中使用线程,代码应该如何实现 推荐方案:使用 ManagedE…...