Docs security / security (pull_request) Successful in 6s
Docs verify / verify (pull_request) Successful in 11s
Conventional commits / commits (pull_request) Successful in 16s
Docs security / security (push) Successful in 9s
Docs verify / verify (push) Successful in 10s
159 lines
14 KiB
Markdown
159 lines
14 KiB
Markdown
# Загрузка портфеля
|
|
|
|
Поток начинается при открытии `/accounts/:accountId` пользователем
|
|
с действующей сессией. Его результат — полный нормализованный снимок
|
|
доступного счёта либо объяснимая ошибка. Браузер обращается к backend;
|
|
доступ к брокерскому токену и внешние запросы остаются на сервере.
|
|
|
|
## Основной обмен
|
|
|
|
Сначала frontend проверяет свой успешный кэш: в течение 30 секунд повторное
|
|
открытие может показать ответ без нового HTTP-запроса. Следующая схема
|
|
описывает запрос, который действительно дошёл до backend.
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant F as Frontend
|
|
participant B as Backend API
|
|
participant D as SQLite
|
|
participant T as T-Bank
|
|
F->>B: Запрос портфеля счёта с Bearer token
|
|
B->>B: Проверяет сессию
|
|
B->>D: Читает текущее подключение пользователя
|
|
D-->>B: Подключение либо отсутствие
|
|
alt Подключения нет
|
|
B-->>F: 409 TBANK_NOT_CONNECTED
|
|
else Подключение доступно
|
|
B->>B: Определяет поколение подключения
|
|
alt Снимок того же поколения не старше 30 секунд
|
|
B-->>F: Полный снимок, isStale=false
|
|
else Нужен новый снимок
|
|
B->>T: Получает открытые счета
|
|
T-->>B: Состав доступных счетов
|
|
alt Счёт недоступен
|
|
B-->>F: 404
|
|
else Счёт доступен
|
|
par Финансовые позиции
|
|
B->>T: GetPortfolio
|
|
T-->>B: Позиции и цены
|
|
and Доступные деньги
|
|
B->>T: GetPositions
|
|
T-->>B: Денежные остатки
|
|
end
|
|
B->>T: Метаданные отсутствующих инструментов по UID, затем FIGI
|
|
T-->>B: Метаданные, отсутствие или ошибка
|
|
B->>B: Нормализует состав и точно вычисляет суммы
|
|
B-->>F: Полный новый снимок, isStale=false
|
|
end
|
|
end
|
|
end
|
|
```
|
|
|
|
Свежий серверный снимок выдаётся после разрешения текущего сохранённого
|
|
подключения, но без нового `GetAccounts`. После истечения свежего кэша
|
|
проверка доступности счёта предшествует финансовым RPC. SQLite хранит
|
|
подключение, а не снимок или право владения счётом вне этого подключения.
|
|
|
|
Для нового снимка требуются оба финансовых ответа. Backend не выдаёт
|
|
позиции без денег или деньги без позиций как полный портфель.
|
|
Метаданные дополняют имя и идентификаторы, но их временная недоступность
|
|
не срывает финансовый результат. Поиск выполняется партиями по пять позиций,
|
|
чтобы крупный портфель не переполнял очередь одновременными запросами.
|
|
Порядок позиций сохраняется. Backend ищет сведения по UID, затем по FIGI,
|
|
если UID не дал пригодного названия или тикера.
|
|
Ожидание метаданных ограничено 30 секундами на снимок счёта. После этого
|
|
используется доступный кэш либо идентификатор, новые поиски для снимка
|
|
не запускаются. Уже начатые запросы могут закончиться позже и обновить общий
|
|
справочный кэш, но не меняют выданный финансовый снимок.
|
|
Общий серверный клиент обеспечивает TLS, очередь,
|
|
ограничение частоты, deadline RPC и разрешённые повторные попытки —
|
|
[граница интеграции](../../../integrations/tbank.md).
|
|
|
|
## Время и изменение подключения
|
|
|
|
| Слой | Что сохраняется | Условие повторного использования |
|
|
| --- | --- | --- |
|
|
| Frontend | Последний успешный HTTP-ответ для выбранного счёта и сессии | Свежим считается 30 секунд; при обычном обновлении уже показанный ответ остаётся видимым |
|
|
| Backend | Полный снимок пары пользователь–счёт в памяти процесса | До 30 секунд, только в том же поколении текущего подключения |
|
|
| Backend при временной ошибке | Тот же полный снимок | Не старше пяти минут с момента успешного сохранения, только в том же поколении |
|
|
| Метаданные инструмента | Успешный поиск или подтверждённое отсутствие | Свежими считаются 24 часа; общий кэш на 500 ключей UID/FIGI, ошибки не сохраняются |
|
|
|
|
Это параметры текущей реализации, а не самостоятельная политика этого
|
|
документа: [снимки](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/modules/investments/portfolio/application/portfolio.service.ts),
|
|
[метаданные](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/src/integrations/tbank/instrument-metadata.service.ts) и
|
|
[клиентский запрос](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/src/entities/portfolio/api/usePortfolio.ts).
|
|
|
|
Пять минут — предельный возраст успешного снимка, а не дополнительные
|
|
пять минут после окончания свежих 30 секунд. Запасной ответ сохраняет
|
|
исходное `lastSuccessfulAt` и получает `isStale: true`.
|
|
|
|
После истечения 24 часов backend обновляет справочные данные. Если обновление
|
|
временно недоступно, ранее успешные сведения остаются запасным вариантом,
|
|
пока запись есть в ограниченном кэше процесса. Ошибка не продлевает срок
|
|
свежести: следующий поиск снова пытается обновить запись. Это относится только
|
|
к названию, тикеру, ISIN и логотипу, а не к ценам или денежным значениям.
|
|
Перезапуск backend и вытеснение записи из кэша удаляют этот запасной вариант.
|
|
Неполный ответ без названия и тикера также не затирает известные сведения
|
|
и не обновляет срок их свежести. Подтверждённое отсутствие инструмента
|
|
заменяет прежнюю запись отрицательным результатом поиска.
|
|
|
|
Время снимка фиксируется при получении финансовых ответов, до ожидания
|
|
названий. Сроки серверного кэша отсчитываются от этого времени, поэтому
|
|
долгая загрузка справочных данных не делает старые суммы новыми.
|
|
Успешные справочные данные общие для всех пользователей; выполняющиеся
|
|
запросы объединяются только внутри одного брокерского токена, чтобы ошибка
|
|
его авторизации не передавалась другому подключению.
|
|
|
|
Успешное создание, замена или удаление подключения меняет границу
|
|
повторного использования финансовых данных. Старое или поздно завершившееся
|
|
получение снимка не делает его доступным новым запросам другого поколения.
|
|
Явная очистка памяти при изменении подключения дополняет эту проверку.
|
|
Frontend сбрасывает всё поддерево финансовых запросов при изменении
|
|
подключения и общий кэш сессии при выходе; старый портфель не служит
|
|
заглушкой загрузки нового подключения.
|
|
|
|
В общем режиме сначала определяется состав доступных счетов одного
|
|
подключения, затем получаются их снимки. Это
|
|
[не атомарное наблюдение нескольких счетов](../all-accounts.md#полнота-свежесть-и-доступ).
|
|
|
|
## Ошибки и результат для пользователя
|
|
|
|
| Причина | Ответ backend | Действие на экране |
|
|
| --- | --- | --- |
|
|
| Нет подключения | `409 TBANK_NOT_CONNECTED` | Открыть настройки T-Bank |
|
|
| Брокер отклонил токен | `400 INVALID_TBANK_TOKEN` | Открыть настройки; токен автоматически не удаляется |
|
|
| Счёт недоступен через текущее подключение | `404` | Вернуться к списку счетов |
|
|
| Временная ошибка, пригодный предыдущий снимок есть | `200`, `isStale: true` | Показать снимок и его исходное время с предупреждением |
|
|
| Временная ошибка, пригодного снимка нет | `503 TBANK_UNAVAILABLE` | Повторить загрузку |
|
|
| Неизвестная ошибка | Безопасная ошибка API | Повторить загрузку |
|
|
|
|
`INVALID_TBANK_TOKEN` и `404` не заменяются серверным запасным снимком.
|
|
Временная ошибка определения доступных счетов также может использовать
|
|
пригодный снимок того же поколения. При временном сбое метаданных сначала
|
|
используются ранее успешные сведения, а при их отсутствии — идентификатор
|
|
инструмента; это не устанавливает `isStale`. Отказ авторизации при поиске
|
|
метаданных не маскируется запасным названием.
|
|
|
|
Для запроса самого портфеля на клиенте нет polling, автоматического повтора или
|
|
обновления при возврате фокуса. При обновлении того же уже показанного
|
|
результата клиент оставляет его на экране, добавляя индикатор загрузки
|
|
или предупреждение с доступной с клавиатуры повторной попыткой.
|
|
Это клиентский сохранённый ответ, независимо от серверного `isStale`.
|
|
|
|
Frontend отображает название и остальные сведения из ответа портфеля.
|
|
Повторные попытки поиска для акций, облигаций и других типов инструментов
|
|
принадлежат [общему клиенту T-Bank на backend](../../../integrations/tbank.md#конфигурация-и-ограничения).
|
|
|
|
Первое обращение к крупному портфелю может потребовать много метаданных.
|
|
Переполнение общей очереди RPC может произойти при конкурирующих запросах;
|
|
поиск метаданных в этом случае повторяется с ограниченными паузами. После
|
|
успешного поиска метаданные используются повторно до истечения
|
|
кэша. Значение `TBANK_RATE_LIMIT_PER_SECOND` следует соотносить с квотой
|
|
источника; его увеличение не является гарантированным исправлением
|
|
ограничения самого T-Bank.
|
|
|
|
Проверяемые правила: [свежесть и запасной снимок](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-свежесть-и-fallback-полного-portfolio-snapshot),
|
|
[клиентские состояния](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/openspec/specs/portfolio-view/spec.md#requirement-состояния-и-ошибки-portfolio-на-клиенте).
|
|
Для расследования конкретного значения используйте
|
|
[порядок диагностики](../how-to/investigate-discrepancy.md).
|