мета-данные страницы
  •  

Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

Предыдущая версия справа и слева Предыдущая версия
Следующая версия
Предыдущая версия
agbis_idempotency [13.05.2020 11:13]
void
agbis_idempotency [31.05.2022 10:44] (текущий)
Строка 1: Строка 1:
-====== Идемпотентность ​в мобильных ​приложениях ​====== +====== Идемпотентность ​запросов ​======
-\\+
  
-  - Добавлены property  +Можно ознакомится ​со статьей от Яндекс:​ [[https://​habr.com/​ru/​company/​yandex/​blog/​442762/​]] 
-    * Himstat.IdempotencyText устанавливается ​на прямую (доступно на чтение и запись) +---- 
-    * Himstat.IdempotencyHash устанавливается при установке IdempotencyText (доступно только на чтение) +[[#Введение|Введение]]\\ 
-  - uErrorRes добавлен тип Exception TIdenpotencyException+[[#​Обработка в агенте|Обработка в агенте]]\\ 
 +[[#Обработка на клиенте|Обработка на клиенте]]\\ 
 +[[#Пример МП Courier|Пример МП Courier]]\\ 
 +[[#​Пример Бонусы Онлайн|Пример Бонусы Онлайн]]\\ 
 +----
  
-На примере запроса PayPlanOrders:​ 
-  - THimstatDM.ReplyWithPayPlanOrders 
-    * Himstat.IdempotencyText := Srvr.Request.Query.Text;​ Получаем IdempotencyText IdempotencyHash 
  
-  - Himstat.DoPayPlanOrders +===== Введение ===== 
-    * QueryIdempotency Записываем в таблицу Hash и время + 
-      * если ошибка получения ​"старого" ​ответа ​то ничего не делаем - пропускаем запрос ​выполнять свои ​действия - старый ​функционал - нет таблицы +Идемпотентность - это операция, которая ​при многократном вызове возвращает один и тот же результат.\\ 
-      * если ответ =""​ - идет запись - Отправляем пустой не ошибочный ответ +Или ​метод HTTP является идемпотентным, ​если ​повторный ​идентичный запрос, сделанный один или несколько раз подряд,​ имеет один и тот же эффект,​ не изменяющий состояние сервера
-      если ответ !=""​ - ответ был Отправляем ответ клиенту +---- 
-      ​если ошибка записи в уникальные поля таблицы  ​- Отправляем пустой не ошибочный ответ +===== Обработка в агенте ===== 
-  ​+ 
 +  * Новые property: 
 +    * Himstat.IdempotencyText - ключ идемпотентности,​ берется из заголовка запроса и устанавливается на прямую (доступно на чтение и запись);​ 
 +    * Himstat.IdempotencyHash - хэш ключ идемпотентности,​ устанавливается ​при установке IdempotencyText (доступно только на чтение); 
 +  * в модуле <font color="​blue"​ size="​+1">​ErrorRes.pas</​font>​ новый тип Exception TIdenpotencyException. 
 + 
 + 
 +**Пример ​запроса:** 
 + 
 +<sxh Delphi>​ 
 +procedure THimstatDM. ...(Srvr: TRtcDataServer);​ 
 +var 
 +  ... 
 +begin 
 +  ...  
 +  try      
 +    ... 
 +    try 
 +      Himstat := THimstat(GlobalConnectionPoolClass.LockFreeConnection(THimstat));​ // Получаем IdempotencyText -> (IdempotencyHash) 
 +      Himstat.IdempotencyText := Srvr.Request.Query.Text;​ 
 +      Himstat.Do...(SessionID,​ s); // Выполняем команду 
 +    finally 
 +      GlobalConnectionPoolClass.UnlockConnection(TObject(Himstat));​ 
 +    end; 
 +  ... 
 +  except 
 +    on E: TIdenpotencyException do 
 +    begin 
 +      ... 
 +      s := Format('​{"​error":​ ' + IntToStr(ErrorIdenpotency) + ', "​Msg":​ "​%s"​}',​ 
 +                  [AEncodeURL(Utf8Encode('​Повторный запрос. Обновите приложение!'​))]);​ 
 +      ... 
 +    end; 
 +    on E: Exception do 
 +    begin 
 +      ...    
 +    end; 
 +  end; 
 +  ... 
 +end; 
 + 
 +procedure THimstat.Do...(const nSessionID: string; out Str: string); 
 +var  
 +  ... 
 +begin 
 +  ...  
 +  try 
 +  ... 
 +   
 +    QueryIdempotency;​ // Записываем в таблицу Hash и время 
 +    (* 
 +     ​*Нет таблицы IDEMPOTENCY_QUERY ​выходим из проверки 
 +     * - получаем ​старый ​ОТВЕТ (предыдущего АНАЛОГИЧНОГО запроса) ИЛИ получаем ОТВЕТ="" ​Идет запись 
 +     ​- ОТВЕТ=null - пытаемся записать HASH   
 +     * - ERROR Присваиваем ОТВЕТ:=""​ - Идет запись 
 +     ​* ​FINALY 
 +     ​* ​  - (1) ЕСЛИ ​ОТВЕТ=null - то у нас уникальный запрос записанный ​в таблицу  
 +     ​* ​  - (2) ЕСЛИ ОТВЕТ<>​null - запрос не уникален - возбуждаем исключение TIdenpotencyException.Create(ОТВЕТ);​  
 +     *) 
 +     
 +    // Выполняем действия 
 +    ... 
 + 
 +    UpdateIdempotency(Str);​ // сохраняем ОТВЕТ в таблицу   
 +  except 
 +    on E: TIdenpotencyException do 
 +    begin 
 +      Str := e.Message;​ 
 +      if Trim(Str) = ''​ then 
 +        raise TIdenpotencyException.Create('​Нет ответа на запрос - повторяющийся запрос!'​);​ 
 +        (* 
 +         ​*Если исключение содержит НЕ ПУСТОЕ сообщение то это ОТВЕТ и мы его ​отправляем повторно 
 +         ​*Если ​исключение содержит ПУСТОЕ сообщение ​то снова возбуждаем TIdenpotencyException и отправляем как ошибку ОДНОВРЕМЕННОГО ЗАПРОСА (ErrorCode = 5) 
 +         *) 
 +    end; 
 +    on E: Exception do 
 +    begin 
 +      ... 
 +      DeleteIdempotency;​ // Если ОШИБКА ​произошла в теле обработки до сохранения ОТВЕТа ​то HASH запроса удаляется 
 +    end; 
 +  end; 
 +  ... 
 +end; 
 +</​sxh>​ 
 +---- 
 +==== Клиент ==== 
 +  - Организовать уникальность запроса;​ 
 +  - Обработку код Error = 5. 
 + 
 +---- 
 +===== Обработка на клиенте ===== 
 + 
 +===== Пример МП Courier ===== 
 + 
 +<sxh Delphi>​ 
 +... 
 + 
 +... 
 +var 
 +  PayIdempotency:​ string;//'​Переменная сохраняет состояние текущей транзакции /​запрос(dor_id,​ debet) + ответ(JSON)/'​ 
 +... 
 + 
 +... 
 +      ​JSONAnswer := KassaFiscalRegistar.PrintFiscalCheck(JSONResultObj);​ //'Печать чека'​ 
 +      ... 
 +    except 
 +      on E: Exception do 
 +      begin 
 +        PayIdempotency := '';​ //'Если ошибка ​при печати - обнуляем текущую транзакцию'  
 +... 
 + 
 +... 
 +procedure TmoneyPayFramePanel.OnShow(curVisible:​ TuniFramePanel);​ 
 +begin 
 +  PayIdempotency := '';//​ 'При входе обнуление предыдущей транзакции'​ 
 +... 
 + 
 +... 
 +      ClnDM.GetFiscalCheck(sJSON,​ 
 +        procedure (AJObject: TJSONObject) 
 +        var CurrentPayIdempotency:​ string; //'​Текущая транзакция'​ 
 +        begin 
 +          CurrentPayIdempotency := THashMD5.GetHashString(sJSON + '​='​ + AJObject.ToString);​ 
 +          if PayIdempotency = CurrentPayIdempotency then //'​Текущую сравниваем с запомненой'​ 
 +          begin 
 +            // '​Ничего не делаем если транзакция все еще ТА ЖЕ'​ 
 +            WriteLog("​TmoneyPayFramePanel.WorkOfPay PayIdempotency = CurrentPayIdempotency Abort"​);​ 
 +          end 
 +          else 
 +          begin 
 +            //'​Транзакция другая - выпонить оплату'​ 
 +            PayIdempotency := CurrentPayIdempotency;​ 
 +            AnswerFromWeb('​GetFiscalCheck',​ '',​ 0, AJObject);​ 
 +          end; 
 +        end); 
 +...  
 + 
 +... 
 +</​sxh>​ 
 +---- 
 +===== Пример Бонусы Онлайн ===== 
 +<sxh Delphi>​ 
 +function BonusOnlineSend(const data: string; out j: TJsonObject):​ Boolean; 
 +var 
 +  ... 
 +  Idempotency_GUID:​ string; // Переменная сохраняет состояние текущей транзакции 
 +  ReconIdempotencyCount:​ integer;  
 +  json: TJsonValue;​ 
 +  JSON_O: TJsonObject;​ 
 +begin 
 +  Result := False; 
 +  Idempotency_GUID := '';​ // При входе обнуление предыдущей транзакции 
 +  try   
 +    CreateIdempotency_GUID(Idempotency_GUID);​ // Текущая ​транзакция 
 +    ReconIdempotencyCount := 0; 
 +    json := nil; 
 + 
 +    while True do 
 +    begin 
 +      if not BonusPaySend(data,​ ApiPath, hmPOST, res, Idempotency_GUID) then 
 +      begin 
 +        WriteLog('​Не получили данные о бонусе'​);​ 
 +        raise Exception.Create('​Бонусы онлайн. Получить бонусы невозможно!'​ + #13#10 + '​Отсутствует связь с сервером!'); 
 +      end; 
 + 
 +      ... 
 + 
 +      codeApi := JSON_O.Get('​error'​).JsonValue.Value;​ 
 +       
 +      ... 
 +      else 
 +      if codeApi = '​5'​ then // Действия ​при 5 коде  
 +      begin 
 +        if ReconIdempotencyCount < 2 then // Проверяем количество провторов 
 +        begin 
 +          WriteLog('​errorResult = 5, повторный запрос'​);​ 
 +          Sleep(500);​ 
 + 
 +          inc(ReconIdempotencyCount);​  
 +          Continue; // Пробуем еще раз 
 +        end 
 +        else 
 +        begin 
 +          ReconIdempotencyCount := 0; // Обнулим счетчик и сообщим об ошибке 
 + 
 +          WriteLog(_UTFDecode(JSON_O.Get('​Msg'​).JsonValue.Value));​ 
 +          raise Exception.Create('​Бонусы онлайн. Получить бонусы невозможно!'​ + #13#10 + _UTFDecode(JSON_O.Get('​Msg'​).JsonValue.Value));​ 
 +        end; 
 +      end 
 +      else 
 +      ... 
 + 
 +      Break; 
 +    end; 
 +     
 +    ... 
 +    Result := True; 
 +  finally 
 +    if Assigned(json) then 
 +      FreeAndNil(json);​ 
 +    Idempotency_GUID := '';​  
 +  end; 
 +end; 
 + 
 +... 
 + 
 +function BonusPaySend(const data, URL: string; Method: THttpMethod;​ out json: string; Idempotency_GUID:​ string = ''​):​ Boolean; 
 +var 
 +  i: Integer; 
 +  http: T_Response;​ 
 +  Headers: TStringList;​ 
 +  url_data, ContentEncoding:​ string; 
 +begin 
 +  Result := False; 
 + 
 +  i := 0; 
 +  while (i < 2) and not Result do 
 +  begin 
 +    Headers := TStringList.Create;​ 
 +    try 
 +      if Idempotency_GUID <> ''​ then // идентификатор запроса 
 +        Headers.Add('​XIdempotency-GUID:​ ' + Idempotency_GUID);​ // Передаем в запросе 
 +         
 +      Headers.Add('​Accept-Encoding:​ deflate'​);​ 
 +      Headers.Add('​Content-Encoding:​ deflate'​);​ 
 +      Headers.Add('​Content-Type:​ application/​json'​);​ 
 +       
 +      ...  
 +          
 +    finally 
 +      if Assigned(Headers) then 
 +        FreeAndNil(Headers);​ 
 +      inc(i); 
 +    end; 
 +  ​end; 
 +end; 
 +</​sxh>​ 
 +----