# Путь события расхода от Rab123bitMQ до блокировки

STR0002064 · quota-from-queue · архитектура реализации

[Обработка события квоты\.md](https://notes.vtorum.net/api/public/web/%D0%BF%D1%83%D1%82%D1%8C-%D1%81%D0%BE%D0%B1%D1%8B%D1%82%D0%B8%D1%8F-%D1%80%D0%B0%D1%81%D1%85%D0%BE%D0%B4%D0%B0-%D0%BE%D1%82-rabbitmq-%D0%B4%D0%BE-%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-4b421ae7/assets/2af6f436-8c33-4358-b885-ebb99ecc9c75/raw)

# Путь события расхода от RabbitMQ до блокировки

flowchart LR
    R["Путь события расхода от RabbitMQ до блокировки"] --> A1["Приход события"]
    R --> A2["Вход в SimpleOne"]
    R --> A3["Обработка"]
    R --> A4["Блокировка"]
    R --> A5["Вызовы Script Include"]
    R --> A6["Неопределённые вопросы"]
    A1 --> B1["Proxy публикует в exchange x.platform.app.v1"]
    A1 --> B2["ClickHouse читает из своей очереди"]
    A1 --> B3["SimpleOne получает копию через свою очередь"]
    A2 --> B4["sys_rabbitmq_consumer слушает очередь"]
    A2 --> B5["Запускает скрипт с importIncludeScript и run()"]
    A2 --> B6["Вход от TMS через TMSCallback"]
    A3 --> B7["Разбор события и поиск записей T1"]
    A3 --> B8["Отсечение повторов T2"]
    A3 --> B9["Выбор правил квоты T6"]
    A3 --> B10["Прибавление расхода T3"]
    A3 --> B11["Постановка блокировки T4"]
    A4 --> B12["Блок пишется в mai_blocked_nexus"]
    A4 --> B13["Для токена — запрос в TMS через blockToken"]
    A4 --> B14["Путь до Proxy не найден"]
    A5 --> B15["consumer-скрипт → Обработчик"]
    A5 --> B16["Обработчик → MAI.QuotaRecalculation"]
    A5 --> B17["MAI.QuotaRecalculation → MAI.LicenseTools"]
    A5 --> B18["MAI.QuotaRecalculation → MAI.LQMTools"]
    A5 --> B19["MAI.QuotaRecalculation → MAI.ApiGatewayHelper"]
    A5 --> B20["MAI.LQMTools → [MAI]-CryptoJS"]
    A5 --> B21["MAI.ApiGatewayHelper → SimpleExternalRabbitMQ"]
    A6 --> B22["Как блок доходит до Proxy"]
    A6 --> B23["Кто снимает блок"]
    A6 --> B24["Квота истекла без события"]
    A6 --> B25["Одновременная запись расхода"]
    A6 --> B26["Вложенное подключение и таймаут"]
    B13 --> C1["PATCH token/uuid/update в TMS"]
    B14 --> C2["Middleware читает mai_blocked_nexus?"]
    B22 --> C3["updateMWConfig не вызывается"]
    B23 --> C4["Шедулер удалял блоки, теперь некому"]
    B24 --> C5["Блок 'Quota expired' ставился по времени"]
    B25 --> C6["usage += aToken — гонка при нескольких consumer"]
    B26 --> C7["Script Include подключается внутри функции"]
    B26 --> C8["Таймаут consumer 10 сек, blockToken — синхронный HTTP"]

Что происходит с одним событием потребления: откуда оно приходит, какой скрипт его ловит, какие Script Include и в каком порядке вызываются, что пишется в таблицы и как блокировка доходит до клиента. Собрано по снятому со стенда коду и ТЗ 14.10.2026.

есть на стенде сделано в src запланировано ждёт ответа Q#

## Коротко

- Приход. Proxy уже публикует каждое событие в exchange x.platform.app.v1. ClickHouse читает его из своей очереди. SimpleOne получит копию через свою очередь, привязанную к тому же exchange.
- Вход в SimpleOne. Запись sys_rabbitmq_consumer слушает очередь и на каждое сообщение запускает короткий скрипт. Скрипт подключает Script Include-обработчик и вызывает run(). Так же сейчас устроен вход от TMS (TMSCallback).
- Обработка. Обработчик по очереди: разбирает событие и находит записи (T1) → отсекает повтор (T2) → выбирает правила квоты (T6, уже сделано) → прибавляет расход (T3) → при превышении ставит блок (T4).
- Блокировка. Блок пишется в mai_blocked_nexus. Для токена дополнительно уходит запрос в TMS. Как Proxy узнаёт о блоках лицензии и консьюмера, в коде не найдено — это главный открытый вопрос.
## Общая схема

```mermaid
flowchart TB
  P[Proxy] -->|событие JSON по контракту| X{{"exchange x.platform.app.v1direct · ключ message_log.delivery"}}
  X --> QCH]] --> CH[(ClickHouseainergy.message_log)]
  X --> QSO]]:::q

  subgraph SO["SimpleOne"]
    direction TB
    CON["sys_rabbitmq_consumerпо записи на хост RabbitMQскрипт: importIncludeScript + run()"]:::plan
    H["Обработчик · Script Include, имя не выбраноT1 разбор и поиск записейT2 защита от повтора"]:::plan
    QR["MAI.QuotaRecalculationT6 getQuotaLimitsRules — готовоT3 пересчёт · T4 реакция — план"]:::done
    LT["MAI.LicenseToolsgetLimitsRule · getActiveQuota"]:::stand
    LQ["MAI.LQMToolsblockToken / unblockToken"]:::stand
    AG["MAI.ApiGatewayHelperupdateMWConfig"]:::stand
    T1[("mai_client_installation · mai_lqm_consumermai_api_token · mai_nexus_configuration")]
    TR[("mai_quota_limits_rule")]
    TQ[("mai_quota.usagemai_quota.quota_exceeded")]
    TB[("mai_blocked_nexus")]
    TP[("обработанные uuidхранилище — Q3")]:::q
  end

  QSO --> CON --> H
  H -->|ищет sys_id| T1
  H -->|проверяет и отмечает| TP
  H --> QR
  QR --> LT
  LT --> TR
  LT -->|активная квота| TQ
  QR -->|"+= aToken"| TQ
  QR -->|блок 429| TB
  QR -->|блок токена| LQ -->|PATCH token/uuid/update| TMS[TMS · Token Storage]
  QR -->|режим throttle| AG -->|"config.update в x.api-gateway.config.v1"| MW[middleware]
  TMS -.->|"ответ в q.tms.token.delivery → TMSCallback"| SO
  TB -.->|как доходит до Proxy — не найдено| P
  MW -.-> P

  classDef stand stroke:#1d6a86,stroke-width:2px
  classDef done stroke:#2f7d4f,stroke-width:2px
  classDef plan stroke:#b8862a,stroke-width:2px,stroke-dasharray: 4 3
  classDef q stroke:#d0563f,stroke-width:2px,stroke-dasharray: 6 4
```

Синие рамки — код со стенда, зелёная — новый код в src, жёлтый пунктир — запланировано, красный пунктир — ждёт ответа.

## Последовательность вызовов на одно событие

```mermaid

  autonumber
  participant P as Proxy
  participant R as RabbitMQ
  participant C as consumer-скрипт
  participant H as Обработчик (T1, T2)
  participant Q as MAI.QuotaRecalculation
  participant L as MAI.LicenseTools
  participant DB as Таблицы
  participant K as MAI.LQMTools → TMS

  P->>R: publish в x.platform.app.v1
  R->>C: сообщение: body (строка), headers
  C->>H: ss.importIncludeScript('Обработчик')new Обработчик(body).run()
  H->>H: JSON.parse · несколько строк? (Q15)
  H->>DB: client → консьюмер и лицензияtokenId → токен (мастер? Q14)nexus → sys_id нексуса
  H->>DB: uuid уже обработан? (Q3, Q13)
  alt повтор
    H-->>C: выход без изменений
  else новое событие
    H->>Q: getQuotaLimitsRules(L, C, T, N)
    Q->>L: getLimitsRule × 6
    L->>DB: mai_quota_limits_rule
    L-->>Q: 6 правил или null
    Q->>L: getActiveQuota(L)
    L->>DB: mai_quota (активная)
    Q->>DB: usage += aToken, quota_exceeded
    opt лимит достигнут (состав — Q5, порядок — Q11)
      Q->>DB: insert mai_blocked_nexus (429)
      Q->>K: blockToken(token, 'Quota')
    end
    H->>DB: отметить uuid обработанным
  end
  C-->>R: скрипт завершился → ack? исключение → ? (Q7)
```

## Шаги, код и таблицы

# | Что происходит | Где код | Таблицы | Статус
1 | Proxy публикует событие. Proxy не меняется. | — | — | есть
2 | Своя очередь SimpleOne на том же exchange и ключе. В записи consumer'а полей exchange и ключа нет, привязку делают в RabbitMQ. | — | — | Q1Q16
3 | Платформа держит подключение и на каждое сообщение запускает скрипт записи с глобалами body и headers. Скрипт выполняется от run_as_user. | src/rabbitmq-consumers/, образец tms_token_delivery.js | sys_rabbitmq_consumer: host, port, vhost, queue_name, script | план
4 | Обработчик: JSON.parse, проверка полей, поиск записей по client, tokenId, nexus. Мастер-токен превращается в «без токена». | новый Script Include в src/script-includes/ | R: mai_client_installation, mai_lqm_consumer, mai_api_token, mai_nexus_configuration | T1Q2Q14Q15
5 | Повтор по uuid: если уже был — выход, ничего не меняем. | обработчик, isProcessed / markProcessed | хранилище не выбрано | T2Q3Q13
6 | Шесть правил: лицензия, лицензия × нексус, консьюмер, консьюмер × нексус, токен, токен × нексус. Каждое выбирает стендовая getLimitsRule, при отсутствии берётся правило уровнем выше. | MAI.QuotaRecalculation.getQuotaLimitsRules → MAI.LicenseTools.getLimitsRule | R: mai_quota_limits_rule | T6 готово
7 | Активная квота лицензии и прибавка расхода. Расход консьюмера, токена и нексуса хранить негде. | getActiveQuota + новый код в MAI.QuotaRecalculation | R/W: mai_quota.usage, quota_exceeded | T3Q4Q8
8 | Сравнение расхода с лимитом. При превышении: запись блока; для токена — blockToken; для режима throttle — updateMWConfig. | MAI.QuotaRecalculation → MAI.LQMTools, MAI.ApiGatewayHelper | W: mai_blocked_nexus; R: mai_license.enforcement_mode | T4Q5Q11
9 | blockToken расшифровывает ключ TMS и шлёт PATCH со статусом BLOCKED. Локально ничего не пишет: флаг blocked_by_quota ставит TMSCallback, когда TMS пришлёт ответ в q.tms.token.delivery. | MAI.LQMTools → [MAI]-CryptoJS; обратно TMSCallback | W: mai_api_token.blocked_by_quota, status, reason | есть
10 | Скрипт завершился или бросил исключение. Что платформа делает с сообщением в каждом случае, не описано. | — | — | Q7

## Какой Script Include кого подключает

```mermaid
flowchart LR
  S["consumer-скриптsys_rabbitmq_consumer"] -->|importIncludeScript| H["Обработчикимя не выбрано"]
  H -->|importIncludeScript| QR["MAI.QuotaRecalculation"]
  QR -->|на верхнем уровне файла| LT["MAI.LicenseTools"]
  QR -.->|T4, план| LQ["MAI.LQMTools"]
  QR -.->|T4, план| AG["MAI.ApiGatewayHelper"]
  LQ -->|внутри blockToken| CJ["[MAI]-CryptoJSdecrypt"]
  AG --> RMQ["SimpleExternalRabbitMQплатформа"]
  OLD["MAI.UsageClickHouse"]:::old
  CHT["MAI.CHTools"]:::old
  OLD --> CHT
  classDef old stroke-dasharray: 3 3
```

Пунктиром внизу — то, чем пользовался шедулер для подсчёта из ClickHouse. В новом пути оно не вызывается. В эмуляторе MAI.CHTools и [MAI]-CryptoJS подставляются заглушками.

## Что в этой цепочке пока не определено

Как блок доходит до ProxyБлоки лицензии и консьюмера пишутся в mai_blocked_nexus, но в снятом коде никто после этого не шлёт конфиг в middleware: бизнес-правил в выгрузке нет, updateMWConfig('nexuses') никто не вызывает. Значит, это делает что-то, что мы не сняли, или middleware сам читает таблицу. Для токена путь есть: blockToken → TMS.

Кто снимет блокШедулер сам удалял неактуальные блоки и разблокировал токены при каждом проходе. В событийной схеме событие только прибавляет расход. При новой квоте, новом периоде или увеличении лимита снимать блок некому.

«Квота истекла» без событияБлок «Quota expired» шедулер ставил по времени, когда у лицензии нет активной квоты. Событие этот случай не поймает.

Одновременная запись расходаusage += aToken — это «прочитать, прибавить, записать». Если consumer'ов несколько (по одному на хост, как у TMS), два события одной лицензии могут затереть прибавку друг друга.

Вложенное подключение и таймаутНа стенде Script Include подключает другой Script Include только внутри функции, а не на верхнем уровне файла, как делает MAI.QuotaRecalculation. На инстансе это не проверено. Кроме того, у consumer'а таймаут 10 секунд, а blockToken делает синхронный HTTP-запрос в TMS.
