提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
现状是学过很多课程,很多框架,很多源码,但是总在用的时候就发现学过的东西都没办法得心应手的运用.写过很多设计模式的demo,实际项目中基本上没有用起来。想一想原因,还是学过的东西停留在最表层,没有深入到其本质.因此写这篇文章作为后面学习的一个标准实践,学的东西一定要实践起来,从0到1想一想.
一、场景是什么?
1. 场景问题
最近在做统一门户的集成,主要是完成A系统的前端嵌入到统一门户中去,A系统的用户默认是保持和统一门户的用户是同步的。嵌入后的结果是A系统的用户在统一门户完成登录后,通过跳转系统菜单到A系统。其中两方系统的架构方式如下:
门户基本架构:
A系统架构:
授权认证的技术栈主要是: Spring Security + oauth2 + redis
网关承担的职责如下:
这里再详细描述一下A系统接入统一门户的改造点及原因:
首先A系统的登录功能被去掉了,只能使用统一门户的登录功能,意味着A系统的token也将无法获取,意味着A系统的交易经过网关的时候token验证无法通过;
涉及系统集成时的改造时有规则如下:
1: 尽量不改动已有代码(改动越小,影响越小);
2: 尽量使用扩展机制进行集成;
3: 尽量做新增不做代码的修改;
通过分析,A系统集成到统一门户需要做的事情如下:
1: 如果要保持A系统架构不变,那么必须要保留A系统的登录功能;
2: 在保留A系统登录功能的情况下,统一门户的用户不能看到A系统的登录界面,所以只能做前端做一个loading,在loading里面做自动登录的逻辑;
3: 于是在A系统集成统一门户之后,会存在2个token,一个是统一门户的,一个是A系统;
4: 那么问题来了, 如何保证A系统的token和统一门户的同步呢?
2个token的生成如下:
2. 解决方案一
通过绑定门户token和A系统token关系,后台进行定时任务判断只要有其中一个token失效,就让2个token同时失效;
弊端:
1: 定时任务需要去不停的调用接口,浪费CPU等资源,做的都是无意义的循环逻辑;
2: 定时任务会频繁地调用接口,浪费网络资源;
3: 定时任务本身和token的失效时间存在误差,不能精确清除失效token
4: 需要使用容器在后端内存存储双token的绑定关系并且需要维护;
综上考虑,使用定时任务并不可取;
3. 解决方案二
首先定一个约定,在A集成到统一门户之后,按照道理说,A系统的token其实是无意义的存在,只不过为了维护原框架的不变性,才保留下来的,所以我们可以规定门户的token优先级高于A系统的token, 意思如下:
上图中隐藏了一个点是:
当门户的token未过期的时候,而A系统的token却过期了,按照正常的情况,A系统应该继续能做交易,所以需要将A系统的token续期;
还有一个问题是: A系统内部的交易并不会对门户的token进行续期,所以时间已久,门户的token会过期,所以网关需要对门户的token做一个续期的操作,图示:
这个插件主要是通过开发小jar包的方式实现的;
下面这个插件机制开发方式可以作为基础平台中的一种方式,对外提供了高度的可定制接口(符合开闭原则);
二、抽象过程?
1.抽象流程
网关本身就是一个过滤器链(责任链设计模式);
先画一张图表明演示的责任链模式:
代码如下:
1: Filter核心接口定义
public interface Filter {
/**
*
* @param contextExchange
* @param chain
*/
void filter(ServletContextExchange contextExchange, FilterChain chain);
}
2: 核心上下文对象定义
public class ServletContextExchange {
private Request request;
private Response response;
public ServletContextExchange() {
}
public ServletContextExchange(Request request, Response response) {
this.request = request;
this.response = response;
}
public Request getRequest() {
return request;
}
public void setRequest(Request request) {
this.request = request;
}
public Response getResponse() {
return response;
}
public void setResponse(Response response) {
this.response = response;
}
}
3: 核心FilterChain定义
/**
* @author
* @version V1.0
* @package com.hd.mybatis.simple.gateway
* @date 2022/9/18 9:52
* @desc 链式容器对象
* 需要对Filter的存储容器进行维护和遍历访问
*
* 这里面有Filter对象 所以不能放着玩的吧, 所以理所当然地存在方法:
* filter.filter(context) -> 推出有一个 Context 上下文对象;
* 并且 Context是必须存在的; todo 就算 Context是必须存在的, 如果这样用还是耦合太高了
* 所以使用 参数传参的方式进行 doFilter
* 后面可以优化成一个接口 提供默认实现玩玩;
*
* @copyright @ 2022-2030 hduong
*/
public class FilterChain {
/**
* 请求响应上下文对象;
*
*/
// private ServletContextExchange contextExchange;
private List<Filter> filterList = new ArrayList<>();
private int index = 0;
//
// public FilterChain(ServletContextExchange contextExchange) {
// this.contextExchange = contextExchange;
// }
// public FilterChain(ServletContextExchange contextExchange, List<Filter> filterList) {
// this.contextExchange = contextExchange;
// this.filterList = filterList;
// }
/**
* 新增过滤器
* 小技巧 -.> 链式调用
* @param filter
*/
public void addFilter(Filter filter) {
this.filterList.add(filter);
}
/**
* 支持链式调用
* @param filter
* @return
*/
public FilterChain filter(Filter filter) {
this.filterList.add(filter);
return this;
}
/**
*
* @param servletContextExchange
*/
public void doFilter(ServletContextExchange servletContextExchange) {
//1: 遍历过滤器链
//2: 走过滤器的 filter方法逻辑
//3: 需要记录走到哪个位置了
//相当于记录开始和结束点
if (index == filterList.size()) {
System.out.println("链路执行完毕.");
return;
}
Filter filter = filterList.get(index);
//继续下一个过滤器
index++;
filter.filter(servletContextExchange, this);
}
}
4: Request对象定义
public class Header {
private String authorization;
private String authValue;
public Header(String authorization) {
this.authorization = authorization;
}
public Header() {
}
public String getAuthorization() {
return authorization;
}
public void setAuthorization(String authorization) {
this.authorization = authorization;
}
public String getAuthValue() {
return authValue;
}
public void setAuthValue(String authValue) {
this.authValue = authValue;
}
}
5: Response对象定义
public class Response {
private String status;
private String message;
public Response() {
}
public Response(String status, String message) {
this.status = status;
this.message = message;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
6: Header对象定义
public class Header {
private String authorization;
private String authValue;
public Header(String authorization) {
this.authorization = authorization;
}
public Header() {
}
public String getAuthorization() {
return authorization;
}
public void setAuthorization(String authorization) {
this.authorization = authorization;
}
public String getAuthValue() {
return authValue;
}
public void setAuthValue(String authValue) {
this.authValue = authValue;
}
}
7: SecurityFilter过滤器实现
public class SecurityFilter implements Filter {
/**
*
* @param contextExchange
* @param chain
*/
@Override
public void filter(ServletContextExchange contextExchange, FilterChain chain) {
Request request = contextExchange.getRequest();
System.out.println("1- 开始安全校验");
checkRequest(request);
System.out.println("1- 安全验证通过, 继续下一个过滤器处理!");
chain.doFilter(contextExchange);
}
/**
*
* @param request
*/
private void checkRequest(Request request) {
if (request == null) {
throw new RuntimeException("请求对象为空!");
}
String name = request.getName();
if (!"admin".equals(name)) {
throw new RuntimeException("假设admin才能发起请求!");
}
}
}
8: TokenFilter过滤器实现
public class TokenFilter implements Filter {
/**
* 模拟 token缓存
*/
private static Map<String, String> tokenCache = new HashMap<>();
static {
tokenCache.put("authorization", "1111");
tokenCache.put("authorization2", "2222");
tokenCache.put("authorization3", "3333");
}
/**
* 完成token校验
* @param contextExchange
* @param chain
*/
@Override
public void filter(ServletContextExchange contextExchange, FilterChain chain) {
System.out.println("2- 开始token校验");
Request request = contextExchange.getRequest();
checkRequest(request);
System.out.println("2- token校验通过,继续下一个过滤器处理");
chain.doFilter(contextExchange);
}
/**
*
* @param request
*/
private void checkRequest(Request request) {
Header header = request.getHeader();
if (header == null) {
throw new RuntimeException("授权未通过!Header为null!");
}
String authorization = header.getAuthorization();
if (authorization == null || "".equals(authorization)) {
throw new RuntimeException("authorization为null!");
}
String token = tokenCache.get(authorization);
if (token == null || "".equals(token.trim())) {
throw new RuntimeException("授权未通过, 不是登录的token!");
}
}
}
9: 最后一个演示DemoController实现
/**
* @author
* @version V1.0
* @package com.hd.mybatis.simple.gateway.controller
* @date 2022/9/18 10:40
* @desc
* 演示 责任链的 请求使用
* 这里将 controller放到最后的一个责任链对象中;
* 在 SpringMVC源码中是会存在映射关系的:
* url -> controller 的 method方法;
*
* @copyright @ 2022-2030 hduong
*/
public class DemoController implements Filter {
/**
* 执行业务逻辑:
* 暂时放到过滤器链 中 ;
*
* @param contextExchange
* @param chain
*/
@Override
public void filter(ServletContextExchange contextExchange, FilterChain chain) {
//打印请求参数即可
Request request = contextExchange.getRequest();
String params = request.getParams();
System.out.println("结果- 执行业务逻辑: " + params);
}
}
10: 演示结果如下:
1- 开始安全校验
1- 安全验证通过, 继续下一个过滤器处理!
2- 开始token校验
2- token校验通过,继续下一个过滤器处理
结果- 执行业务逻辑: 请求参数待处理.
注: 这里仅仅是演示了一下责任链模式.
设计模式的运用需要一种更强大的抽象能力,追其本质还是面向对象的三大特征:
封装 (个人理解的封装)
如果我们不局限于定义, 一切皆可封装. 假如我们首先认为一切皆可封装,那么就先去封装;
比如做一个东西时,我们不管三七二十一,先封装起来。后面发现封装的太大了,大乱了,于是就要进行分离,那么如何进行分离呢? 主要分为两类:
第一类就是逻辑上的共性分离 -> 引申出继承;
第二类就是抽象上的共性分离 -> 引出多态;
继承(个人理解的封装)
继承就是封装的一种体现, 将公共的东西都写到一起,后面的人用的时候只需要继承一下即可.
多态
多态就是多种形态,一个具体的事物会有多种形态吗? 正常思考下是不会这么认为的。
那么就是说抽象的事物才会有多种形态,抽象的事物会有多种具体表现的形态。
综上所述: 个人还是认为面向对象编程用到极致,本质就是封装!
注: (个人的谬论,不作他人参考!)
结合spring可以通过定义一个只有接口的工程,然后被其他工程引用,就可以开发相关的插件机制;
这块在spring相关的文章会详述。
mybatis的插件机制用到的动态代理模式, 注解, 责任链模式等等
参考: 非常不错的文章
https://www.cnblogs.com/qdhxhz/p/11390778.html
https://www.jianshu.com/p/b82d0a95b2f3
2.模拟方案一实现
方案一被pass,未做,仅仅作为思考过程记录.
3.模拟方案二实现
方案二模拟实现待后续…
引入责任链
搜索个多个责任链的源码,关于这块还有许多东西值得实践.
1. 审批流责任链(职责集中在链路上的一个对象)
经典案例: 审批流程
贴图如下:
2. 请求处理型责任链(职责分散在链路上的每一个对象)
经典案例: 过滤器等
参考上面的网关责任链,以下待办:
- 责任链综合其他模式及场景实践
- 责任链扩展的Pipeline案例
- 责任链结合策略工厂模式案例
- 责任链在Zookeeper中的应用案例
- 责任链在Security中结合代理的应用案例
- 责任链在Netty中的应用案例
- 责任链在Dubbo中的应用案例
- 其他