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

Различия

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

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

Предыдущая версия справа и слева Предыдущая версия
Следующая версия
Предыдущая версия
agbis_idempotency [31.05.2022 10:34]
Anatoly [Пример МП Courier]
agbis_idempotency [31.05.2022 10:44]
Строка 1: Строка 1:
-====== Идемпотентность запросов ====== 
  
-Можно ознакомится со статьей от Яндекс:​ [[https://​habr.com/​ru/​company/​yandex/​blog/​442762/​]] 
----- 
-[[#​Обработка в агенте|Обработка в агенте]] 
-[[#​Обработка на клиенте|Обработка на клиенте]] 
-[[#​Пример МП Courier|Пример МП Courier]] 
-[[#​Пример Бонусы Онлайн|Пример Бонусы Онлайн]] 
----- 
- 
-===== 1. Пишущая транзакция (на сервере) ===== 
- 
-===== Обработка в агенте ===== 
- 
-  * Новые 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>​ 
----- 
-==== Клиент ==== 
-  - Организовать уникальность запроса 
-  - Обработку ErrorCode = 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 
-  codeApi, res, 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 
-      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>​ 
-----