系列文章的目录:https://blog.csdn.net/hjkl950217/article/details/89490709
记住:设计模式重理解,轻照搬
上期遗留的坑
上期的那种写法使用时还有“只适用基础数据结构不变、使用多变、无序、无分支的场景”的问题,这次想解决的就是多变、无序的场景。
项目背景
每次写文章我都会在文章开始前描述下背景,为的是让读者知道背景后,可以更好的在自己的项目中应用,所以这期一样要废话一下。
我们项目需要重构登陆验证部分,将之前的登陆代码简化,在不影响功能的前提下优化代码流程。这些功能包括:
- 一些基础请求头检查
- 检查IP地址
- 检查登陆者在session中是否存在
- 需要URL白名单
- 验证完成后将用户的数据加载到一个地方,供业务代码使用
- 内部测试用户为了测试方便会使用固定token
分析
分析下这个业务的特点有:
- 验证情况可能有多种,但目的都是为了登陆.(分支少,线性逻辑)
- 需要在验证流程中添加一些检查
- 不同用户需要一些特别的检查逻辑
- 整体上可以分为 验证前、验证中、验证后
结合.net core中的中间件写法+Context思想(一次业务,将相关的基础数据放在一个实体中,可参考HttpConetxt),我搞出了一种类中间件的业务代码。
伪中间件实现
1. 设计Context
我按照整体流程的3个阶段将Context设计为这样:
- 需要的配置和数据:包括从请求中采集的信息和我们的一些配置
- 分析是否返回给前端: 存放每步执行后的处理结果,如果结果为true了则直接返回。(比如IP检查没过,就没必要进行后续检查了)
- 向IOC写数据:这里利用
IOC
的Scoped
模式,临时保存用户的信息
说白了就是把要处理的所有信息提前安放好,而不是用到了 才去找。
2. 设计业务中间件
中间件代码最重要的就是需要知道它的下一个是谁。
比如中间件定义:
public interface IAuthenticationMw
{
IAuthenticationMw Next { get; set; }
int ExecuteOrder { get; }
Task InvokeAsync(IAuthenticationContext context);
}
那么实现代码中一定会有:
public Task InvokeAsync(IAuthenticationContext context)
{
return this.Next(context);
}
这样就串起来了。
而我们的业务中,检查有一个不过则直接返回,这就要求顺序了(比如检查没通过时,一定不能调用加载数据的方法)
3. 设计检查服务
前面说的都是中间件的接口,我们还需要一种类用来将它们管理起来。我设计了一个CheckService
,它负责将所有中间件从IOC中取出来,并为它们的Next属性复制。此时就用到了ExecuteOrder
属性。
//初始化
public CheckService(IEnumerable<IAuthenticationMw> authenticationMws)
{
this.firstMw = this.SortMw(authenticationMws);
}
//真实调用
public async Task<IAuthenticationContext> StartCheckAsync(IAuthenticationContext context)
{
await this.firstMw.InvokeAsync(context);
IAuthenticationContext result = this.ProcessResult(context);
return result;
}
挂接代码:
protected IAuthenticationMw SortMw(IEnumerable<IAuthenticationMw> authenticationMws)
{
var mwLsit = authenticationMws
.OrderBy(i => i.ExecuteOrder)
.ToArray();
//排序
//为保证最后一个中间件的next不会执行,所以这里使用降序
IAuthenticationMw tempMw = mwLsit.FirstOrDefault();
foreach (var item in mwLsit)
{
tempMw.Next = item;
tempMw = item;
}
tempMw.Next = mwLsit.LastOrDefault();
return mwLsit.FirstOrDefault();
}
剩余的事就是将需求拆分,并且实现IAuthenticationMw
接口即可。在认证的地方只要调用到CheckService
的StartCheckAsync
方法即可完成检查了。
后续增加只需要增加新的中间件代码即可,修改也只会影响单个中间件自己,是不是非常棒?
调用
我是使用的IOC去配置初始化的代码,利用它的延迟加载特性,可以在调用到用户数据时才会触发这些检查逻辑,因为每个项目使用的IOC框架可能不一样,我就没有再贴代码了,大体思路是:
- Context的构造方法中写需要的数据,由IOC负责注入
- 将所有中间件注册到IOC中,这样
CheckService
就能一次拿到所有中间件代码 CheckService
的构造方法中写需要中间件集合+排列顺序- IOC中配置用户数据的创建方法,在需要用户数据时即可触发
CheckService
的调用
总结
好处:非常适合做一些业务检查,扩展性维护性非常好
缺点:要额外编写一些代码,不懂这个套路的人阅读代码会有点困难
到第2个阶段,我们已经能解决"基础数据结构不变,但业务情况多变、无序”的情况。登陆认证业务中,一般需要的基础数据非常少,所以很适合这种方法。