Восстановление сети
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,186 +0,0 @@
|
||||
# Умная синхронизация сообщений при восстановлении сети
|
||||
|
||||
## Проблема
|
||||
При восстановлении соединения после офлайна, приложение запрашивало **все сообщения заново** (последние 50 для каждого чата), вместо того чтобы получить только **новые сообщения**, которые пришли пока клиент был офлайн.
|
||||
|
||||
## Решение
|
||||
Добавлен новый параметр API `afterSequenceId` который позволяет запрашивать только сообщения с sequenceId > указанного.
|
||||
|
||||
## Изменения
|
||||
|
||||
### Бэкенд
|
||||
|
||||
#### 1. IMessageRepository.cs
|
||||
Добавлен метод для получения сообщений ПОСЛЕ указанного sequenceId:
|
||||
```csharp
|
||||
Task<List<Message>> GetChatMessagesAfterAsync(Guid chatId, long sequenceId, int limit, CancellationToken cancellationToken);
|
||||
```
|
||||
|
||||
#### 2. MessageRepository.cs
|
||||
Реализация метода:
|
||||
```csharp
|
||||
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`:
|
||||
```csharp
|
||||
public record GetMessagesQuery(
|
||||
Guid UserId,
|
||||
Guid ChatId,
|
||||
string? Cursor,
|
||||
long? Pivot = null,
|
||||
long? AfterSequenceId = null, // НОВЫЙ ПАРАМЕТР
|
||||
int? Limit = null
|
||||
) : IQuery<List<MessageDetailDto>>;
|
||||
```
|
||||
|
||||
Обработчик использует новый метод:
|
||||
```csharp
|
||||
if (request.AfterSequenceId.HasValue)
|
||||
{
|
||||
messages = await _messageRepository.GetChatMessagesAfterAsync(
|
||||
request.ChatId,
|
||||
request.AfterSequenceId.Value,
|
||||
queryLimit,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. MessagesEndpoints.cs
|
||||
Добавлен query параметр в endpoint:
|
||||
```csharp
|
||||
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:
|
||||
```kotlin
|
||||
@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
|
||||
Добавлен метод:
|
||||
```kotlin
|
||||
suspend fun getMessages(
|
||||
chatId: String,
|
||||
cursor: String? = null,
|
||||
pivot: Long? = null,
|
||||
afterSequenceId: Long? = null, // НОВЫЙ ПАРАМЕТР
|
||||
limit: Int? = null
|
||||
): List<Message>
|
||||
```
|
||||
|
||||
Добавлен метод для получения последнего sequenceId:
|
||||
```kotlin
|
||||
suspend fun getLastKnownSequenceId(chatId: String): Int?
|
||||
```
|
||||
|
||||
#### 3. ChatRepositoryImpl.kt
|
||||
Реализация:
|
||||
```kotlin
|
||||
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
|
||||
Умная синхронизация:
|
||||
```kotlin
|
||||
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
|
||||
|
||||
```http
|
||||
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
|
||||
```
|
||||
@@ -89,7 +89,6 @@ class SignalRNotificationObserver @Inject constructor(
|
||||
// При восстановлении соединения SignalR обновляем список чатов и вступаем в них
|
||||
signalrClient.status
|
||||
.filter { it == ConnectionStatus.CONNECTED }
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
android.util.Log.d("SignalRNtfObserver", "SignalR connected, refreshing chats and joining rooms")
|
||||
onSignalRConnected()
|
||||
|
||||
Reference in New Issue
Block a user