API开放平台

项目介绍

做一个提供API接口调用的平台,用户可以注册登录,开通接口调用权限。用户可以使用接口,并且每次调用会进行统计。管理员可以发布接口、下线接口、接入接口,以及可视化接口的调用情况、数据

背景:

  1. 前端开发需要用到后台接口
  2. 使用现成的接口(https://api.btstu.cn/)

image-20240604181727464

业务流程

做一个API接口平台

  1. 防止攻击(安全性)
  2. 不能随便调用(限制、开通)
  3. 计费
  4. 统计调用次数
  5. 流量保护
  6. API接入

1、SDK的外语全称是Software Development Kit,中文为:软件开发工具包,一般都是一些软件工程师为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件时的开发工具的集合;

  2、可以将其理解为,由第三方服务商提供的实现软件产品某项功能的工具包,里面一般以集合kpi和文档、范例、工具的形式出现,也就是由很多类型文件的集合;

技术选型

前端

Ant Design Pro

React

Ant Design Procomoponents

Umi

Umi Request (Axios封装)

后端

Java Spring Boot

Spring Boot Starter(SDK开发)

???(网关、限流、日志实现)

项目计划

第一阶段

  • 项目介绍、设计、技术选型
  • 基础项目搭建
  • 接口管理
  • 用户查看接口

第二阶段

  • 接口调用
  • 接口文档展示、接口在线调用
  • 保证调用的安全性(API签名认证)
  • 客户端SDK的开发

第三阶段

  • 统计用户调用次数
  • 限流、计费、日志、开通

第四阶段

  • 提供可视化平台,用图表的方式展示所有接口的调用情况,便于调整
  • 自己实现:预警

需求分析

1、管理员可以对接口信息进行增删查改

2、用户可以访问前台,查看接口信息

数据库表设计

接口信息表

id

userId 创建人id

name 接口名称

description 描述

url 请求地址

method 请求类型

requestHeader 请求头

responseHeader 响应头

status 接口状态 0-关闭 1-开启

  • isDelete

  • creatTime

  • updateTime

使用其他类似的Controller进行快速开发(copy)

项目脚手架

前端:ant design pro脚手架

基础功能

增删查改、登录(以及校验)

前端接口调用:oneapi插件自动生成

openapi规范

第二期

模拟接口项目myapi-interface

提供三个模拟接口

  1. GET接口
  2. POST接口(url传参)
  3. POST接口(restful)

调用接口

几种HTTP调用方式:

  1. HttpClient
  2. RestTemplate
  3. 第三方库(OKHttp,Hutool)

https://hutool.cn/docs/#/http/Http%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%B7%A5%E5%85%B7%E7%B1%BB-HttpUtil

API 签名认证

本质:

  1. 签发签名
  2. 使用签名(校验)

为什么需要?

  1. 保证安全性,不能随便一个人调用

怎么实现?

通过http request header 头传递参数

参数1:accessKey: 调用标识 (复杂无序无规律)

参数2:secretKey: 密钥 (不放到请求头中)

(类比用户名和密码,区别:ak,sk是无状态的)

千万不能把密钥直接在服务器之间传递,也可能会被拦截

参数3:用户参数

参数4:sign

加密方式:对称加密、非对称加密、md5签名(不可逆)

用户参数: abc + 密钥 => 签名生成算法(MD5,Hmac) = > 不可解密的值

abc + abcdefg =》 osidjfoasipdf

那服务端怎么知道签名是否正确?

服务端用一模一样的参数和算法去生成签名,只要和用户传的一致,就表示一致。

怎么防重放?

参数5:加nonce 随机数,只能用一次

​ 但服务端要保存用过的随机数

参数6:加一个timestamp 时间戳。校验时间戳是否过期。

API 签名认证是一个很灵活的设计,具体要有哪些参数、参数名如何一定要根据场景来。(比如userId、appId、version)

难道每次开发者调用接口都要自己写签名算法?

开发一个简单易用的SDK

理想情况:开发者只需要关心调用哪些接口,传递哪些参数,就跟调用自己写的代码一样。

开发starter的好处:

  1. 开发者引入后,可以直接在application.yml中写配置。自动创建客户端

自动生成代码提示

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version> 3.2.2</version>
</dependency>

// TODO 之后可以把打好的包上传到maven仓库

第三期

  1. 开发接口发布/下线的功能(管理员)
  2. 前端去浏览接口、查看接口文档、申请签名(注册)
  3. 在线调试(用户)
  4. 统计用户调用接口次数
  5. 优化系统-API网关

开发接口发布/下线功能(管理员)

后台接口:

发布接口:

  1. 校验接口是否存在
  2. 判断接口是否可用
  3. 修改接口数据库中状态字段为1

下线接口:

  1. 校验接口是否存在
  2. 修改接口数据库中状态字段为0

扩展:用户可以申请更换签名

在线调用

请求参数的类型(直接用JSON类型)

流程:

  1. 前端将用户输入的请求参数和要测试的接口id发给平台后端1
  2. 在调用前可以做一些校验
  3. 平台后端去调用模拟接口

TODO

  1. 判断该接口是否可以调用时,由固定方法名改为请求地址
  2. 用户测试接口也是这样优化
  3. 模拟接口改为从数据库校验 ak ,sk

第四阶段

  1. 开发接口调用次数的统计
  2. 优化整个系统的架构(API 网关)
    1. 网关是什么?
    2. 网关作用?
    3. 网关的应用场景以及实现
    4. 结合业务去应用网关

接口调用次数统计

需求:

  1. 用户每次调用接口成功,次数 + 1
  2. 给用户分配或用户自主申请接口调用次数

业务流程:

  1. 用户调用接口(之前已完成)
  2. 修改数据库,调用次数+1

设计库表

哪个用户?哪个接口?

用户 <=> 接口(N = N)

用户调用接口关系表:


create table if not exists API_db.`user_interface_info`
(
`id` bigint not null auto_increment comment '主键' primary key,
`userId` bigint not null comment '调用用户id',
`interfaceInfoId` bigint not null comment '接口id',
`totalNum` int default 0 not null comment '总调用次数',
`leftNum` int default 0 not null comment '剩余调用次数',
`status` int default 0 not null comment '0-正常 1-禁用',
`createTime` datetime default CURRENT_TIMESTAMP not null comment '创建时间',
`updateTime` datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
`isDelete` tinyint default 0 not null comment '是否删除(0-未删, 1-已删)'
) comment '用户调用接口关系表';

开发增删查改功能给管理员

用户调用接口后次数加1

问题:

如果每个接口的方法都写调用次数+1,是不是比较麻烦?

致命问题:接口开发者需要自己去统计

  1. AOP
  2. 写一个通用方法,用到就调用
  3. servlet 拦截器

AOP,独立于接口,在每个接口调用后统计次数+1

缺点:只存在于单个项目中,如果每个团队都开发自己的模拟接口,都需要调用AOP所在的项目

网关

什么是网关?理解为门卫老大爷,需要统一经过审查才能进门。

网关优点:统一去进行一些操作、处理一些问题

作用

  1. 路由
  2. 负载均衡
  3. 鉴权
  4. 跨域
  5. 缓存
  6. 流量染色
  7. 访问控制
  8. 统一业务处理
  9. 发布控制
  10. 接口保护
    1. 限制请求
    2. 消息脱敏
    3. 降级(熔断)
    4. 限流: 学习令牌桶算法,学习redisRateLimiter
    5. 超时时间
  11. 统一日志
  12. 统一文档

路由

起到转发的作用,比如有接口a,接口b。网关会记录这些信息,根据用户访问的地址和参数,

转发请求到对应的接口(服务器/集群)

/a => interface a

/b => interface b

/c => interface c

断言路由 predicate

负载均衡

在路由的基础上

/c=> 服务a/集群a

统一鉴权

判断用户是否有权限进行操作。无论访问什么接口,我都去统一去判断权限,不用重复写。

统一跨域

网关统一处理跨一,不用在每个项目里单独处理

CORS Configuration :: Spring Cloud Gateway

统一业务处理

AOP plus版?

把一些项目中都要做的通用1逻辑放到上层(网关)。统一处理,例如本次的统计调用次数

访问控制

黑白名单,比如限制DDOS IP

发布控制

灰度发布,比如上线新接口,先给新接口分配20%的流量,然后慢慢调正比例

version1.0 version2.0 等2.0版本稳定后,逐渐给更多用户使用,然后逐渐覆盖迭代掉1.0

spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2

流量染色

给请求(流量)添加一些标识,一般是设置请求头中,添加新的请求头。链路追踪

例如:

AddRequestHeader GatewayFilter Factory :: Spring Cloud Gateway

全局染色:

Default Filters :: Spring Cloud Gateway

统一接口保护

  1. 限制请求
  2. 消息脱敏
  3. 降级(熔断)
  4. 限流
  5. 超时时间

统一日志

统一的请求、响应信息记录

统一文档

将下游项目的文档进行聚合,方便管理查看

网关的分类

  1. 业务网关(微服务网关):作用是将请求转发到不同的业务/项目/接口/服务
  2. 全局网关(接入层网关):作用是负载均衡、请求日志等,不和业务逻辑绑定

实现

  1. Nginx (全局网关)\ Kong网关(API网关)
  2. SpringCloud GateWay( 取代了Zuul ) 性能高, 可以用Java代码写逻辑

https://zhuanlan.zhihu.com/p/500587132

Spring Cloud GateWay

Spring Cloud Gateway(demo)

Spring Cloud Gateway(详细)

核心概念

路由(根据说明条件,转发请求到哪里)

断言(一组规则或者条件,用来确定如何转发规则)

过滤器:对一系列请求或者响应进行处理,比如添加请求、请求参数

image

官方文档:

Spring Cloud Gateway :: Spring Cloud Gateway

两种配置方式

  1. 配置式(方便、规范)
    1. 简化版
    2. 全称
  2. 编程式(灵活、相对麻烦)

image-20240608184013176

可以保护后台接口url

建议开启日志


logging:
level:
org:
springframework:
cloud:
gateway: trace

断言有很多规则

  1. After
  2. Before
  3. …..

image-20240608184151217

推荐自己先好好看看官方文档,知道大概有哪些东西。

顺便记得把simple demo看看

spring-cloud/spring-cloud-gateway: An API Gateway built on Spring Framework and Spring Boot providing routing and more. (github.com)

第五阶段:

  1. 实现统一的用户鉴权。把API网关应用到项目中
  2. 完善功能

要用到的特性

  1. 路由 (转发请求到模拟接口项目)
  2. 负载均衡
  3. 鉴权 (accessKey,secretKey)
  4. 跨域 (看情况)
  5. 流量染色 (记录请求是否为网关来的)
  6. 访问控制 (黑白名单)
  7. 统一业务处理 (每次请求接口后次数+1)
  8. 发布控制
  9. 接口保护
    1. 限制请求
    2. 消息脱敏
    3. 降级(熔断)
    4. 限流: 学习令牌桶算法、学习漏桶算法。学习redisRateLimiter
    5. 超时时间
  10. 统一日志 (记录每次的请求和响应日志)
  11. 统一文档

业务逻辑

  1. 用户发送请求到API网关

  2. 请求日志

  3. (黑白名单)

  4. 用户鉴权,判断Ak,Sk是否合法

  5. 请求的模拟接口是否存在?

  6. 请求转发、调用模拟接口

  7. 响应日志

  8. 调用成功,调用次数加1

  9. 调用失败,返回一个规范的错误码

具体实现

1.请求转发

使用前缀匹配断言:

spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://localhost:8123
predicates:
- Path=/api/**

所有路径为:/api/的请求进行转发。转发到 http ://localhost:8123/api/

比如请求:

http ://localhost:8090/api/name/get?name=moying

转发到

http ://localhost:8123/api/name/get?name=moying

2.全局过滤器

类似于AOP?建议看看流程图

@Bean
public GlobalFilter customFilter() {
return new CustomGlobalFilter();
}

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; //数字越小越优先
}
}

因为网关项目没有引入Mybatis等操作数据库的类库,如果操作较为复杂,可以由backend增删查改项目提供接口,我们直接调用。就不用重复写逻辑了

  • HTTP请求(用HTTPClient、RestTemplate、Feign、OpenFeign)
  • RPC(Dubbo)

问题

预期是等模拟接口调用完成,才记录响应日志、统计调用次数

但是现实是chain.filter方法立刻返回了,直到filter过滤器return后才调用了模拟接口。

原因是:chain.filter是个异步操作?(看上面的流程图)

解决方案:利用二response装饰者,增强原有对象response的能力

第六阶段

计划

  1. 补充完整网关的业务逻辑(怎么去操作数据库、怎么复用之前的方法?RPC)
  2. 完善系统、开发一个监控统计功能

网关业务逻辑

问题:网关项目比较纯净、没有操作数据库的包、并且还要调用我们之前写过的代码,维护麻烦

理想:直接请求到其他项目的方法

怎么调用其他项目的方法

  1. 复制代码和依赖、环境
  2. HTTP请求(提供一个接口供其他项目调用)
  3. RPC
  4. 把公共的代码打个jar包,其他项目去引用(客户端SDK)

HTTP请求怎么调用

  1. 提供方开发一个接口(地址、请求方法、参数、返回值)
  2. 调用方使用HTTP client之类的代码包去发送HTTP请求

RPC

作用:像调用本地方法一样调用远程方法

对开发者来说更透明,减少了很多的沟通成本

RPC向远程服务器发送请求时,未必要使用HTTP协议。比如还可以用TCP/IP。性能更高(内部服务更适用)

https://blog.csdn.net/NF_ALONG/article/details/139506951

Dubbo框架 (RPC实现)

GRPC、TRPC

官方文档:

https://dubbo-next.staged.apache.org/zh-cn/overview/mannual/java-sdk/

两种方式:

  1. Springboot代码(注解——编程式): 写Java接口,服务提供者和消费者都去引用这个接口
  2. IDL(接口调用语言):创建一个公共的接口文件,服务提供者和消费者都去读取这个文件,主要是支持不同语言去使用

整合运用(Dubbo Nacos)

  1. backend作为服务提供者
    1. 实际情况是从数据库中查询secretKey
    2. 调用成功,调用次数加1
    3. 从数据库从查询模拟接口是否存在,以及请求方法是否匹配(还可以校验请求参数
  2. gateway项目作为服务调用者,调用这三个方法

注意:

  1. 服务接口类必须要在同一个包下,建议是抽象出一个公共项目(放接口、实体类等)
  2. 设置注解(比如启动类的EnableDubbo、接口实现类和Bean引用的注解)
  3. 添加配置
  4. 消费者和提供者尽量引入同样版本的配置和依赖

整合nacos:

https://dubbo-next.staged.apache.org/zh-cn/overview/reference/integrations/nacos/

moying-backend (7592)提供基础服务、用户登录、查询接口、上线接口、下线接口等。

myapi-client-sdk(指向8090) 提供接口服务,后续添加固定接口服务时在此扩展。

common公共模块,提供实体类,以及用户、调用接口校验的接口类。

myapi-gateway (8090,指向8123)网关。通过断言去转换请求地址,用户鉴权。

myapi-interface(8123) 接口实现?

大概流程,登录基础等操作在backend模块完成,此模块还提供inner服务接口(校验功能— >提供给网关)。

前端请求backend地址的服务。

使用invoke方法调用接口时,通过使用自制的sdk包装的请求。将请求地址转为gateway模块。

gateway模块接受到接口调用请求的时候,经过一系列处理和校验(通过tcp请求用户校验、接口校验、用户接口校验功能)后

通过断言转路由,将请求地址转给interface,在interface模块中写实际的接口功能。

最后返回结果。