Merge pull request 'Исправление ошибок и новые уведомления' (#16) from dev into main
All checks were successful
Build rosetta-wss / build (push) Successful in 4m39s

Reviewed-on: #16
This commit was merged in pull request #16.
This commit is contained in:
2026-03-20 18:12:59 +00:00
5 changed files with 75 additions and 38 deletions

View File

@@ -1,4 +1,5 @@
name: Build rosetta-wss name: Build rosetta-wss
run-name: Build and Deploy WSS
on: on:
push: push:
branches: branches:

View File

@@ -1,7 +1,7 @@
package im.rosetta.client; package im.rosetta.client;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import im.rosetta.client.tags.ECIAuthentificate; import im.rosetta.client.tags.ECIAuthentificate;
@@ -33,7 +33,7 @@ public class ClientManager {
} }
public boolean isClientConnected(String publicKey) { public boolean isClientConnected(String publicKey) {
HashSet<Client> clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey); Set<Client> clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey);
if(clients == null){ if(clients == null){
/** /**
* Нет клиентов с таким публичным ключом * Нет клиентов с таким публичным ключом
@@ -59,7 +59,7 @@ public class ClientManager {
* @throws ProtocolException если произошла ошибка при отправке пакета клиенту * @throws ProtocolException если произошла ошибка при отправке пакета клиенту
*/ */
public void sendPacketToAuthorizedPK(String publicKey, Packet packet) throws ProtocolException { public void sendPacketToAuthorizedPK(String publicKey, Packet packet) throws ProtocolException {
HashSet<Client> clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey); Set<Client> clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey);
if(clients == null){ if(clients == null){
/** /**
* Нет клиентов с таким публичным ключом, значит отправлять некому * Нет клиентов с таким публичным ключом, значит отправлять некому
@@ -91,7 +91,7 @@ public class ClientManager {
*/ */
public void retranslate(Client client, Packet packet) throws ProtocolException{ public void retranslate(Client client, Packet packet) throws ProtocolException{
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class); ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
HashSet<Client> clients = this.clientIndexer Set<Client> clients = this.clientIndexer
.getClients(ECIAuthentificate.class, "publicKey", eciAuthentificate.getPublicKey()); .getClients(ECIAuthentificate.class, "publicKey", eciAuthentificate.getPublicKey());
if(clients == null){ if(clients == null){
/** /**
@@ -129,7 +129,7 @@ public class ClientManager {
* @return список клиентов с таким публичным ключом, может быть пустым, если клиентов с таким публичным ключом нет * @return список клиентов с таким публичным ключом, может быть пустым, если клиентов с таким публичным ключом нет
*/ */
public List<Client> getPKClients(String publicKey) { public List<Client> getPKClients(String publicKey) {
HashSet<Client> clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey); Set<Client> clients = this.clientIndexer.getClients(ECIAuthentificate.class, "publicKey", publicKey);
if(clients == null){ if(clients == null){
/** /**
* Нет клиентов с таким публичным ключом * Нет клиентов с таким публичным ключом

View File

@@ -9,6 +9,9 @@ import java.util.concurrent.Executors;
import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions; import com.google.firebase.FirebaseOptions;
import com.google.firebase.messaging.ApnsConfig;
import com.google.firebase.messaging.Aps;
import com.google.firebase.messaging.ApsAlert;
import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification; import com.google.firebase.messaging.Notification;
@@ -51,13 +54,7 @@ public class FirebaseDispatcher {
} }
} }
/** public void sendPushNotification(String publicKey, String title, String messageText, String senderPublicKey) {
* Отправляет push-уведомление пользователю с данным публичным ключом (асинхронно)
* @param publicKey публичный ключ пользователя
* @param title заголовок уведомления
* @param messageText текст уведомления
*/
public void sendPushNotification(String publicKey, String title, String messageText) {
executor.submit(() -> { executor.submit(() -> {
try { try {
List<String> tokens = userService.getNotificationsTokens(publicKey); List<String> tokens = userService.getNotificationsTokens(publicKey);
@@ -72,6 +69,17 @@ public class FirebaseDispatcher {
.setTitle(title) .setTitle(title)
.setBody(messageText) .setBody(messageText)
.build()) .build())
.setApnsConfig(ApnsConfig.builder()
.setAps(Aps.builder()
.setMutableContent(true)
.setSound("default")
.setAlert(ApsAlert.builder()
.setTitle(title)
.setBody(messageText)
.build())
.build())
.build())
.putData("sender_public_key", senderPublicKey)
.setToken(token) .setToken(token)
.build(); .build();
@@ -86,6 +94,30 @@ public class FirebaseDispatcher {
}); });
} }
/**
* Отправляет push-уведомление пользователю с данным публичным ключом (асинхронно)
* @param publicKey публичный ключ пользователя
* @param title заголовок уведомления
* @param messageText текст уведомления
*/
public void sendPushNotification(String publicKey, String title, String messageText) {
sendPushNotification(publicKey, title, messageText, null);
}
/**
* Отправляет push-уведомление нескольким пользователям (асинхронно)
* @param publicKeys список публичных ключей пользователей
* @param title заголовок уведомления
* @param messageText текст уведомления
*/
public void sendPushNotification(List<String> publicKeys, String title, String messageText, String senderPublicKey) {
executor.submit(() -> {
for (String publicKey : publicKeys) {
sendPushNotificationSync(publicKey, title, messageText, senderPublicKey);
}
});
}
/** /**
* Отправляет push-уведомление нескольким пользователям (асинхронно) * Отправляет push-уведомление нескольким пользователям (асинхронно)
* @param publicKeys список публичных ключей пользователей * @param publicKeys список публичных ключей пользователей
@@ -95,12 +127,12 @@ public class FirebaseDispatcher {
public void sendPushNotification(List<String> publicKeys, String title, String messageText) { public void sendPushNotification(List<String> publicKeys, String title, String messageText) {
executor.submit(() -> { executor.submit(() -> {
for (String publicKey : publicKeys) { for (String publicKey : publicKeys) {
sendPushNotificationSync(publicKey, title, messageText); sendPushNotificationSync(publicKey, title, messageText, null);
} }
}); });
} }
private void sendPushNotificationSync(String publicKey, String title, String messageText) { private void sendPushNotificationSync(String publicKey, String title, String messageText, String senderPublicKey) {
try { try {
List<String> tokens = userService.getNotificationsTokens(publicKey); List<String> tokens = userService.getNotificationsTokens(publicKey);
if (tokens == null || tokens.isEmpty()) { if (tokens == null || tokens.isEmpty()) {

View File

@@ -4,13 +4,15 @@ import java.util.List;
import im.rosetta.client.ClientManager; import im.rosetta.client.ClientManager;
import im.rosetta.client.tags.ECIAuthentificate; import im.rosetta.client.tags.ECIAuthentificate;
import im.rosetta.database.entity.User;
import im.rosetta.database.repository.BufferRepository; import im.rosetta.database.repository.BufferRepository;
import im.rosetta.database.repository.GroupRepository; import im.rosetta.database.repository.GroupRepository;
import im.rosetta.database.repository.UserRepository;
import im.rosetta.packet.Packet11Typeing; import im.rosetta.packet.Packet11Typeing;
import im.rosetta.packet.Packet7Read; import im.rosetta.packet.Packet7Read;
import im.rosetta.packet.base.PacketBaseDialog; import im.rosetta.packet.base.PacketBaseDialog;
import im.rosetta.service.services.BufferService; import im.rosetta.service.services.BufferService;
import im.rosetta.service.services.UserService;
import io.orprotocol.ProtocolException; import io.orprotocol.ProtocolException;
import io.orprotocol.client.Client; import io.orprotocol.client.Client;
import io.orprotocol.packet.PacketManager; import io.orprotocol.packet.PacketManager;
@@ -29,6 +31,8 @@ public class MessageDispatcher {
private final BufferRepository bufferRepository = new BufferRepository(); private final BufferRepository bufferRepository = new BufferRepository();
private final BufferService bufferService; private final BufferService bufferService;
private final FirebaseDispatcher firebaseDispatcher = new FirebaseDispatcher(); private final FirebaseDispatcher firebaseDispatcher = new FirebaseDispatcher();
private final UserRepository userRepository = new UserRepository();
private final UserService userService = new UserService(userRepository);
public MessageDispatcher(ClientManager clientManager, PacketManager packetManager) { public MessageDispatcher(ClientManager clientManager, PacketManager packetManager) {
this.clientManager = clientManager; this.clientManager = clientManager;
@@ -101,7 +105,7 @@ public class MessageDispatcher {
/** /**
* Отправляем PUSH уведомление * Отправляем PUSH уведомление
*/ */
this.firebaseDispatcher.sendPushNotification(groupMembersPublicKeys, "Rosetta", "New message in group"); this.firebaseDispatcher.sendPushNotification(groupMembersPublicKeys, "Rosetta", "New message in group", toPublicKey.replace("#group:", ""));
} }
/** /**
@@ -115,7 +119,13 @@ public class MessageDispatcher {
public void sendPeer(PacketBaseDialog packet, Client client, boolean bufferizationNeed) throws ProtocolException { public void sendPeer(PacketBaseDialog packet, Client client, boolean bufferizationNeed) throws ProtocolException {
String fromPublicKey = packet.getFromPublicKey(); String fromPublicKey = packet.getFromPublicKey();
String toPublicKey = packet.getToPublicKey(); String toPublicKey = packet.getToPublicKey();
User user = this.userService.fromClient(client);
if(user == null){
/**
* Если пользователь не найден, то не отправляем сообщение, так как у нас нет информации о том, кто отправляет сообщение
*/
return;
}
/** /**
* Ретранслируем сообщение ВСЕМ авторизованным сессиям отправителя КРОМЕ текущей, * Ретранслируем сообщение ВСЕМ авторизованным сессиям отправителя КРОМЕ текущей,
* чтобы синхронизировать отправленные сообщения * чтобы синхронизировать отправленные сообщения
@@ -154,7 +164,7 @@ public class MessageDispatcher {
/** /**
* Отправляем PUSH уведомление получателю * Отправляем PUSH уведомление получателю
*/ */
this.firebaseDispatcher.sendPushNotification(toPublicKey, "Rosetta", "New message"); this.firebaseDispatcher.sendPushNotification(toPublicKey, user.getTitle(), "New message");
} }
/** /**

View File

@@ -1,7 +1,7 @@
package io.orprotocol.index; package io.orprotocol.index;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import io.orprotocol.client.Client; import io.orprotocol.client.Client;
@@ -19,14 +19,8 @@ public class ClientIndexer {
* Ключ третьего уровня - значение поля. * Ключ третьего уровня - значение поля.
* Значение - клиент. * Значение - клиент.
* Структура: tagClass -> indexName -> indexValue -> Client * Структура: tagClass -> indexName -> indexValue -> Client
*
*
* В качестве клиента используем HashSet потому что он очень хорош для операции contains,
* в отличие от ArrayList, который при поиске перебирает элементы один за другим,
* HashSet использует хэш таблицу и находит нужный объект почти
* мгновенно, независимо от того, 10 там элементов или миллион.
*/ */
private final Map<Class<?>, Map<String, Map<Object, HashSet<Client>>>> indices private final ConcurrentHashMap<Class<?>, ConcurrentHashMap<String, ConcurrentHashMap<Object, Set<Client>>>> indices
= new ConcurrentHashMap<>(); = new ConcurrentHashMap<>();
public <T extends ECITag> void indexTag(Client client, Class<T> tagClass, T tag) { public <T extends ECITag> void indexTag(Client client, Class<T> tagClass, T tag) {
@@ -53,7 +47,7 @@ public class ClientIndexer {
* Если использовать putIfAbsent, то он вернет null если значение там уже есть, * Если использовать putIfAbsent, то он вернет null если значение там уже есть,
* что не подходит * что не подходит
*/ */
Map<String, Map<Object, HashSet<Client>>> tagIndices = this.indices.computeIfAbsent( ConcurrentHashMap<String, ConcurrentHashMap<Object, Set<Client>>> tagIndices = this.indices.computeIfAbsent(
tagClass, tagClass,
k -> new ConcurrentHashMap<>()); k -> new ConcurrentHashMap<>());
@@ -76,7 +70,7 @@ public class ClientIndexer {
/** /**
* Инициализируем имя индекса, почему compute, а не put - написано выше * Инициализируем имя индекса, почему compute, а не put - написано выше
*/ */
Map<Object, HashSet<Client>> index = tagIndices.computeIfAbsent( Map<Object, Set<Client>> index = tagIndices.computeIfAbsent(
indexName, indexName,
k -> new ConcurrentHashMap<>() k -> new ConcurrentHashMap<>()
); );
@@ -86,7 +80,7 @@ public class ClientIndexer {
* с одинковыми индексами, хотя такого лучше избегать, желательно чтобы индексы * с одинковыми индексами, хотя такого лучше избегать, желательно чтобы индексы
* были уникальными, тогда обработка будет быстрее всего * были уникальными, тогда обработка будет быстрее всего
*/ */
HashSet<Client> clients = index.computeIfAbsent(indexValue, k -> new HashSet<>()); Set<Client> clients = index.computeIfAbsent(indexValue, k -> ConcurrentHashMap.newKeySet());
/** /**
* Добавляем клиента в инициализиованный индекс * Добавляем клиента в инициализиованный индекс
*/ */
@@ -117,7 +111,7 @@ public class ClientIndexer {
* @internal * @internal
*/ */
public <T extends ECITag> void removeTagIndex(Client client, Class<T> tagClass) { public <T extends ECITag> void removeTagIndex(Client client, Class<T> tagClass) {
Map<String, Map<Object, HashSet<Client>>> tagIndices = indices.get(tagClass); ConcurrentHashMap<String, ConcurrentHashMap<Object, Set<Client>>> tagIndices = indices.get(tagClass);
if (tagIndices == null) { if (tagIndices == null) {
/** /**
* Индекса и так не было, удалять нечего * Индекса и так не было, удалять нечего
@@ -128,12 +122,12 @@ public class ClientIndexer {
/** /**
* Удаляем все ключи indexName по tagClass если Client == client * Удаляем все ключи indexName по tagClass если Client == client
*/ */
for (Map<Object, HashSet<Client>> index : tagIndices.values()) { for (ConcurrentHashMap<Object, Set<Client>> index : tagIndices.values()) {
/** /**
* contains всегда использует переопределенный equals, по этому * contains всегда использует переопределенный equals, по этому
* обьекты клиентов сравниваются нормально * обьекты клиентов сравниваются нормально
*/ */
for(HashSet<Client> clients : index.values()){ for(Set<Client> clients : index.values()){
if(!clients.contains(client)){ if(!clients.contains(client)){
continue; continue;
} }
@@ -147,9 +141,9 @@ public class ClientIndexer {
* @internal * @internal
*/ */
public void removeClientFromIndex(Client client) { public void removeClientFromIndex(Client client) {
for(Map<String, Map<Object, HashSet<Client>>> tagIndices : this.indices.values()){ for(Map<String, ConcurrentHashMap<Object, Set<Client>>> tagIndices : this.indices.values()){
for(Map<Object, HashSet<Client>> index : tagIndices.values()){ for(Map<Object, Set<Client>> index : tagIndices.values()){
for(HashSet<Client> clients : index.values()){ for(Set<Client> clients : index.values()){
/** /**
* Этот тройной цикл не такой страшный, так как мы всего лишь * Этот тройной цикл не такой страшный, так как мы всего лишь
* проходим по всем тегам (их немного), дальше идем по всем значениям в тегах * проходим по всем тегам (их немного), дальше идем по всем значениям в тегах
@@ -173,7 +167,7 @@ public class ClientIndexer {
* @param indexValue значение в теге * @param indexValue значение в теге
* @return список клиентов с заданными значениями * @return список клиентов с заданными значениями
*/ */
public <T extends ECITag> HashSet<Client> getClients(Class<T> tagClass, String indexName, Object indexValue) { public <T extends ECITag> Set<Client> getClients(Class<T> tagClass, String indexName, Object indexValue) {
if(indexName == null || indexValue == null){ if(indexName == null || indexValue == null){
return null; return null;
} }
@@ -181,12 +175,12 @@ public class ClientIndexer {
/** /**
* Получение по индексу простое, так как каждое из заданных значений и есть ключ * Получение по индексу простое, так как каждое из заданных значений и есть ключ
*/ */
Map<String, Map<Object, HashSet<Client>>> tagIndices = indices.get(tagClass); ConcurrentHashMap<String, ConcurrentHashMap<Object, Set<Client>>> tagIndices = indices.get(tagClass);
if (tagIndices == null) { if (tagIndices == null) {
return null; return null;
} }
Map<Object, HashSet<Client>> index = tagIndices.get(indexName); ConcurrentHashMap<Object, Set<Client>> index = tagIndices.get(indexName);
if (index == null) { if (index == null) {
return null; return null;
} }