мета-данные страницы
Различия
Здесь показаны различия между двумя версиями данной страницы.
| Предыдущая версия справа и слева Предыдущая версия Следующая версия | Предыдущая версия | ||
|
agbis_idempotency [31.05.2022 10:28] Anatoly [Обработка в агенте] |
agbis_idempotency [31.05.2022 10:44] (текущий) |
||
|---|---|---|---|
| Строка 3: | Строка 3: | ||
| Можно ознакомится со статьей от Яндекс: [[https://habr.com/ru/company/yandex/blog/442762/]] | Можно ознакомится со статьей от Яндекс: [[https://habr.com/ru/company/yandex/blog/442762/]] | ||
| ---- | ---- | ||
| + | [[#Введение|Введение]]\\ | ||
| [[#Обработка в агенте|Обработка в агенте]]\\ | [[#Обработка в агенте|Обработка в агенте]]\\ | ||
| [[#Обработка на клиенте|Обработка на клиенте]]\\ | [[#Обработка на клиенте|Обработка на клиенте]]\\ | ||
| [[#Пример МП Courier|Пример МП Courier]]\\ | [[#Пример МП Courier|Пример МП Courier]]\\ | ||
| + | [[#Пример Бонусы Онлайн|Пример Бонусы Онлайн]]\\ | ||
| ---- | ---- | ||
| - | ===== 1. Пишущая транзакция (на сервере) ===== | ||
| + | ===== Введение ===== | ||
| + | |||
| + | Идемпотентность - это операция, которая при многократном вызове возвращает один и тот же результат.\\ | ||
| + | Или метод HTTP является идемпотентным, если повторный идентичный запрос, сделанный один или несколько раз подряд, имеет один и тот же эффект, не изменяющий состояние сервера. | ||
| + | ---- | ||
| ===== Обработка в агенте ===== | ===== Обработка в агенте ===== | ||
| Строка 95: | Строка 101: | ||
| end; | end; | ||
| </sxh> | </sxh> | ||
| - | |||
| - | THimstatDM.ReplyWithPayPlanOrders | ||
| - | - Himstat.IdempotencyText := Srvr.Request.Query.Text; Получаем IdempotencyText IdempotencyHash | ||
| - | - Himstat.DoPayPlanOrders | ||
| - | - QueryIdempotency Записываем в таблицу Hash и время | ||
| - | - Нет таблицы IDEMPOTENCY_QUERY - выходим из проверки | ||
| - | - получаем старый ОТВЕТ (предыдущего АНАЛОГИЧНОГО запроса) ИЛИ получаем ОТВЕТ="" - Идет запись | ||
| - | - ОТВЕТ=null - пытаемся записать HASH | ||
| - | - ERROR Присваиваем ОТВЕТ:="" - Идет запись | ||
| - | - FINALY | ||
| - | - (1) ЕСЛИ ОТВЕТ=null - то у нас уникальный запрос записанный в таблицу | ||
| - | - (2) ЕСЛИ ОТВЕТ<>null - запрос не уникален - возбуждаем исключение TIdenpotencyException.Create(ОТВЕТ); | ||
| - | - (1) UpdateIdempotency сохраняем ОТВЕТ в таблицу | ||
| - | - (2) TIdenpotencyException | ||
| - | - Если исключение содержит НЕ ПУСТОЕ сообщение то это ОТВЕТ и мы его отправляем повторно | ||
| - | - Если исключение содержит ПУСТОЕ сообщение то снова возбуждаем TIdenpotencyException и отправляем как ошибку ОДНОВРЕМЕННОГО ЗАПРОСА (ErrorCode = 5) | ||
| - | - Если ОШИБКА произошла в теле обработки до сохранения ОТВЕТа то HASH запроса удаляется | ||
| - | |||
| ---- | ---- | ||
| ==== Клиент ==== | ==== Клиент ==== | ||
| - | - Организовать уникальность запроса | + | - Организовать уникальность запроса; |
| - | - Обработку ErrorCode = 5 | + | - Обработку код Error = 5. |
| ---- | ---- | ||
| Строка 123: | Строка 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 := ''; //'Если ошибка при печати - обнуляем текущую транзакцию' |
| ... | ... | ||
| Строка 143: | Строка 131: | ||
| procedure TmoneyPayFramePanel.OnShow(curVisible: TuniFramePanel); | procedure TmoneyPayFramePanel.OnShow(curVisible: TuniFramePanel); | ||
| begin | begin | ||
| - | PayIdempotency := '';/ / 'При входе обнуление предыдущей транзакции' | + | PayIdempotency := '';// 'При входе обнуление предыдущей транзакции' |
| ... | ... | ||
| Строка 149: | Строка 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); | ||
| Строка 167: | Строка 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> | ||
| + | ---- | ||