本项目是一个面向开发者的 API 平台,提供 API 接口供开发者调用。用户通过注册登录,可以开通接口调用权限,并可以浏览和调用接口。每次调用都会进行统计,用户可以根据统计数据进行分析和优化。管理员可以发布接口、下线接口、接入接口,并可视化接口的调用情况和数据。本项目侧重于后端,涉及多种编程技巧和架构设计层面的知识。
一个提供 API 接口供开发者调用的平台: 管理员可以发布接口,同时统计分析各接口的调用情况,用户可以注册登录并开通接口调用权限,浏览接口以及在线进行调试,并可以使用 SDK 轻松地在代码中调用接口。该项目前端简单,后端丰富,涵盖编程技巧和架构设计等多个技术领域。
实现功能:
- 主页:浏览接口
- 管理员:接口管理
- 用户:在线调试
- 使用自己开发的客户端SDK,一行代码调用接口
包含内容: API 签名认证、网关、RPC、分布式等必学知识
- 初始化前端、后端
- 直接ant.design拉下来,注意不要用dev模式运行
- 项目瘦身(注意)
-
前端运行dev的时候报错显示
'cross-env' 不是内部或外部命令,也不是可运行的程序
好像是yarn没安装成功,重装了就好了
-
运行
i18n-remove
报错 exit code 1.去github的issue上找:antdesign官方的开源地址
列一下第二节的思路
- 新建一个xuanapi-interface项目
- 版本2.7.12
- 注解选择SpringWeb、Lombok、Spring Boot DevTools
- 创建模拟接口
- model层建user类
- controller层建NameController
- 注意三种请求接口方式:Get、Post(url+json传参)
- 重构application.yml
- 测试一下
我们现在是通过url去测试接口的,但是真正给用户用的时候肯定不是这种方式,而是选择在后端调用第三方api,目前HTTP 调用方式:HttpClient、RestTemplate 、第三方库(OKHTTP、Hutool)
- 开发调用接口
- 加Hutool依赖
- 建client层,重写三种请求接口方式
- 测试一下
- api签名认证
- user表加ak、sk,重新生成代码、重新加一条数据
- 在client传入ak、sk字段,请求头传到后端去
- 在main中传入ak、sk数据
- 在controller校验
- 测试一下
- 两个都debug运行才能断点成功
- 优化:防止重放请求
- 建个utils层搞加密算法
- client多传时间戳、随机数和加密后的sk
- controller页改变检验方式
现在已经实现了调用接口,但是很麻烦欸,所以我们打包搞成sdk,这样开发者只需要关心调用哪些接口传那些参数
- 新建xuanapi-client-sdk
- Lombok、Spring Configuration Processor
- pom.xml删掉bulid
- 把client、model、controlle复制过来
- 创建client配置类,传入ak、sk,新建bean
- 新建META-INF
- spring.factories
- 把client配置类指定为配置类
- maven install,按小闪电退出test
- sdk放进interface项目
- 测试一下
第三节思路:
- 发布/下线接口
- 判断接口使用可以调用(模拟)
- InterfaceInfoStatusEnum
- 编写online和offonline
- 前端查看接口需要改一下(有Bug)
- register新增ak、sk注入
- 测试真实数据在线调用接口
- 接口信息里面没有请求参数、、、、、重新生成改表
好啦现在停一下,理一下现在的逻辑
我们目前有三个项目:前端项目(刚刚写的点击调用)、接口平台的后端项目backend、以及提供给开发者的模拟接口项目sdk。
- 在线调用接口(注意要运行interface接口)
mark一下是怎么找到这两个bug的
-
首先是前端分页接口报404错误,发现add接口同样出错,这个时候是先去怀疑了前端哪里出错了(因为上次就是前端的一个参数写错了,但是前端我也看不懂捏,是从url的一个个参数入手的,先是token,发现没错,直接访问接口不行,但是后端接口文档运行又是可以的,进一步怀疑url,发现是前后端的路径不一样、、、一个是interface,一个是interfaceInfo、、、、
-
改了上面的问题后分页可以显示,但是add接口还是出错,显示validInterfaceInfo方法的时候name为null,往上一步就是BeanUtils.copyProperties,果然是参数传错了(悲
- 调用次数统计
- 就是加个表啦
- 网关知识点讲解(阅读官方文档)
网关的作用:
- 路由:起到转发的作用,比如有接口 A 和接口 B,网关会记录这些信息,根据用户访问的地址和参数,转发请求到对应的接口(服务器 / 集群)。 /a => 接口A /b => 接口B 参考文档:The After Route Predicate Factory。
- 负载均衡:在路由的基础上。 /c => 服务 A / 集群 A(随机转发到其中的某一个机器) uri 从固定地址改成 lb:xxxx
- 统一处理跨域:网关统一处理跨域,不用在每个项目里单独处理。 参考文档:Global CORS Configuration。
- 发布控制:灰度发布,比如上线新接口,先给新接口分配 20% 的流量,老接口 80%,再慢慢调整比重。 参考文档:The Weight Route Predicate Factory。
- 流量染色:给请求(流量)添加一些标识,一般是设置请求头中,添加新的请求头。 参考文档:TheAddRequestHeaderGatewayFilterFactory。
- 全局染色:Default Filters。
- 统一接口保护:
- 统一业务处理:把一些每个项目中都要做的通用逻辑放到上层(网关),统一处理,比如本项目的次数统计。
- 统一鉴权:判断用户是否有权限进行操作,无论访问什么接口,我都统一去判断权限,不用重复写。
- 访问控制:黑白名单,比如限制 DDOS IP。
- 统一日志:统一的请求、响应信息记录。
- 统一文档:将下游项目的文档进行聚合,在一个页面统一查看。 建议用:knife4j 文档。
阅读SpringCloudGateWay官方文档
业务逻辑:
-
用户发请求到API网关
-
请求日志
- 就在全局拦截器里面写
ServerHttpRequest request = exchange.getRequest(); System.out.println("请求id:" + request.getId()); System.out.println("请求路径:" + request.getPath()); System.out.println("请求方法:" + request.getMethodValue()); System.out.println("请求参数:" + request.getQueryParams()); System.out.println("请求头:" + request.getHeaders()); System.out.println("请求来源地址" + request.getRemoteAddress().toString());
-
黑白名单
String sourceAddress = request.getLocalAddress().getHostString(); ServerHttpResponse response = exchange.getResponse(); if (!IP_WHITE_LIST.contains(sourceAddress)) { // 403 禁止访问 response.setStatusCode(HttpStatus.FORBIDDEN); return response.setComplete(); }
-
用户鉴权(ak、sk)
// 3. 用户鉴权:aksk HttpHeaders headers = request.getHeaders(); String accessKey = headers.getFirst("accessKey"); // 不能直接获取secretKey,这样很容易被截胡到 // String secretKey = httpServletRequest.getHeader("secretKey"); String body = headers.getFirst("body"); String nonce = headers.getFirst("nonce"); String sign = headers.getFirst("sign"); String timestamp = headers.getFirst("timestamp"); if (!accessKey.equals("123")) { throw new RuntimeException("无权限"); } // todo:检测随机数()正常来说应该是放进redis或者数据库中,这里简单处理 if (Integer.parseInt(nonce) > 10000) { throw new RuntimeException("随机数错误"); } // 检验时间戳与当前时间差距不能超过5分钟 if (Long.parseLong(timestamp) - System.currentTimeMillis() / 1000 > 5 * 60) { throw new RuntimeException("请求超时"); } // 这个时候再检验sign,todo:从数据库中查出secretKey,并且和现在的进行比较 String serverSign = SignUtils.genSign(body, "123"); if (!sign.equals(serverSign)) { throw new RuntimeException("无权限"); }
-
请求的模拟接口是否存在
-
请求转发,调用模拟接口
-
响应日志
-
成功的话次数+1
全局拦截器
@Slf4j
@Component// 哇趣别忘了这个
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("custom global filter");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
Bug1:注意client项目不要引入spring-boot-starter-web
,因为SpringCloudGateway和SpringMVC有冲突,我们的gateway项目引入这个client的话相当于引入了SpringMvc,就会报错
BUG2:在线请求接口的时候一直报错415,一开始一直觉得是contenttype的问题 后面跑去backend项目打了下断点才发现requestprarm根本没取到 马上翻到前端去找,果然是参数命名的问题 改了之后显示无权限,这个应该是accesskey传过来的时候加密了所以和数据库之前写的不一样,改成加密前的就跑通了
RPC:作用:像调用本地方法一样调用远程方法。
- 提供者(Producer/Provider): 首先,我们需要一个项目来提供方法,这个项目被称为提供者。它的主要任务是为其他人提供已经写好的代码,让其他人可以使用。举例来说,我们可以提供一个名为 invokeCount 的方法。
- 调用方(Invoker/Consumer): 一旦服务提供者提供了服务,调用方需要能够找到这个服务的位置。这就需要一个存储,用于存储已提供的方法,调用方需要知道提供者的地址和 invokeCount 方法,这里需要一个公共的存储。
- 存储: 这是一个公共存储,用于存储提供者提供的方法信息。调用方可以从这个存储中获取提供者的地址和方法信息,例如,提供者的地址可能是 123.123.123.1,而方法是 invokeCount,这些信息会存储在这个存储器中。
调用方可以从存储中获取信息后,就知道调用 invokeCount 方法时需要访问 123.123.123.1,这就是 RPC 的基本流程,这三个角色构成了整个 RPC 模型。
Dubbo
Nacos:Nacos 快速开始
Dubbo 配置Nacos:https://dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/registry/nacos/
写个demo在gateway项目里面调用backend的方法
后端网关业务:
- 实际情况应该是去数据库中查是否已分配给用户秘钥(ak、sk是否合法)
- 先根据 accessKey 判断用户是否存在,查到 secretKey
- 对比 secretKey 和用户传的加密后的 secretKey 是否一致
- 从数据库中查询模拟接口是否存在,以及请求方法是否匹配(还可以校验请求参数)
- 调用成功,接口调用次数 + 1 invokeCount
创建一个公共服务:xuanapi-common
目的:让方法、实体类在多个项目间复用,减少重复编写。
步骤:
- 新建干净的 maven 项目,只保留必要的公共依赖
- 抽取 service 和实体类
- install 本地 maven 包
- 让服务提供者引入 common 包,测试是否正常运行
- 让服务消费者引入 common 包
wokao太乱了我脑子都要炸了
我们的网关没有校验用户是否还有调用次数,这个一定要放在发送请求前
上传自己的starter到中央maven仓库中