Дизайн интерфейса как я понимаю

задняя часть алгоритм Безопасность API

предисловие

Я сам давно (три года) занимаюсь разработкой интерфейсов, по сути, прошел почти год с тех пор, как я захотел написать эту статью. Обобщу свое понимание дизайна интерфейсов со следующих направлений:

Определение параметра интерфейса -> проблема версии интерфейса -> безопасность интерфейса -> дизайн кода интерфейса -> читабельность интерфейса -> документация по интерфейсу -> ямки, с которыми я столкнулся

Определение параметра интерфейса

В дизайне интерфейса некоторые новые общедоступные параметры могут быть абстрагированы.После почти трех лет работы над интерфейсом я могу думать о некоторых общих общедоступных параметрах интерфейса следующим образом:

общедоступный параметр значение Определите значение этого параметра
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, чтобы + не превратился в пробел. Например, при проверке параметров интерфейса~