мета-данные страницы
Различия
Здесь показаны различия между двумя версиями данной страницы.
Предыдущая версия справа и слева Предыдущая версия Следующая версия | Предыдущая версия | ||
agbis_idempotency [28.04.2022 14:42] Anatoly |
agbis_idempotency [31.05.2022 10:44] Anatoly [Введение] |
||
---|---|---|---|
Строка 3: | Строка 3: | ||
Можно ознакомится со статьей от Яндекс: [[https://habr.com/ru/company/yandex/blog/442762/]] | Можно ознакомится со статьей от Яндекс: [[https://habr.com/ru/company/yandex/blog/442762/]] | ||
---- | ---- | ||
- | [[Обработка в агенте|Обработка в агенте]]\\ | + | [[#Введение|Введение]]\\ |
- | [[Обработка на клиенте|Обработка на клиенте]]\\ | + | [[#Обработка в агенте|Обработка в агенте]]\\ |
- | [[Пример МП Courier|Пример МП Courier]]\\ | + | [[#Обработка на клиенте|Обработка на клиенте]]\\ |
+ | [[#Пример МП Courier|Пример МП Courier]]\\ | ||
+ | [[#Пример Бонусы Онлайн|Пример Бонусы Онлайн]]\\ | ||
---- | ---- | ||
- | ===== 1. Пишущая транзакция (на сервере) ===== | ||
+ | ===== Введение ===== | ||
+ | |||
+ | Идемпотентность - это операция, которая при многократном вызове возвращает один и тот же результат.\\ | ||
+ | Или метод HTTP является идемпотентным, если повторный идентичный запрос, сделанный один или несколько раз подряд, имеет один и тот же эффект, не изменяющий состояние сервера. | ||
+ | ---- | ||
===== Обработка в агенте ===== | ===== Обработка в агенте ===== | ||
- | - Добавлены property | + | |
- | - Himstat.IdempotencyText устанавливается на прямую (доступно на чтение и запись) | + | * Новые property: |
- | - Himstat.IdempotencyHash устанавливается при установке IdempotencyText (доступно только на чтение) | + | * Himstat.IdempotencyText - ключ идемпотентности, берется из заголовка запроса и устанавливается на прямую (доступно на чтение и запись); |
- | - uErrorRes добавлен тип Exception TIdenpotencyException | + | * Himstat.IdempotencyHash - хэш ключ идемпотентности, устанавливается при установке IdempotencyText (доступно только на чтение); |
- | * На примере запроса PayPlanOrders: | + | * в модуле <font color="blue" size="+1">ErrorRes.pas</font> новый тип Exception TIdenpotencyException. |
- | - THimstatDM.ReplyWithPayPlanOrders | + | |
- | - Himstat.IdempotencyText := Srvr.Request.Query.Text; Получаем IdempotencyText IdempotencyHash | + | |
- | - Himstat.DoPayPlanOrders | + | **Пример запроса:** |
- | - QueryIdempotency Записываем в таблицу Hash и время | + | |
- | - Нет таблицы IDEMPOTENCY_QUERY - выходим из проверки | + | <sxh Delphi> |
- | - получаем старый ОТВЕТ (предыдущего АНАЛОГИЧНОГО запроса) ИЛИ получаем ОТВЕТ="" - Идет запись | + | procedure THimstatDM. ...(Srvr: TRtcDataServer); |
- | - ОТВЕТ=null - пытаемся записать HASH | + | var |
- | - ERROR Присваиваем ОТВЕТ:="" - Идет запись | + | ... |
- | - FINALY | + | begin |
- | - (1) ЕСЛИ ОТВЕТ=null - то у нас уникальный запрос записанный в таблицу | + | ... |
- | - (2) ЕСЛИ ОТВЕТ<>null - запрос не уникален - возбуждаем исключение TIdenpotencyException.Create(ОТВЕТ); | + | try |
- | - (1) UpdateIdempotency сохраняем ОТВЕТ в таблицу | + | ... |
- | - (2) TIdenpotencyException | + | try |
- | - Если исключение содержит НЕ ПУСТОЕ сообщение то это ОТВЕТ и мы его отправляем повторно | + | Himstat := THimstat(GlobalConnectionPoolClass.LockFreeConnection(THimstat)); // Получаем IdempotencyText -> (IdempotencyHash) |
- | - Если исключение содержит ПУСТОЕ сообщение то снова возбуждаем TIdenpotencyException и отправляем как ошибку ОДНОВРЕМЕННОГО ЗАПРОСА (ErrorCode = 5) | + | Himstat.IdempotencyText := Srvr.Request.Query.Text; |
- | - Если ОШИБКА произошла в теле обработки до сохранения ОТВЕТа то HASH запроса удаляется | + | 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 | + | - Обработку код Error = 5. |
---- | ---- | ||
===== Обработка на клиенте ===== | ===== Обработка на клиенте ===== | ||
Строка 41: | Строка 111: | ||
===== Пример МП Courier ===== | ===== Пример МП Courier ===== | ||
- | <code pascal> | + | <sxh Delphi> |
... | ... | ||
... | ... | ||
var | var | ||
- | PayIdempotency: string;/ /'Переменная сохраняет состояние текущей транзакции /запрос(dor_id, debet) + ответ(JSON)/' | + | PayIdempotency: string;//'Переменная сохраняет состояние текущей транзакции /запрос(dor_id, debet) + ответ(JSON)/' |
... | ... | ||
... | ... | ||
- | JSONAnswer := KassaFiscalRegistar.PrintFiscalCheck(JSONResultObj); / /'Печать чека' | + | JSONAnswer := KassaFiscalRegistar.PrintFiscalCheck(JSONResultObj); //'Печать чека' |
... | ... | ||
except | except | ||
on E: Exception do | on E: Exception do | ||
begin | begin | ||
- | PayIdempotency := ''; / /'Если ошибка при печати - обнуляем текущую транзакцию' | + | PayIdempotency := ''; //'Если ошибка при печати - обнуляем текущую транзакцию' |
... | ... | ||
Строка 61: | Строка 131: | ||
procedure TmoneyPayFramePanel.OnShow(curVisible: TuniFramePanel); | procedure TmoneyPayFramePanel.OnShow(curVisible: TuniFramePanel); | ||
begin | begin | ||
- | PayIdempotency := '';/ / 'При входе обнуление предыдущей транзакции' | + | PayIdempotency := '';// 'При входе обнуление предыдущей транзакции' |
... | ... | ||
Строка 67: | Строка 137: | ||
ClnDM.GetFiscalCheck(sJSON, | ClnDM.GetFiscalCheck(sJSON, | ||
procedure (AJObject: TJSONObject) | procedure (AJObject: TJSONObject) | ||
- | var CurrentPayIdempotency: string; / /'Текущая транзакция' | + | var CurrentPayIdempotency: string; //'Текущая транзакция' |
begin | begin | ||
CurrentPayIdempotency := THashMD5.GetHashString(sJSON + '=' + AJObject.ToString); | CurrentPayIdempotency := THashMD5.GetHashString(sJSON + '=' + AJObject.ToString); | ||
- | if PayIdempotency = CurrentPayIdempotency then / /'Текущую сравниваем с запомненой' | + | if PayIdempotency = CurrentPayIdempotency then //'Текущую сравниваем с запомненой' |
begin | begin | ||
- | / / 'Ничего не делаем если транзакция все еще ТА ЖЕ' | + | // 'Ничего не делаем если транзакция все еще ТА ЖЕ' |
WriteLog("TmoneyPayFramePanel.WorkOfPay PayIdempotency = CurrentPayIdempotency Abort"); | WriteLog("TmoneyPayFramePanel.WorkOfPay PayIdempotency = CurrentPayIdempotency Abort"); | ||
end | end | ||
else | else | ||
begin | begin | ||
- | / /'Транзакция другая - выпонить оплату' | + | //'Транзакция другая - выпонить оплату' |
PayIdempotency := CurrentPayIdempotency; | PayIdempotency := CurrentPayIdempotency; | ||
AnswerFromWeb('GetFiscalCheck', '', 0, AJObject); | AnswerFromWeb('GetFiscalCheck', '', 0, AJObject); | ||
Строка 85: | Строка 155: | ||
... | ... | ||
- | </code> | + | </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> | ||
+ | ---- |