Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Deal.Telegram.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// Зеркало каталога диалогов тенанта в памяти telegram-service (план Task 10, Dialogs/DialogCatalog.cs;
|
||||
/// Ruling 7: «сервис держит зеркало мониторинга в памяти», ядро — владелец списка мониторинга в своей БД).
|
||||
///
|
||||
/// Тенант хранит два набора id диалогов: полный каталог (из refresh_dialogs/realtime_sweep, L468–519)
|
||||
/// и подмножество с включённым мониторингом. Мониторинг актуализируется тремя путями:
|
||||
/// * командой SetMonitor/SetMonitorAll ядра (обновление одной записи / всех сразу);
|
||||
/// * ответом Ingress.SyncDialogs (ядро применило entries с autoMonitorNew — L244–246 «_reload_monitored»);
|
||||
/// * очисткой после Logout/отключения аккаунта (прототип L203: `_monitored.clear()`).
|
||||
/// Все операции потокобезопасны; зеркало чисто в памяти — потерю при рестарте догоняет realtime_sweep.
|
||||
/// </summary>
|
||||
public sealed class DialogCatalog
|
||||
{
|
||||
private readonly object _gate = new();
|
||||
private readonly Dictionary<string, HashSet<string>> _knownByTenant = new(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, HashSet<string>> _monitoredByTenant = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Обновляет полный каталог диалогов тенанта (entries refresh/realtime_sweep). Мониторинг при этом
|
||||
/// не трогается — актуальный monitored-набор приходит ответом SyncDialogs (или командами SetMonitor).
|
||||
/// </summary>
|
||||
/// <param name="tenantId">Id тенанта.</param>
|
||||
/// <param name="dialogIds">Актуальные id каталога (entries списка диалогов).</param>
|
||||
public void ReplaceKnown(string tenantId, IEnumerable<string> dialogIds)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
_knownByTenant[tenantId] = new HashSet<string>(dialogIds, StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Включает/выключает мониторинг одного диалога (SetMonitor RPC, set_monitor L536–546). Команда
|
||||
/// приходит от ядра после обновления его БД; зеркало повторяет решение без собственной проверки
|
||||
/// «известности» — запись может появиться раньше каталога (включение после рестарта сервиса).
|
||||
/// </summary>
|
||||
/// <param name="tenantId">Id тенанта.</param>
|
||||
/// <param name="dialogId">Id диалога.</param>
|
||||
/// <param name="enabled">Мониторить (true) или выключить (false).</param>
|
||||
public void SetMonitored(string tenantId, string dialogId, bool enabled)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(dialogId);
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
HashSet<string> monitored = MonitoredSet(tenantId);
|
||||
if (enabled)
|
||||
{
|
||||
monitored.Add(dialogId);
|
||||
}
|
||||
else
|
||||
{
|
||||
monitored.Remove(dialogId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Заменяет monitored-набор ответом SyncDialogs (актуальный список ядра после SyncFromTelegram —
|
||||
/// авто-мониторинг новых/удаление отсутствующих, Ruling 7). Это авторитетный источник зеркала.
|
||||
/// </summary>
|
||||
/// <param name="tenantId">Id тенанта.</param>
|
||||
/// <param name="monitoredIds">Id диалогов с включённым мониторингом.</param>
|
||||
public void ReplaceMonitored(string tenantId, IEnumerable<string> monitoredIds)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
_monitoredByTenant[tenantId] = new HashSet<string>(monitoredIds, StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Включает/выключает мониторинг всех диалогов каталога (SetMonitorAll RPC, set_monitor_all
|
||||
/// L548–567). При включении учитываются и уже мониторящиеся записи (каталог может отставать от БД
|
||||
/// ядра после рестарта — «монитор» из ответа SyncDialogs приедет следующим циклом realtime_sweep).
|
||||
/// </summary>
|
||||
/// <param name="tenantId">Id тенанта.</param>
|
||||
/// <param name="enabled">Мониторить все (true) или снять мониторинг со всех (false).</param>
|
||||
/// <returns>Сколько диалогов в каталоге тенанта (SetMonitorAllReply.count).</returns>
|
||||
public int SetAllMonitored(string tenantId, bool enabled)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(tenantId);
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
HashSet<string> known = KnownSet(tenantId);
|
||||
if (enabled)
|
||||
{
|
||||
HashSet<string> monitored = MonitoredSet(tenantId);
|
||||
monitored.UnionWith(known);
|
||||
}
|
||||
else
|
||||
{
|
||||
_monitoredByTenant[tenantId] = new HashSet<string>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
return known.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет, мониторится ли диалог (фильтр realtime-событий L262 и догона sweep L429).
|
||||
/// </summary>
|
||||
/// <param name="tenantId">Id тенанта.</param>
|
||||
/// <param name="dialogId">Id диалога.</param>
|
||||
/// <returns>True — диалог в monitored-наборе тенанта.</returns>
|
||||
public bool IsMonitored(string tenantId, string dialogId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tenantId) || string.IsNullOrWhiteSpace(dialogId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
return _monitoredByTenant.TryGetValue(tenantId, out HashSet<string>? monitored) && monitored.Contains(dialogId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сколько диалогов знает каталог тенанта (ответ monitor-all, count каталога).
|
||||
/// </summary>
|
||||
/// <param name="tenantId">Id тенанта.</param>
|
||||
/// <returns>Размер каталога; 0 — каталог ещё не синхронизирован (нет refresh/sweep).</returns>
|
||||
public int KnownCount(string tenantId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
return _knownByTenant.TryGetValue(tenantId, out HashSet<string>? known) ? known.Count : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает копию мониторящихся id тенанта (для логов/тестов).
|
||||
/// </summary>
|
||||
/// <param name="tenantId">Id тенанта.</param>
|
||||
/// <returns>Набор мониторящихся id (пустой, если тенанта нет).</returns>
|
||||
public IReadOnlyCollection<string> SnapshotMonitored(string tenantId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
return _monitoredByTenant.TryGetValue(tenantId, out HashSet<string>? monitored)
|
||||
? new ReadOnlyCollection<string>(monitored.ToArray())
|
||||
: Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сбрасывает состояние тенанта (Logout/отключение аккаунта — прототип L203).
|
||||
/// </summary>
|
||||
/// <param name="tenantId">Id тенанта.</param>
|
||||
public void Reset(string tenantId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
_knownByTenant.Remove(tenantId);
|
||||
_monitoredByTenant.Remove(tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
// Полный каталог тенанта (создаёт пустой при первом обращении). Вызывается под _gate.
|
||||
// tenantId: Id тенанта.
|
||||
private HashSet<string> KnownSet(string tenantId)
|
||||
{
|
||||
if (!_knownByTenant.TryGetValue(tenantId, out HashSet<string>? known))
|
||||
{
|
||||
known = new HashSet<string>(StringComparer.Ordinal);
|
||||
_knownByTenant[tenantId] = known;
|
||||
}
|
||||
|
||||
return known;
|
||||
}
|
||||
|
||||
// Monitored-набор тенанта (создаёт пустой при первом обращении). Вызывается под _gate.
|
||||
// tenantId: Id тенанта.
|
||||
private HashSet<string> MonitoredSet(string tenantId)
|
||||
{
|
||||
if (!_monitoredByTenant.TryGetValue(tenantId, out HashSet<string>? monitored))
|
||||
{
|
||||
monitored = new HashSet<string>(StringComparer.Ordinal);
|
||||
_monitoredByTenant[tenantId] = monitored;
|
||||
}
|
||||
|
||||
return monitored;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user