Синхронизация сообщений для офлайн пользователей в группах
This commit is contained in:
@@ -3,15 +3,20 @@ package im.rosetta.database.entity;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import im.rosetta.database.CreateUpdateEntity;
|
import org.hibernate.annotations.Cascade;
|
||||||
import im.rosetta.database.converters.StringListConverter;
|
import org.hibernate.annotations.CascadeType;
|
||||||
|
|
||||||
|
import im.rosetta.database.CreateUpdateEntity;
|
||||||
|
import jakarta.persistence.CollectionTable;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Convert;
|
import jakarta.persistence.ElementCollection;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GenerationType;
|
import jakarta.persistence.GenerationType;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.OrderColumn;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,12 +33,19 @@ public class Group extends CreateUpdateEntity {
|
|||||||
@Column(name = "groupId")
|
@Column(name = "groupId")
|
||||||
private String groupId;
|
private String groupId;
|
||||||
|
|
||||||
@Convert(converter = StringListConverter.class)
|
|
||||||
@Column(name = "membersPublicKeys", nullable = false, columnDefinition = "TEXT")
|
@ElementCollection(fetch = FetchType.LAZY)
|
||||||
|
@CollectionTable(name = "group_members", joinColumns = @JoinColumn(name = "group_id"))
|
||||||
|
@Column(name = "public_key", nullable = false, columnDefinition = "TEXT")
|
||||||
|
@OrderColumn(name = "order_index")
|
||||||
|
@Cascade({ CascadeType.ALL, CascadeType.DELETE_ORPHAN })
|
||||||
private List<String> membersPublicKeys = new ArrayList<>();
|
private List<String> membersPublicKeys = new ArrayList<>();
|
||||||
|
|
||||||
@Convert(converter = StringListConverter.class)
|
@ElementCollection(fetch = FetchType.LAZY)
|
||||||
@Column(name = "bannedPublicKeys", nullable = false, columnDefinition = "TEXT")
|
@CollectionTable(name = "group_banned", joinColumns = @JoinColumn(name = "group_id"))
|
||||||
|
@Column(name = "public_key", nullable = false, columnDefinition = "TEXT")
|
||||||
|
@OrderColumn(name = "order_index")
|
||||||
|
@Cascade({ CascadeType.ALL, CascadeType.DELETE_ORPHAN })
|
||||||
private List<String> bannedPublicKeys = new ArrayList<>();
|
private List<String> bannedPublicKeys = new ArrayList<>();
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package im.rosetta.database.repository;
|
package im.rosetta.database.repository;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
|
||||||
|
import im.rosetta.database.QuerySession;
|
||||||
import im.rosetta.database.Repository;
|
import im.rosetta.database.Repository;
|
||||||
import im.rosetta.database.entity.Group;
|
import im.rosetta.database.entity.Group;
|
||||||
|
|
||||||
@@ -18,11 +22,16 @@ public class GroupRepository extends Repository<Group> {
|
|||||||
* @return список публичных ключей участников группы
|
* @return список публичных ключей участников группы
|
||||||
*/
|
*/
|
||||||
public List<String> findGroupMembers(String groupId) {
|
public List<String> findGroupMembers(String groupId) {
|
||||||
Group group = this.findByField("groupId", groupId);
|
try(QuerySession<Group> querySession = this.buildQuery("FROM Group g WHERE g.groupId = :groupId", new HashMap<String, Object>(){{
|
||||||
if(group == null) {
|
put("groupId", groupId);
|
||||||
|
}})){
|
||||||
|
Group group = querySession.getQuery().uniqueResult();
|
||||||
|
if(group != null) {
|
||||||
|
Hibernate.initialize(group.getMembersPublicKeys());
|
||||||
|
return group.getMembersPublicKeys();
|
||||||
|
}
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
return group.getMembersPublicKeys();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +54,18 @@ public class GroupRepository extends Repository<Group> {
|
|||||||
* @return группа с заданным id, или null, если группа не найдена
|
* @return группа с заданным id, или null, если группа не найдена
|
||||||
*/
|
*/
|
||||||
public Group getGroup(String groupId) {
|
public Group getGroup(String groupId) {
|
||||||
return this.findByField("groupId", groupId);
|
String hql = "FROM Group g WHERE g.groupId = :groupId";
|
||||||
|
HashMap<String, Object> params = new HashMap<>();
|
||||||
|
params.put("groupId", groupId);
|
||||||
|
|
||||||
|
try (QuerySession<Group> qs = this.buildQuery(hql, params)) {
|
||||||
|
Group group = qs.getQuery().uniqueResult();
|
||||||
|
if (group != null) {
|
||||||
|
Hibernate.initialize(group.getMembersPublicKeys());
|
||||||
|
Hibernate.initialize(group.getBannedPublicKeys());
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,8 +85,12 @@ public class GroupRepository extends Repository<Group> {
|
|||||||
* @param memberPublicKey публичный ключ участника, которого нужно добавить в группу
|
* @param memberPublicKey публичный ключ участника, которого нужно добавить в группу
|
||||||
*/
|
*/
|
||||||
public void addMemberToGroup(String groupId, String memberPublicKey) {
|
public void addMemberToGroup(String groupId, String memberPublicKey) {
|
||||||
Group group = this.findByField("groupId", groupId);
|
try(QuerySession<Group> querySession = this.buildQuery("FROM Group g WHERE g.groupId = :groupId", new HashMap<String, Object>(){{
|
||||||
|
put("groupId", groupId);
|
||||||
|
}})){
|
||||||
|
Group group = querySession.getQuery().uniqueResult();
|
||||||
if(group != null) {
|
if(group != null) {
|
||||||
|
Hibernate.initialize(group.getMembersPublicKeys());
|
||||||
List<String> membersPublicKeys = group.getMembersPublicKeys();
|
List<String> membersPublicKeys = group.getMembersPublicKeys();
|
||||||
if(!membersPublicKeys.contains(memberPublicKey)) {
|
if(!membersPublicKeys.contains(memberPublicKey)) {
|
||||||
membersPublicKeys.add(memberPublicKey);
|
membersPublicKeys.add(memberPublicKey);
|
||||||
@@ -75,6 +99,7 @@ public class GroupRepository extends Repository<Group> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Удалить участника из группы
|
* Удалить участника из группы
|
||||||
@@ -82,8 +107,12 @@ public class GroupRepository extends Repository<Group> {
|
|||||||
* @param memberPublicKey публичный ключ участника, которого нужно удалить из группы
|
* @param memberPublicKey публичный ключ участника, которого нужно удалить из группы
|
||||||
*/
|
*/
|
||||||
public void removeMemberFromGroup(String groupId, String memberPublicKey) {
|
public void removeMemberFromGroup(String groupId, String memberPublicKey) {
|
||||||
Group group = this.findByField("groupId", groupId);
|
try(QuerySession<Group> querySession = this.buildQuery("FROM Group g WHERE g.groupId = :groupId", new HashMap<String, Object>(){{
|
||||||
|
put("groupId", groupId);
|
||||||
|
}})){
|
||||||
|
Group group = querySession.getQuery().uniqueResult();
|
||||||
if(group != null) {
|
if(group != null) {
|
||||||
|
Hibernate.initialize(group.getMembersPublicKeys());
|
||||||
List<String> membersPublicKeys = group.getMembersPublicKeys();
|
List<String> membersPublicKeys = group.getMembersPublicKeys();
|
||||||
if(membersPublicKeys.contains(memberPublicKey)) {
|
if(membersPublicKeys.contains(memberPublicKey)) {
|
||||||
membersPublicKeys.remove(memberPublicKey);
|
membersPublicKeys.remove(memberPublicKey);
|
||||||
@@ -92,6 +121,7 @@ public class GroupRepository extends Repository<Group> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Забанить участника в группе, добавив его публичный ключ в список забаненных публичных ключей группы
|
* Забанить участника в группе, добавив его публичный ключ в список забаненных публичных ключей группы
|
||||||
@@ -99,20 +129,39 @@ public class GroupRepository extends Repository<Group> {
|
|||||||
* @param memberPublicKey публичный ключ участника, которого нужно забанить в группе
|
* @param memberPublicKey публичный ключ участника, которого нужно забанить в группе
|
||||||
*/
|
*/
|
||||||
public void banMemberInGroup(String groupId, String memberPublicKey) {
|
public void banMemberInGroup(String groupId, String memberPublicKey) {
|
||||||
Group group = this.findByField("groupId", groupId);
|
try(QuerySession<Group> querySession = this.buildQuery("FROM Group g WHERE g.groupId = :groupId", new HashMap<String, Object>(){{
|
||||||
|
put("groupId", groupId);
|
||||||
|
}})){
|
||||||
|
Group group = querySession.getQuery().uniqueResult();
|
||||||
if(group != null) {
|
if(group != null) {
|
||||||
|
Hibernate.initialize(group.getBannedPublicKeys());
|
||||||
List<String> bannedPublicKeys = group.getBannedPublicKeys();
|
List<String> bannedPublicKeys = group.getBannedPublicKeys();
|
||||||
List<String> membersPublicKeys = group.getMembersPublicKeys();
|
|
||||||
if(membersPublicKeys.contains(memberPublicKey)) {
|
|
||||||
membersPublicKeys.remove(memberPublicKey);
|
|
||||||
group.setMembersPublicKeys(membersPublicKeys);
|
|
||||||
}
|
|
||||||
if(!bannedPublicKeys.contains(memberPublicKey)) {
|
if(!bannedPublicKeys.contains(memberPublicKey)) {
|
||||||
bannedPublicKeys.add(memberPublicKey);
|
bannedPublicKeys.add(memberPublicKey);
|
||||||
group.setBannedPublicKeys(bannedPublicKeys);
|
group.setBannedPublicKeys(bannedPublicKeys);
|
||||||
}
|
|
||||||
this.update(group);
|
this.update(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает все группы в которых состоит пользователь с заданным публичным ключом
|
||||||
|
* @param memberPublicKey публичный ключ пользователя, для которого нужно найти группы
|
||||||
|
* @return список ID групп, в которых состоит пользователь с заданным публичным ключом, или пустой список, если таких групп нет
|
||||||
|
*/
|
||||||
|
public List<String> findGroupsByMember(String memberPublicKey) {
|
||||||
|
String hql = "FROM Group g WHERE :memberPublicKey MEMBER OF g.membersPublicKeys";
|
||||||
|
List<String> groupIds = new ArrayList<>();
|
||||||
|
HashMap<String, Object> parameters = new HashMap<>();
|
||||||
|
parameters.put("memberPublicKey", memberPublicKey);
|
||||||
|
try(QuerySession<Group> querySession = this.buildQuery(hql, parameters)){
|
||||||
|
List<Group> groups = querySession.getQuery().list();
|
||||||
|
for(Group group : groups){
|
||||||
|
groupIds.add(group.getGroupId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groupIds;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import im.rosetta.client.ClientManager;
|
|||||||
import im.rosetta.client.tags.ECIAuthentificate;
|
import im.rosetta.client.tags.ECIAuthentificate;
|
||||||
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.packet.Packet11Typeing;
|
||||||
import im.rosetta.packet.base.PacketBaseDialog;
|
import im.rosetta.packet.base.PacketBaseDialog;
|
||||||
import im.rosetta.service.services.BufferService;
|
import im.rosetta.service.services.BufferService;
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ import io.orprotocol.packet.PacketManager;
|
|||||||
* Такой диспетчер нужен для того, чтобы не загромождать логику обработчиков сообщений, а так же для того, чтобы
|
* Такой диспетчер нужен для того, чтобы не загромождать логику обработчиков сообщений, а так же для того, чтобы
|
||||||
* централизовать логику отправки сообщений и сохранения их в буфер
|
* централизовать логику отправки сообщений и сохранения их в буфер
|
||||||
* Например, при отправке группового сообщения, диспетчер сам достает участников группы и
|
* Например, при отправке группового сообщения, диспетчер сам достает участников группы и
|
||||||
* отправляет сообщение каждому участнику, а так же сохраняет сообщение в буфер для каждого участника, который офлайн
|
* отправляет сообщение каждому участнику
|
||||||
*/
|
*/
|
||||||
public class MessageDispatcher {
|
public class MessageDispatcher {
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ public class MessageDispatcher {
|
|||||||
* @param packet пакет с групповым сообщением
|
* @param packet пакет с групповым сообщением
|
||||||
*/
|
*/
|
||||||
public void sendGroup(PacketBaseDialog packet, Client client, ECIAuthentificate eciAuthentificate) throws ProtocolException {
|
public void sendGroup(PacketBaseDialog packet, Client client, ECIAuthentificate eciAuthentificate) throws ProtocolException {
|
||||||
|
String fromPublicKey = packet.getFromPublicKey();
|
||||||
String toPublicKey = packet.getToPublicKey();
|
String toPublicKey = packet.getToPublicKey();
|
||||||
List<String> groupMembersPublicKeys = this.groupRepository.findGroupMembers(toPublicKey.replace("#group:", ""));
|
List<String> groupMembersPublicKeys = this.groupRepository.findGroupMembers(toPublicKey.replace("#group:", ""));
|
||||||
if(groupMembersPublicKeys.isEmpty()){
|
if(groupMembersPublicKeys.isEmpty()){
|
||||||
@@ -67,7 +69,18 @@ public class MessageDispatcher {
|
|||||||
}
|
}
|
||||||
this.clientManager.sendPacketToAuthorizedPK(groupMembersPublicKeys, packet);
|
this.clientManager.sendPacketToAuthorizedPK(groupMembersPublicKeys, packet);
|
||||||
|
|
||||||
//TODO: Сохранить сообщение в буфер для группы, чтобы группы тоже синхронизировались
|
if(packet instanceof Packet11Typeing){
|
||||||
|
/**
|
||||||
|
* Если это пакет печати его не обязательно кэшировать, так как он нужен только
|
||||||
|
* для отображения статуса печати в реальном времени
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Кладем пакет в буфер для будущей синхронизации и на случай если кто-то из участников оффлайн,
|
||||||
|
* в toPublicKey при отправке в группу у нас находится #group:groupId
|
||||||
|
*/
|
||||||
|
this.bufferService.pushPacketToBuffer(fromPublicKey, toPublicKey.replace("#group:", ""), packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import im.rosetta.client.tags.ECIAuthentificate;
|
|||||||
import im.rosetta.database.QuerySession;
|
import im.rosetta.database.QuerySession;
|
||||||
import im.rosetta.database.entity.Buffer;
|
import im.rosetta.database.entity.Buffer;
|
||||||
import im.rosetta.database.repository.BufferRepository;
|
import im.rosetta.database.repository.BufferRepository;
|
||||||
|
import im.rosetta.database.repository.GroupRepository;
|
||||||
import im.rosetta.exception.UnauthorizedExeception;
|
import im.rosetta.exception.UnauthorizedExeception;
|
||||||
import im.rosetta.packet.Packet7Read;
|
import im.rosetta.packet.Packet7Read;
|
||||||
import im.rosetta.service.Service;
|
import im.rosetta.service.Service;
|
||||||
@@ -20,6 +21,7 @@ import io.orprotocol.packet.PacketManager;
|
|||||||
public class BufferService extends Service<BufferRepository> {
|
public class BufferService extends Service<BufferRepository> {
|
||||||
|
|
||||||
private PacketManager packetManager;
|
private PacketManager packetManager;
|
||||||
|
private GroupRepository groupRepository = new GroupRepository();
|
||||||
|
|
||||||
public BufferService(BufferRepository repository, PacketManager packetManager) {
|
public BufferService(BufferRepository repository, PacketManager packetManager) {
|
||||||
super(repository);
|
super(repository);
|
||||||
@@ -41,10 +43,24 @@ public class BufferService extends Service<BufferRepository> {
|
|||||||
*/
|
*/
|
||||||
throw new UnauthorizedExeception("Unauthorized client cannot get packets from buffer");
|
throw new UnauthorizedExeception("Unauthorized client cannot get packets from buffer");
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Получаем группы клиента, и исходя из этого формируем список каких пакетов нам нужно взять из базы
|
||||||
|
*/
|
||||||
|
List<String> clientGroups = this.groupRepository.findGroupsByMember(eciAuthentificate.getPublicKey());
|
||||||
|
List<String> toValue = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* Добавляем публичный ключ клиента, так как ему нужны пакеты, которые были отправлены ему напрямую, а не в группу
|
||||||
|
*/
|
||||||
|
toValue.add(eciAuthentificate.getPublicKey());
|
||||||
|
/**
|
||||||
|
* Добавляем группы клиента, так как ему нужны пакеты, которые были отправлены в эти группы
|
||||||
|
*/
|
||||||
|
toValue.addAll(clientGroups);
|
||||||
|
|
||||||
String toPublicKey = eciAuthentificate.getPublicKey();
|
String toPublicKey = eciAuthentificate.getPublicKey();
|
||||||
String hql = "FROM Buffer WHERE (to = :to OR from = :from) AND timestamp > :timestamp ORDER BY timestamp ASC";
|
String hql = "FROM Buffer WHERE (to IN (:to) OR from = :from) AND timestamp > :timestamp ORDER BY timestamp ASC";
|
||||||
HashMap<String, Object> parameters = new HashMap<>();
|
HashMap<String, Object> parameters = new HashMap<>();
|
||||||
parameters.put("to", toPublicKey);
|
parameters.put("to", toValue);
|
||||||
parameters.put("from", toPublicKey);
|
parameters.put("from", toPublicKey);
|
||||||
parameters.put("timestamp", fromTimestampMs);
|
parameters.put("timestamp", fromTimestampMs);
|
||||||
List<Packet> packets = new ArrayList<>();
|
List<Packet> packets = new ArrayList<>();
|
||||||
|
|||||||
Reference in New Issue
Block a user