предисловие
Я сам давно (три года) занимаюсь разработкой интерфейсов, по сути, прошел почти год с тех пор, как я захотел написать эту статью. Обобщу свое понимание дизайна интерфейсов со следующих направлений:
Определение параметра интерфейса -> проблема версии интерфейса -> безопасность интерфейса -> дизайн кода интерфейса -> читабельность интерфейса -> документация по интерфейсу -> ямки, с которыми я столкнулся
Определение параметра интерфейса
В дизайне интерфейса некоторые новые общедоступные параметры могут быть абстрагированы.После почти трех лет работы над интерфейсом я могу думать о некоторых общих общедоступных параметрах интерфейса следующим образом:
общедоступный параметр | значение | Определите значение этого параметра |
---|---|---|
timestamp | отметка времени в миллисекундах | 1. Индикация времени запроса клиента. 2. Серверная часть может выполнять проверку истечения срока действия запроса. 3. Этот параметр участвует в алгоритме подписи для повышения уникальности подписи. |
app_key | Открытый ключ подписи | Открытый ключ алгоритма подписи, серверная часть может получить соответствующий закрытый ключ через открытый ключ. |
sign | подпись интерфейса | Подпись интерфейса генерируется запрошенными параметрами и заданным алгоритмом подписи, что предотвращает вмешательство посредника в параметры запроса. |
did | Идентификатор устройства | Уникальная идентификация устройства, правила генерации, такие как md5 mac-адреса android и md5 udid ios Zengjin (в настоящее время недоступен), 1: сбор данных 2. Это удобно для отслеживания проблем 3. Метка сообщения |
Проблема с версией интерфейса
Существует историческая проблема в дизайне интерфейса -> управление версиями интерфейса. Я также изучил много информации и дизайна о версиях интерфейса, и в конце концов я пришел к следующим выводам:
- Версия интерфейса делится на
- большая версия
- Принцип: максимальное количество основных версий ограничено до менее 5 (я лично предпочитаю 3), а версии, превышающей лимит версий, предлагается обновить до новой версии.
- план
- uri содержит номер версии, например: v1/user/get
- Параметры запроса, например: user/get?v=1.0
- Маленькая версия
- Принцип: контролируй сам 😄
- план
- uri содержит номер версии, например: v1/user/get_01.
- Параметры запроса, минорная версия находится справа от запятой, например: user/get?v=1.1
- большая версия
безопасность интерфейса
Дизайн интерфейса должен быть неотделим от слова безопасность.Чтобы добиться максимальной безопасности, нам нужноУвеличьте сложность атаки настолько, насколько это возможно, ниже приведены некоторые распространенные средства, которые я знаю и использую для повышения безопасности интерфейса (https здесь обсуждаться не будет):
Проверка срока действия / Проверка подписи / Повторная атака / Ограничение тока / Побег
Псевдокод выглядит следующим образом:
// 过期验证
if (microtime(true)*1000 - $_REQUEST['timestamp'] > 5000) {
throw new \Exception(401, 'Expired request');
}
// 签名验证(公钥校验省略)
$params = ksort($_REQUEST);
unset($params['sign']);
$sign = md5(sha1(implode('-', $params) . $_REQUEST['app_key']));
if ($sign !== $_REQUEST['sign']) {
throw new \Exception(401, 'Invalid sign');
}
/**
* 重放攻击
* @params noise string 随机字符串或随机正整数,与 Timestamp 联合起来, 用于防止重放攻击 例如腾讯云是6位随机正整数
*/
$key = md5("{$_REQUEST['REQUEST_URI']}-{$_REQUEST['timestamp']}-{$_REQUEST['noise']}-{$_REQUEST['did']}");
if ($redisInstance->exists($key)) {
throw new \Exception(401, 'Repeated request');
}
// 限流
$key = md5("{$_REQUEST['REQUEST_URI']}-{$_REQUEST['REMOTE_ADDR']}-{$_REQUEST['did']}");
if ($redisInstance->get($key) > 60) {
throw new \Exception(401, 'Request limit');
}
$redisInstance->incre($key);
// 转义
$username = htmlspecialchars($_REQUEST['username']);
Дизайн кода интерфейса -> развязанный бизнес plug and play
Ключевые слова для этого процесса: абстрагирование в класс, предварительное промежуточное ПО, внедрение.
Далее идет уровень дизайна нашего кода, как абстрагировать общедоступную часть и отделить бизнес-код.
В общем, определите глобальную функцию, а затем вызовите функцию в начале каждого интерфейса:
// 全局定义一个函数
function check () {
// 校验公共参数
# code ...
// 校验签名
# code ...
// 校验频率
# code ...
// 等等...
}
Второй способ написания — определить метод родительского класса, а затем каждый интерфейсный класс наследует интерфейс, а конструктор вызывает модифицированный метод, фактически такой же, как и выше:
// 父类方法
class father
{
public function __construct()
{
$this->check();
}
public function check () {
// 校验公共参数
# code ...
// 校验签名
# code ...
// 校验频率
# code ...
// 等等...
}
}
Дело в том, что третий общий метод, который я отстаиваю, цепочка объектов и предварительное промежуточное программное обеспечение:
/**
* 检验抽象类
*/
abstract class Check
{
/**
* 下一个check实体
*
* @var object
*/
private $nextCheckInstance;
/**
* 校验方法
*
* @param Request $request 请求对象
*/
abstract public function operate(Request $request);
/**
* 设置责任链上的下一个对象
*
* @param Check $check
*/
public function setNext(Check $check)
{
$this->nextCheckInstance = $check;
return $check;
}
/**
* 启动
*
* @param Request $request 请求对象
*/
public function start(Request $request)
{
$this->doCheck($request);
// 调用下一个对象
if (! empty($this->nextCheckInstance)) {
$this->nextCheckInstance->start($request);
}
}
}
// 校验公共参数类
class ParamsCheck extends Check
{
public function operate()
{
// 校验公共参数
# code ...
}
}
// 校验签名类
class SignCheck extends Check
{
public function operate()
{
// 校验签名
# code ...
}
}
// 等等...
// 前置中间件类
class FrontMiddleware
{
public function run()
{
// 初始化一个:必传参数校验的check
$checkParams = new ParamsCheck();
// 初始化一个:签名check
$checkSign = new SignCheck();
// 初始化一个:频率check
$checkFrequent = new FrequentCheck();
// 等等...
// 构成对象链
$checkParams->setNext($checkSign)
->setNext($checkFrequent)
...
// 启动
$checkParams->start();
}
}
Читабельность интерфейса
Что я должен упомянуть о читабельности, это RESTFUL. Я не буду обсуждать RESTFUL здесь. Вы можете добавить соответствующие знания самостоятельно. Некоторые из моих мыслей о читабельности дизайна интерфейса:
- url
- Non-RESTFUL: ресурс/ресурс/операция (глагол), например, контент/статья/получить -> получить ресурс статьи под ресурсом контента
- RESTFUL: ресурс/ресурс/ресурс, например, получить контент/статья/1 -> получить ресурс статьи, идентификатор статьи которого равен 1 в ресурсе контента.
- method
- Non-RESTFUL: получить легко, чтобы проверить журналы nginx, загрузить пост ресурса, никаких жестких требований
- RESTFUL: RESTFUL-совместимое мышление
- параметры запроса: лично я предпочитаю подчёркивание имен, соответствующие аббревиатуры
- параметры ответа: код ответа должен соответствовать статусу http
- 200 -> нормальный
- 400 -> Отсутствуют общедоступные обязательные параметры или обязательные бизнес-параметры
- 401 -> Ошибка проверки интерфейса, например, подписи
- 403 -> Нет прав доступа к этому интерфейсу
- 499 -> Время ответа восходящей службы превышает тайм-аут, установленный интерфейсом
- 500 -> ошибка кода
- 501 -> Неподдерживаемый метод интерфейса
- 502 -> Формат данных, возвращаемый восходящей службой, неверен.
- 503 -> Тайм-аут восходящей службы
- 504 -> Восходящая служба недоступна
// 响应的格式
{
"code": 200,
"msg": "ok",
"data": {
}
}
документация по интерфейсу
Хорошая документация по интерфейсу - это производительность, swagger + api blueprint гуглите сами😄
Яма, с которой я столкнулся
Относительно большая яма, встречающаяся здесь, — это ошибка, оставшаяся от истории протокола http:
Не различайте пробелы и плюсики в URL ➕
Проблема в том, что urldecode превратит знак + в параметре в пробел, поэтому в этом случае вам нужно использовать rawurldecode, чтобы + не превратился в пробел. Например, при проверке параметров интерфейса~