红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【杂项】API与RESTful API

curl

终端命令curl用于在终端内向网站发送请求并获取响应

适合访问、测试API

匿名调用:

1
curl 'http://example.com'

非匿名调用:

如调用大模型API,需要获取API key,根据官方文档的样例调用API

API

API即应用程序编程接口,用于将自己的能力通过一个固定的入口持续对外提供,让别的程序来调用

Interface(接口)

接口用于抽象实现,是后端设计的一套供给第三方使用的方法

定义一个接口时,需要写好接口路径和接口方法名的映射,前端通过接口路径来调用方法,进而和后端通信

注意:像HTTP请求这种通信方式是单向的,比如只能前端主动发起请求,而后端不可以主动和前端通信;若后端想主动和前端通信,需要通过双向通信协议如websocket

例:

一个获取商品列表的接口,路径设置为/api/getItemList,接口方法名为:getItemList,这样前端就可以通过请求上述路径来调用上述方法名的方法,进而后端会做相关逻辑处理

接口组成

  • 接口路径
  • 接口描述
  • 请求类型:协议的请求方法,如HTTP协议常用GET/POST
  • 请求参数:后台规定好前端需要传的参数结构
  • 返回结构:后台规定好返回给前端的数据结构,一般返回结构包括三个部分:返回码、错误信息和正确数据

定义接口

以Golang为例,interface定义在service块的前面,在service块中通过@service注解来引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义 interface
interface 接口名 {
    // 这里可以放一些公共的注解,如 @doc、@handler 的前缀等
}

// 在 service 中应用
service 服务名 {
    @server(
        // 引用 interface,通常写 interface: 接口名
        interface: 接口名
    )
    // 这个@server下面的所有handler都会受该interface的控制
    @handler 处理器名
    post /api/user/login (LoginReq) returns (LoginResp)
}

REST API

REST,即Representational State Transfer,是一种设计网络程序API的风格,而REST API是基于REST原则构建的Web服务接口,又称RESTful

RESTful

如何满足RESTful?

例如现在需要从client端发送请求到server端,我们常用HTTP协议发送请求,而HTTP是无状态的,如果你想要维护某些状态,你需要使用Header将这些状态和每个请求一起发送,这就是REST约束之一:无状态性(Stateless)

所有的数据都会被视作资源(resource),满足CRUD,可以通过URI标识,向服务器发送一个GET请求,服务器返回响应,Body部分一般用JSON表示,这被称为资源导向(Resource-based)

REST API延续使用HTTP方法(GET, POST, PUT, PATCH, DELETE)来明确说明请求意图,即统一接口(Uniform Interface)

另外,我们可以用HTTP缓存机制,如最后修改和电子标签,我们可以标记响应为可缓存或不可缓存,由用户决定,避免提出不必要的请求,这是可缓存性(Cacheable)

设计原则

URI设计原则:

  • 使用名词而非动词表示资源
  • 使用小写字母和连字符
  • 避免文件扩展名
  • 分层次表示关系,如/users/{id}/orders

HTTP方法使用:

  • 正确使用HTTP标准方法
  • 注意GET、PUT、DELETE是幂等操作,多次调用产生相同结果
  • 对于批量操作,考虑使用POST而非PUT,PUT通常期望客户端明确资源标识符

版本控制:

在URL或请求头中包含API版本信息

  • URL路径版本:/v1/users
  • 查询参数版本:/api/users?version=1
  • 请求头:Accept: application/vnd.myapi.v1+json
  • URL路径版本直观但URI不稳定,请求头版本保持URI稳定,但对客户端来说不够直观
  • 版本迭代中遵循语义化版本控制原则
    • 向后兼容的变更使用次要版本号
    • 不兼容的变更使用主要版本号
  • 新旧版本之间提供迁移期,允许客户端平滑过渡
  • 文档中明确标注每个API版本的生命周期状态

有效分页:

  • 使用limit,offset或page,size参数
  • 在响应中包含分页元数据
  • 在处理大型数据或频繁更新的数据时考虑使用基于游标的分页
  • 设置合理分页值
  • 在分页响应中提供HATEOAS链接,如下例中的next和prev

例如:

1
2
GET /products?limit=20&offset=40
GET /products?page=3&size=20
1
2
3
4
5
6
7
8
9
10
11
{
  "data": [...],
  "pagination": {
    "total": 523,
    "pages": 27,
    "current_page": 3,
    "per_page": 20,
    "next": "/products?page=4&size=20",
    "prev": "/products?page=2&size=20"
  }
}

游标分页是基于游标的分页方式,使用上一页最后一条记录的标识来确定下一页的起始数据

如靠自增主键id来确定起始位置:

1
select * from table_name where id>100 order by id limit 10;

这样可以实现从上一页最后一条记录的主键id=100之后开始查询10条记录

过滤、排序与搜索:

对集合资源提供查询参数:

  • 过滤:/user?role=admin
  • 排序:/user?order=-created_at
  • 搜索:/user?search=john
  • 支持部分匹配和模糊搜索:?name=like:john
  • 支持字段选择:?fields=id,name,email
  • 提供预定义的过滤器:?filter=recent

设计一致的相应结构:

  • 使用包装对象,区分数据与元数据
  • 保持错误相应格式一致

实现HATEOAS原则:

在响应中包含相关资源链接,使API具有自描述性

  • 使用标准化的链接关系名称
  • 包含链接上下文信息,如HTTP方法
  • 根据用户权限动态生成链接,只显示当前用户可用操作
1
2
3
4
5
6
7
8
9
10
11
12
{
  "data": {
    "id": 123,
    "name": "John Doe"
  },
  "links": {
    "self": "/users/123",
    "orders": "/users/123/orders",
    "update": {"href": "/users/123", "method": "PUT"},
    "delete": {"href": "/users/123", "method": "DELETE"}
  }
}

安全性:

  • API Key:服务器间通信
  • OAuth2.0:第三方授权
  • JWT:无状态认证
  • 需要为不同API的使用场景选择合适的授权流程
    • C to S:授权码流程
    • S to S:客户端凭证流程
  • 实现细粒度的权限控制,遵循最小权限原则

最小权限原则:每个程序和系统用户都应该具有完成任务所必须的最小权限集合,即赋予每一个合法动作最小的权限,来保护数据和功能

即分配他能满足需求所需要的最小的权限

实现速率限制和节流:

  • 使用请求速率限制保护API
  • 在响应头中提供限制信息
1
2
3
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1623760800
  • 实现多层级限制:按IP地址、用户/API、资源端点
  • 使用令牌桶或漏桶算法处理突发流量
  • 实现自适应节流,根据系统负载动态调整限制

适当使用缓存:

  • 使用ETags和If-None-Match头
  • 设置适当的Cache-Control指令
1
2
Cache-Control: max-age=3600, must-revalidate
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
  • 根据资源类型设置不同缓存策略:
    • 静态内容:较长的max-age
    • 个人资料:较短的max-age或private指令
    • 频繁变化的数据:使用ETag
  • 实现条件请求减少带宽使用(If-Modified-Since, If-None-Match)
  • 考虑在API网关或CDN层面实现缓存
  • 提供缓存失效机制
  • 使用缓存标签进行细粒度缓存管理