api
hello,大家好,我是杨杨!如今在我们后端开发程序员的工作中,写API接口就是家常便饭了。小编最近也是老写接口,写着写着让我产生了用它来作为创作灵感。好,话不多说,今天就来聊聊接口(API)设计规范。
当设计一个软件系统时,接口(API)的设计是至关重要的一环。一个好的接口设计可以增强系统的可维护性、可扩展性和可重用性。
设计API意味着提供有效的接口,可以帮助API使用者更好地了解、使用和集成,同时帮助人们有效地维护它。每个产品都需要使用手册,API也不例外。
在API领域,可以将设计视为服务器和客户端之间的协议进行建模。API协议可以帮助内部和外部的利益相关者理解应该做什么,以及如何更好地协同工作来构建一个出色的API。
当下最好遵循RESTful API设计原则
RESTful API是一种基于HTTP协议的API设计风格。它将资源作为基本的概念,并通过HTTP动词(GET、POST、PUT、DELETE等)来表示对资源的操作。在设计RESTful API时,需要遵循以下原则:
- 使用合适的HTTP方法来表示资源的操作,如GET表示获取资源,POST表示创建资源,PUT表示更新资源,DELETE表示删除资源。
- 使用URI来表示资源的位置,URI应该是短小且易于理解的,如/api/users。
- 使用HTTP状态码来表示请求的结果,如200表示请求成功,400表示请求参数有误,404表示请求的资源不存在等。
- 使用HTTP头部来表示附加信息,如Content-Type表示请求和响应的数据类型,Authorization表示授权信息等。
使用一致的命名规范
在设计接口时,需要使用一致的命名规范来表示资源、参数和操作。这样可以使接口更易于理解和使用。以下是一些命名规范的建议:
- 资源名应该使用复数形式,如/users表示多个用户资源。
- 使用动词+名词的方式来表示操作,如GET /users表示获取用户资源。
- 参数名应该使用小写字母和下划线分隔符,如user_id。
- 使用适当的缩写和单词缩写来简化命名,如使用“id”代替“identifier”。
定义清晰的错误处理机制
在设计接口时,需要考虑错误处理机制。一个好的错误处理机制应该包括以下几个方面:
- 返回合适的HTTP状态码来表示错误类型,如400表示请求参数错误,401表示未授权,500表示服务器内部错误等。
- 返回清晰的错误信息,包括错误代码和错误描述。
- 定义错误码和错误描述的映射关系,方便客户端进行处理。
确定合适的安全机制
在设计接口时,需要考虑安全机制。合适的安全机制可以保证接口的安全性和可靠性。以下是一些安全机制的建议:
- 使用HTTPS协议来保证数据的安全传输。
- 使用OAuth2.0授权机制来保护接口资源,限制未授权访问。
- 对敏感数据进行加密处理,如用户密码等。
接口升级
接口升级是 API 开发中的一个常见问题,随着业务需求的变化和技术的进步,接口的设计也需要不断地优化和升级。然而,接口的升级需要谨慎处理,以免影响现有的业务。
在接口升级的过程中,需要考虑以下几个方面:
- 兼容性:新版本的接口应该保持与旧版本的接口兼容,以确保现有的应用能够正常运行。如果新版本的接口与旧版本的接口不兼容,那么就需要在文档中明确告知用户,并给出迁移方案。
- 文档更新:在升级接口的同时,也需要更新相关的文档。文档应该包括接口的使用说明、参数列表、返回值等信息,并且应该与实际的接口保持一致。
- 测试验证:在升级接口之前,需要进行充分的测试验证。测试可以包括单元测试、集成测试、功能测试等,以确保新版本的接口能够正确地运行。
- 版本控制:在升级接口时,需要对不同版本的接口进行版本控制。这可以让用户明确地知道自己使用的是哪个版本的接口,并且可以让用户有机会在自己的时间范围内迁移到新版本的接口。
总的来说,接口升级是一个复杂的过程,需要在设计阶段就充分考虑接口的扩展性和兼容性。同时,升级过程中需要进行充分的测试和文档更新,并进行版本控制,以确保用户的正常使用。
细节
- 客户端IP白名单:在接口设计中,可以设置客户端IP白名单,仅允许指定的IP地址访问该接口,从而增加接口的安全性。
- IP限流:为了防止恶意请求或其他不合法请求,可以通过IP限流的方式限制同一IP在一定时间内访问接口的频率。
- 请求日志:接口应该记录请求日志,包括请求的URL、请求参数、请求时间等信息,以便于后续排查问题或性能优化。
- 接口的幂等性:接口应该具备幂等性,即多次请求同一接口,对于同一资源的操作结果应该保持一致。这样可以避免因为重复请求而引起的业务问题。
- 公共参数:接口设计中应该考虑到公共参数,例如token等,在接口请求时应该对公共参数进行校验,保证接口的安全性。
- 标准响应:接口应该返回标准的响应结果,包括响应码、响应消息、响应数据等。并且,响应结果应该符合统一的格式,以方便客户端进行解析。
- 统一业务码:接口设计时应该使用统一的业务码,以区分不同的业务场景和业务操作,方便系统进行跟踪和日志记录。
示例
用部分代码来说明接口规范细节
- 接口的幂等性设计
class Idempotency
{
private $redis; // Redis 实例
public function __construct()
{
// 初始化 Redis 连接
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
/**
* 判断请求是否已处理过
* @param $key string 请求唯一标识
* @return bool
*/
public function isProcessed($key)
{
// 判断 Redis 中是否已存在请求标识
return $this->redis->get($key) === '1';
}
/**
* 处理请求
* @param $key string 请求唯一标识
* @param $callback callable 回调函数,用于处理实际请求
* @return mixed|null
*/
public function processRequest($key, $callback)
{
// 如果请求已经处理过,直接返回处理结果
if ($this->isProcessed($key)) {
return $this->redis->get($key . ':response');
}
// 执行回调函数,并记录请求处理结果
$result = $callback();
$this->redis->set($key . ':response', $result);
$this->redis->set($key, '1');
// 返回处理结果
return $result;
}
}
// 实例化 Idempotency 类
$idempotency = new Idempotency();
// 生成请求唯一标识
$key = md5(json_encode(['user_id' => 123, 'product_id' => 456]));
// 定义回调函数,用于处理实际请求
$callback = function () {
// 实际处理请求的代码
// ...
return 'success';
};
// 处理请求,并保证接口的幂等性
$result = $idempotency->processRequest($key, $callback);
- IP限流
常见的限流算法有:
- 漏桶算法 漏桶算法(Leaky Bucket)是一种简单的限流算法,它通过对请求的速率进行限制来平滑流量。漏桶算法通过一个固定容量的“漏桶”来限制流量。当一个请求到达时,将会被加入到漏桶中。如果漏桶已满,则请求将被拒绝。如果漏桶未满,则请求将被从漏桶中取出,同时漏桶中的容量会随着时间的流逝而减少,这样可以平滑流量。
- 令牌桶算法 令牌桶算法(Token Bucket)是一种基于令牌的限流算法,它通过对请求的速率进行限制来平滑流量。令牌桶算法维护一个固定容量的令牌桶,每个令牌代表着一个请求的权限。当一个请求到达时,将会从令牌桶中取出一个令牌。如果令牌桶中没有令牌,则请求将被拒绝。令牌桶算法可以通过控制生成令牌的速率和令牌桶中最大容量来限制请求的速率。
- 计数器算法 计数器算法(Counter)是一种基于计数器的限流算法,它通过对请求的数量进行限制来平滑流量。计数器算法维护一个计数器,每当一个请求到达时,计数器的值会加一。当计数器的值超过一个预设的阈值时,请求将被拒绝。计数器算法可以通过调整阈值来控制请求的速率。
- 滑动窗口算法 滑动窗口算法(Sliding Window)是一种基于时间窗口的限流算法,它通过对请求的时间间隔进行限制来平滑流量。滑动窗口算法将请求按照时间顺序分组,并在每个时间窗口内对请求进行计数。如果一个时间窗口内的请求超过了预设的阈值,则请求将被拒绝。滑动窗口算法可以通过调整时间窗口的长度和阈值来控制请求的速率。
基于令牌桶算法的 示例:
class TokenBucket
{
private $capacity; // 桶容量
private $tokens; // 当前令牌数量
private $rate; // 令牌产生速率
private $timestamp; // 上一次令牌产生时间戳
/**
* 构造函数
* @param $capacity int 桶容量
* @param $rate int 令牌产生速率
*/
public function __construct($capacity, $rate)
{
$this->capacity = $capacity;
$this->tokens = $capacity; // 初始化为满桶
$this->rate = $rate;
$this->timestamp = time();
}
/**
* 获取令牌,如果获取不到则返回 false
* @return bool
*/
public function get()
{
$this->generateTokens(); // 生成令牌
if ($this->tokens > 0) { // 桶中有令牌
$this->tokens--; // 取出一个令牌
return true;
} else { // 桶中没有令牌
return false;
}
}
/**
* 生成令牌
*/
private function generateTokens()
{
$now = time();
$deltaTime = $now - $this->timestamp;
$this->tokens += $deltaTime * $this->rate;
$this->tokens = min($this->tokens, $this->capacity); // 令牌数量不超过桶容量
$this->timestamp = $now;
}
}
class IpLimiter
{
private $bucket;
/**
* 构造函数
* @param $capacity int 桶容量
* @param $rate int 令牌产生速率
*/
public function __construct($capacity, $rate)
{
$this->bucket = new TokenBucket($capacity, $rate);
}
/**
* 判断 IP 是否允许访问
* @param $ip string IP 地址
* @return bool
*/
public function allow($ip)
{
$key = 'ip_limiter:' . $ip;
$redis = new Redis();
$redis->connect('127.0.0.1', 6379); // 连接 Redis
if ($redis->exists($key)) { // IP 已经访问过
$count = $redis->incr($key); // 增加计数
if ($count > $this->bucket->capacity) { // 计数超过桶容量
return false;
}
} else { // IP 第一次访问
$redis->set($key, 1, $this->bucket->rate); // 设置计数器并设置过期时间为令牌生成间隔
}
return $this->bucket->get(); // 获取令牌
}
}
基于漏桶算法的示例:
该算法维护一个固定容量的水桶,当请求到来时,将水桶中的水流出一部分,然后判断是否可以接受该请求。如果当前水桶中的水不足以容纳请求,则拒绝该请求。通过设置不同的容量和速率参数,可以实现不同的限流策略
class LeakyBucket {
private $capacity;
private $rate;
private $water = 0;
private $lastLeakTime;
public function __construct($capacity, $rate) {
$this->capacity = $capacity;
$this->rate = $rate;
$this->lastLeakTime = time();
}
public function allowRequest() {
$now = time();
$elapsed = $now - $this->lastLeakTime;
$this->water = max(0, $this->water - $elapsed * $this->rate);
$this->lastLeakTime = $now;
if ($this->water < $this->capacity) {
$this->water++;
return true;
} else {
return false;
}
}
}
这些算法各有优缺点,应根据具体的场景选择合适的算法来进行 IP 限流。当然,在实际的业务下,接口限流不单只针对ip来做,还有其它的限流条件,因此,往往我们会定义抽象出不同的限流场景,例如我下面的:
ratelimiter
- 业务码和http状态码
在RESTful API设计中,HTTP状态码被广泛应用来表示请求的处理结果。标准的HTTP状态码包含了一系列常见的处理结果,例如200表示请求成功,404表示未找到资源,500表示服务器内部错误等。这样设计的好处是客户端和服务器可以通过HTTP状态码快速准确地获取请求的处理结果,从而进行相应的处理。
除了HTTP状态码,定义更明确的业务码也是非常重要的。业务码是指在处理业务逻辑时,根据不同的处理结果定义的标识码。通过定义明确的业务码,可以更准确地表示请求的处理结果,并且可以为客户端提供更具体的错误信息,帮助客户端更好地处理请求的结果。
在定义业务码时,可以根据业务逻辑的不同,将业务码进行分类,例如成功处理、参数错误、权限错误、系统错误等对于每一种业务码,可以提供对应的错误信息,例如错误码、错误信息等。在API设计中,建议将错误信息封装成一个标准的响应格式返回给客户端。
- 常用http状态码
成功处理:
200:请求成功
201:创建成功
202:请求已接收,但未处理完成
参数错误:
400:请求的参数不正确
401:缺少必要参数
402:参数格式不正确
403:参数值不在指定范围内
权限错误:
401:没有权限访问请求的资源
402:需要进行身份验证
403:已经进行了身份验证,但是没有权限进行该操作
404:请求的资源不存在
系统错误:
500:服务器内部错误
501:请求的功能尚未实现
502:网关错误
503:服务器暂时不可用
- 业务码统一设定参考
20000:成功处理
20001:新建/更新资源成功
20002:删除资源成功
20003:获取资源成功
20004:操作已执行,但没有任何数据变化
40000:请求参数错误
40001:缺少必要参数
40002:参数类型不正确
40003:参数值不在合法范围内
40004:参数格式不正确
40100:权限错误
40101:用户未登录
40102:用户没有足够的权限
40103:用户认证信息无效
50000:系统错误
50001:系统繁忙,请稍后重试
50002:系统错误,请联系管理员
50003:数据存储异常
请注意,这仅是一个示例,业务码分类定义可以根据实际情况进行调整和扩展(例如:http状态码+业务场景【订单】+场景动作【生成订单】+动作状态【成功】)。在应用程序中,这些业务码可以用来更精确地描述错误或状态,而不是仅使用HTTP状态码。同时,HTTP状态码可以用来表示请求本身的处理结果,如成功、参数错误、权限错误、服务器错误等。
在这里推荐我参考的一篇文章,相信大家会更明白:史上最全的接口(API)设计规范,值得收藏 - 环信
好了,有点啰嗦,今天的就分享到这里了,下期我将继续奉献接口设计,计划如下:
- 将本期接口限流设计作为单独的一篇推文来说明
- 用自定义错误异常来统一业务码