Files
forkmessager/client-mobile/SYNC_IMPROVEMENTS.md
T

6.2 KiB

Умная синхронизация сообщений при восстановлении сети

Проблема

При восстановлении соединения после офлайна, приложение запрашивало все сообщения заново (последние 50 для каждого чата), вместо того чтобы получить только новые сообщения, которые пришли пока клиент был офлайн.

Решение

Добавлен новый параметр API afterSequenceId который позволяет запрашивать только сообщения с sequenceId > указанного.

Изменения

Бэкенд

1. IMessageRepository.cs

Добавлен метод для получения сообщений ПОСЛЕ указанного sequenceId:

Task<List<Message>> GetChatMessagesAfterAsync(Guid chatId, long sequenceId, int limit, CancellationToken cancellationToken);

2. MessageRepository.cs

Реализация метода:

public async Task<List<Message>> GetChatMessagesAfterAsync(Guid chatId, long sequenceId, int limit, CancellationToken cancellationToken)
{
    var builder = Builders<Message>.Filter;
    var filter = builder.And(
        builder.Eq(m => m.ChatId, chatId),
        builder.Gt(m => m.SequenceId, sequenceId)
    );

    return await _messages.Find(filter)
        .SortBy(m => m.SequenceId)
        .Limit(limit)
        .ToListAsync(cancellationToken);
}

3. GetMessagesQuery.cs

Добавлен параметр AfterSequenceId:

public record GetMessagesQuery(
    Guid UserId, 
    Guid ChatId, 
    string? Cursor, 
    long? Pivot = null, 
    long? AfterSequenceId = null,  // НОВЫЙ ПАРАМЕТР
    int? Limit = null
) : IQuery<List<MessageDetailDto>>;

Обработчик использует новый метод:

if (request.AfterSequenceId.HasValue)
{
    messages = await _messageRepository.GetChatMessagesAfterAsync(
        request.ChatId, 
        request.AfterSequenceId.Value, 
        queryLimit, 
        cancellationToken
    );
}

4. MessagesEndpoints.cs

Добавлен query параметр в endpoint:

group.MapGet("chat/{chatId:guid}", async (
    [FromRoute] Guid chatId, 
    [FromQuery] string? cursor, 
    [FromQuery] long? afterSequenceId,  // НОВЫЙ ПАРАМЕТР
    [FromQuery] long? pivot,
    [FromQuery] int? limit,
    ISender sender, 
    IUserContext userContext, 
    CancellationToken ct) => { ... }
);

Мобильное приложение

1. ChatApi.kt

Добавлен параметр в API:

@GET("messages/chat/{chatId}")
suspend fun getMessages(
    @Path("chatId") chatId: String,
    @Query("cursor") cursor: String? = null,
    @Query("pivot") pivot: Long? = null,
    @Query("afterSequenceId") afterSequenceId: Long? = null,  // НОВЫЙ ПАРАМЕТР
    @Query("limit") limit: Int? = 50
): List<MessageDto>

2. ChatRepository.kt

Добавлен метод:

suspend fun getMessages(
    chatId: String, 
    cursor: String? = null, 
    pivot: Long? = null, 
    afterSequenceId: Long? = null,  // НОВЫЙ ПАРАМЕТР
    limit: Int? = null
): List<Message>

Добавлен метод для получения последнего sequenceId:

suspend fun getLastKnownSequenceId(chatId: String): Int?

3. ChatRepositoryImpl.kt

Реализация:

override suspend fun getLastKnownSequenceId(chatId: String): Int? {
    return try {
        messageDao.getMaxSequenceId(chatId)
    } catch (e: Exception) {
        Log.e(TAG, "Failed to get last sequenceId", e)
        null
    }
}

4. SignalRNotificationObserver.kt

Умная синхронизация:

private fun syncMissedMessages() {
    scope.launch {
        val chats = chatRepository.getChats()
        
        chats.forEach { chat ->
            val lastSequenceId = chatRepository.getLastKnownSequenceId(chat.id)
            
            if (lastSequenceId != null) {
                // Запрашиваем только сообщения ПОСЛЕ последнего известного
                chatRepository.getMessages(
                    chatId = chat.id,
                    afterSequenceId = lastSequenceId.toLong(),
                    limit = 100
                )
            } else {
                // Нет локальных сообщений - загружаем последние 50
                chatRepository.getMessages(chatId = chat.id, limit = 50)
            }
        }
    }
}

Преимущества

До изменений

  • Запрашивались все последние 50 сообщений для каждого чата
  • При 20 чатах: 20 × 50 = 1000 сообщений из сети
  • Дублирование данных, медленная синхронизация

После изменений

  • Запрашиваются только новые сообщения с последнего sequenceId
  • При 20 чатах и 5 новых сообщениях в каждом: 20 × 5 = 100 сообщений
  • В 10 раз меньше трафика
  • Быстрая синхронизация

Пример использования API

GET /api/messages/chat/{chatId}?afterSequenceId=12345&limit=100

Ответит только с сообщениями где sequenceId > 12345.

Тестирование

  1. Откройте приложение, загрузите чаты
  2. Отключите сеть
  3. Отправьте несколько сообщений с другого устройства
  4. Включите сеть
  5. Проверьте логи: должно быть fetching newer... и загрузка только новых сообщений

Логи

SignalRNtfObserver: Starting missed messages sync...
SignalRNtfObserver: Syncing 5 chats for missed messages
SignalRNtfObserver: Chat abc-123: last known seqId=100, fetching newer...
SignalRNtfObserver: Chat abc-123: fetched 3 new messages
SignalRNtfObserver: Synced chat abc-123