User-Flow, поиск, редактирование информации о себе
This commit is contained in:
@@ -3,6 +3,8 @@ package com.rosetta.im;
|
||||
import com.rosetta.im.client.ClientManager;
|
||||
import com.rosetta.im.event.EventManager;
|
||||
import com.rosetta.im.executors.Executor0Handshake;
|
||||
import com.rosetta.im.executors.Executor1UserInfo;
|
||||
import com.rosetta.im.executors.Executor3Search;
|
||||
import com.rosetta.im.listeners.HandshakeCompleteListener;
|
||||
import com.rosetta.im.listeners.ServerStopListener;
|
||||
import com.rosetta.im.logger.Logger;
|
||||
@@ -75,10 +77,11 @@ public class Boot {
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрация пакетов, обработчиков, событий приложения
|
||||
* Запуск сервера, регистрация пакетов, обработчиков, событий приложения
|
||||
* @return Boot
|
||||
*/
|
||||
public Boot bootstrap() {
|
||||
this.server.start();
|
||||
this.registerAllPackets();
|
||||
this.registerAllExecutors();
|
||||
this.registerAllEvents();
|
||||
@@ -100,6 +103,8 @@ public class Boot {
|
||||
|
||||
private void registerAllExecutors() {
|
||||
this.packetManager.registerExecutor(0, new Executor0Handshake(this.eventManager));
|
||||
this.packetManager.registerExecutor(1, new Executor1UserInfo());
|
||||
this.packetManager.registerExecutor(3, new Executor3Search(this.clientManager));
|
||||
}
|
||||
|
||||
private void printBootMessage() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.rosetta.im.client.tags;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.rosetta.im.packet.runtime.HandshakeStage;
|
||||
@@ -58,7 +59,15 @@ public class ECIAuthentificate implements ECITag {
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getIndex() {
|
||||
return null;
|
||||
Map<String, Object> indexes = new HashMap<>();
|
||||
if(this.hasAuthorized()){
|
||||
/**
|
||||
* Индексируем пользователя только если он авторизован,
|
||||
* иначе не нужно их индексировать, чтобы не забивать память
|
||||
*/
|
||||
indexes.put("publicKey", publicKey);
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
26
src/main/java/com/rosetta/im/database/QuerySession.java
Normal file
26
src/main/java/com/rosetta/im/database/QuerySession.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.rosetta.im.database;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.query.Query;
|
||||
|
||||
public class QuerySession<T> implements AutoCloseable {
|
||||
|
||||
private Session session;
|
||||
private Query<T> query;
|
||||
|
||||
public QuerySession(Session session, Query<T> query) {
|
||||
this.session = session;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public Query<T> getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (session != null && session.isOpen()) {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.util.List;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.query.Query;
|
||||
|
||||
/**
|
||||
* Базовый репозиторий для работы с сущностями базы данных
|
||||
@@ -92,11 +93,27 @@ public abstract class Repository<T> {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " LIKE :value";
|
||||
return session.createQuery(queryString, entityClass)
|
||||
.setParameter("value", "%" + value + "%")
|
||||
.setParameter("value", value + "%")
|
||||
.list();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск сущности по значению одного поля с использованием оператора LIKE и ограничения LIMIT
|
||||
* @param fieldName поле
|
||||
* @param value значение
|
||||
* @return найденная сущность или null
|
||||
*/
|
||||
public List<T> likeSearchAll(String fieldName, String value, int take) {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " LIKE :value";
|
||||
return session.createQuery(queryString, entityClass)
|
||||
.setParameter("value", value + "%")
|
||||
.setMaxResults(take)
|
||||
.list();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск сущности по набору полей
|
||||
* @param fields карта полей и их значений
|
||||
@@ -212,6 +229,26 @@ public abstract class Repository<T> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет запрос с параметрами и возвращает список сущностей
|
||||
* @param queryString SQL запрос
|
||||
* @param parameters параметры запроса
|
||||
* @return список сущностей
|
||||
*/
|
||||
public QuerySession<T> buildQuery(String queryString, HashMap<String, Object> parameters) {
|
||||
Session session = HibernateUtil.openSession();
|
||||
try {
|
||||
Query<T> query = session.createQuery(queryString, entityClass);
|
||||
for (var entry : parameters.entrySet()) {
|
||||
query.setParameter(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return new QuerySession<>(session, query);
|
||||
} catch (Exception e) {
|
||||
session.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Подсчет сущностей по набору полей
|
||||
* @param fields карта полей и их значений
|
||||
|
||||
180
src/main/java/com/rosetta/im/executors/Executor1UserInfo.java
Normal file
180
src/main/java/com/rosetta/im/executors/Executor1UserInfo.java
Normal file
@@ -0,0 +1,180 @@
|
||||
package com.rosetta.im.executors;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.rosetta.im.Failures;
|
||||
import com.rosetta.im.client.tags.ECIAuthentificate;
|
||||
import com.rosetta.im.database.entity.User;
|
||||
import com.rosetta.im.database.repository.UserRepository;
|
||||
import com.rosetta.im.packet.Packet1UserInfo;
|
||||
import com.rosetta.im.packet.Packet2Result;
|
||||
import com.rosetta.im.packet.runtime.ResultCode;
|
||||
import com.rosetta.im.service.services.UserService;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
import io.orprotocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor1UserInfo extends PacketExecutor<Packet1UserInfo> {
|
||||
|
||||
private final UserRepository userRepository = new UserRepository();
|
||||
private final UserService userService = new UserService(userRepository);
|
||||
private final HashSet<String> blockedUsernames = new HashSet<>(Arrays.asList(
|
||||
"user",
|
||||
"admin",
|
||||
"rosettasupport",
|
||||
"rosettaupdates",
|
||||
"freddie871",
|
||||
"updates",
|
||||
"deleted",
|
||||
"safety",
|
||||
"secure",
|
||||
"rosettasafe"
|
||||
));
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet1UserInfo packet, Client client) throws Exception, ProtocolException {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
String username = packet.getUsername();
|
||||
String title = packet.getTitle();
|
||||
|
||||
if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()){
|
||||
/**
|
||||
* Только для авторизованных пользователей, а этот пользователь - не авторизован
|
||||
*/
|
||||
client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED);
|
||||
return;
|
||||
}
|
||||
|
||||
User user = userService.fromClient(client);
|
||||
if(user == null){
|
||||
/**
|
||||
* Пользователь с таким ключем не найден в базе,
|
||||
* такого не может быть, но лучше чтобы была дополнительная проверка
|
||||
*/
|
||||
client.disconnect(Failures.DATA_MISSMATCH);
|
||||
return;
|
||||
}
|
||||
|
||||
ResultCode usernameResult = tryChangeUsername(user, username);
|
||||
if(usernameResult == ResultCode.USERNAME_TAKEN){
|
||||
/**
|
||||
* Это имя пользователя уже занято, отправляем клиенту ошибку
|
||||
*/
|
||||
Packet2Result result = new Packet2Result();
|
||||
result.setResultCode(ResultCode.USERNAME_TAKEN);
|
||||
client.send(result);
|
||||
return;
|
||||
}
|
||||
if(usernameResult != ResultCode.SUCCESS){
|
||||
/**
|
||||
* Не удалось сменить username, отправляем клиенту ошибку
|
||||
*/
|
||||
Packet2Result result = new Packet2Result();
|
||||
result.setResultCode(ResultCode.INVALID);
|
||||
client.send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
ResultCode titleResult = tryChangeTitle(user, title);
|
||||
if(titleResult != ResultCode.SUCCESS){
|
||||
/**
|
||||
* Не удалось сменить title, отправляем клиенту ошибку
|
||||
*/
|
||||
Packet2Result result = new Packet2Result();
|
||||
result.setResultCode(ResultCode.INVALID);
|
||||
client.send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправляем клиенту успешный результат
|
||||
*/
|
||||
Packet2Result result = new Packet2Result();
|
||||
result.setResultCode(ResultCode.SUCCESS);
|
||||
client.send(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Пробует сменить username
|
||||
* @param user пользователь
|
||||
* @param username имя пользователя для смены
|
||||
* @return вернет false если смена прошла неудачно или true если username
|
||||
* не нуждается в изменении или изменен
|
||||
*/
|
||||
public ResultCode tryChangeUsername(User user, String username){
|
||||
String targetRegexp = "^[a-z][a-z0-9_]{4,15}$";
|
||||
Pattern pattern = Pattern.compile(targetRegexp);
|
||||
Matcher matcher = pattern.matcher(username);
|
||||
|
||||
if(user.getUsername().equalsIgnoreCase(username)){
|
||||
/**
|
||||
* Пользователь не меняет имя, значит операция прошла успешно,
|
||||
* по крайней мере нам не нужно возвращать клиенту код ошибки
|
||||
*/
|
||||
return ResultCode.SUCCESS;
|
||||
}
|
||||
if(!matcher.matches()){
|
||||
/**
|
||||
* Не подходит по регулярному выражению
|
||||
*/
|
||||
return ResultCode.INVALID;
|
||||
}
|
||||
if(blockedUsernames.contains(username)){
|
||||
/**
|
||||
* Это имя пользователя не доступно для смены
|
||||
*/
|
||||
return ResultCode.INVALID;
|
||||
}
|
||||
if(userService.isUsernameTaken(username)){
|
||||
/**
|
||||
* Такое имя пользователя уже занято
|
||||
*/
|
||||
return ResultCode.USERNAME_TAKEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Меняем имя пользователя
|
||||
*/
|
||||
user.setUsername(username);
|
||||
userRepository.update(user);
|
||||
return ResultCode.SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Пробует сменить заголовок пользователя (title)
|
||||
* @param user пользователь
|
||||
* @param username имя пользователя для смены
|
||||
* @return вернет false если смена прошла неудачно или true если title
|
||||
* не нуждается в изменении или изменен
|
||||
*/
|
||||
public ResultCode tryChangeTitle(User user, String title) {
|
||||
String targetRegexp = "^[a-zA-Z0-9а-яА-Я _-]{1,22}$";
|
||||
Pattern pattern = Pattern.compile(targetRegexp);
|
||||
Matcher matcher = pattern.matcher(title);
|
||||
|
||||
if(user.getTitle().equalsIgnoreCase(title)){
|
||||
/**
|
||||
* Пользователь не меняет имя, значит операция прошла успешно,
|
||||
* по крайней мере нам не нужно возвращать клиенту код ошибки
|
||||
*/
|
||||
return ResultCode.SUCCESS;
|
||||
}
|
||||
if(!matcher.matches()){
|
||||
/**
|
||||
* Не подходит по регулярному выражению
|
||||
*/
|
||||
return ResultCode.INVALID;
|
||||
}
|
||||
/**
|
||||
* Меняем имя пользователя
|
||||
*/
|
||||
user.setTitle(title);
|
||||
userRepository.update(user);
|
||||
return ResultCode.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,17 @@
|
||||
package com.rosetta.im.executors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.rosetta.im.Failures;
|
||||
import com.rosetta.im.client.ClientManager;
|
||||
import com.rosetta.im.client.tags.ECIAuthentificate;
|
||||
import com.rosetta.im.database.entity.User;
|
||||
import com.rosetta.im.database.repository.UserRepository;
|
||||
import com.rosetta.im.packet.Packet3Search;
|
||||
import com.rosetta.im.packet.runtime.NetworkStatus;
|
||||
import com.rosetta.im.packet.runtime.SearchInfo;
|
||||
import com.rosetta.im.service.services.UserService;
|
||||
|
||||
import io.orprotocol.ProtocolException;
|
||||
import io.orprotocol.client.Client;
|
||||
@@ -12,6 +20,12 @@ import io.orprotocol.packet.PacketExecutor;
|
||||
public class Executor3Search extends PacketExecutor<Packet3Search> {
|
||||
|
||||
private final UserRepository userRepository = new UserRepository();
|
||||
private final UserService userService = new UserService(userRepository);
|
||||
private final ClientManager clientManager;
|
||||
|
||||
public Executor3Search(ClientManager clientManager) {
|
||||
this.clientManager = clientManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet3Search packet, Client client) throws Exception, ProtocolException {
|
||||
@@ -26,7 +40,33 @@ public class Executor3Search extends PacketExecutor<Packet3Search> {
|
||||
return;
|
||||
}
|
||||
|
||||
if(search.trim().equals("")){
|
||||
/**
|
||||
* Пустой поисковой запрос
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
List<User> usersFindedList = userService.searchUsers(search, 7);
|
||||
Packet3Search response = new Packet3Search();
|
||||
response.setSearch("");
|
||||
|
||||
response.setPrivateKey("");
|
||||
|
||||
List<SearchInfo> searchInfos = new ArrayList<>();
|
||||
for(User user : usersFindedList){
|
||||
SearchInfo searchInfo = new SearchInfo(
|
||||
user.getUsername(),
|
||||
user.getTitle(),
|
||||
user.getPublicKey(),
|
||||
user.getVerified(),
|
||||
NetworkStatus.fromBoolean(this.clientManager.isClientConnected(user.getPublicKey()))
|
||||
);
|
||||
searchInfos.add(searchInfo);
|
||||
}
|
||||
|
||||
response.setSearchInfos(searchInfos);
|
||||
client.send(response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ public class Packet1UserInfo extends Packet {
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.packetId = stream.readInt16();
|
||||
this.username = stream.readString();
|
||||
this.title = stream.readString();
|
||||
this.privateKey = stream.readString();
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
package com.rosetta.im.packet;
|
||||
|
||||
import com.rosetta.im.packet.runtime.ResultCode;
|
||||
|
||||
import io.orprotocol.Stream;
|
||||
import io.orprotocol.packet.Packet;
|
||||
|
||||
public class Packet2Result extends Packet {
|
||||
|
||||
private int resultCode;
|
||||
private ResultCode resultCode;
|
||||
|
||||
@Override
|
||||
public void read(Stream stream) {
|
||||
this.resultCode = stream.readInt8();
|
||||
this.resultCode = ResultCode.fromCode(stream.readInt16());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream write() {
|
||||
Stream stream = new Stream();
|
||||
stream.writeInt16(this.packetId);
|
||||
stream.writeInt8(this.resultCode);
|
||||
stream.writeInt16(this.resultCode.getCode());
|
||||
return stream;
|
||||
}
|
||||
|
||||
@@ -24,7 +26,7 @@ public class Packet2Result extends Packet {
|
||||
* Получает код результата операции
|
||||
* @return код результата
|
||||
*/
|
||||
public int getResultCode() {
|
||||
public ResultCode getResultCode() {
|
||||
return this.resultCode;
|
||||
}
|
||||
|
||||
@@ -32,7 +34,7 @@ public class Packet2Result extends Packet {
|
||||
* Устанавливает код результата операции
|
||||
* @param resultCode код результата
|
||||
*/
|
||||
public void setResultCode(int resultCode) {
|
||||
public void setResultCode(ResultCode resultCode) {
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ public class Packet3Search extends Packet {
|
||||
* @deprecated с версии сервера 1.1 использование приватных ключей
|
||||
* в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей.
|
||||
*/
|
||||
@Deprecated(since = "1.1", forRemoval = true)
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
@@ -78,4 +79,20 @@ public class Packet3Search extends Packet {
|
||||
this.search = search;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает результаты поиска
|
||||
* @return список результатов
|
||||
*/
|
||||
public List<SearchInfo> getSearchInfos() {
|
||||
return this.searchInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает результаты поиска
|
||||
* @param searchInfo
|
||||
*/
|
||||
public void setSearchInfos(List<SearchInfo> searchInfos) {
|
||||
this.searchInfo = searchInfos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.rosetta.im.packet.runtime;
|
||||
|
||||
/**
|
||||
* Статус пользователя в сети
|
||||
*/
|
||||
public enum NetworkStatus {
|
||||
ONLINE(0),
|
||||
OFFILE(1);
|
||||
OFFLINE(1);
|
||||
|
||||
private final int code;
|
||||
|
||||
@@ -22,4 +25,11 @@ public enum NetworkStatus {
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid NetworkStatus code: " + code);
|
||||
}
|
||||
|
||||
public static NetworkStatus fromBoolean(boolean status) {
|
||||
if(status){
|
||||
return NetworkStatus.ONLINE;
|
||||
}
|
||||
return NetworkStatus.OFFLINE;
|
||||
}
|
||||
}
|
||||
|
||||
27
src/main/java/com/rosetta/im/packet/runtime/ResultCode.java
Normal file
27
src/main/java/com/rosetta/im/packet/runtime/ResultCode.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.rosetta.im.packet.runtime;
|
||||
|
||||
public enum ResultCode {
|
||||
SUCCESS(0),
|
||||
ERROR(1),
|
||||
INVALID(2),
|
||||
USERNAME_TAKEN(3);
|
||||
|
||||
private final int code;
|
||||
|
||||
ResultCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static ResultCode fromCode(int code) {
|
||||
for (ResultCode rc : ResultCode.values()) {
|
||||
if (rc.getCode() == code) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid ResultCode code: " + code);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,16 @@ public class SearchInfo implements Serializable {
|
||||
public int verified;
|
||||
public NetworkStatus networkStatus;
|
||||
|
||||
public SearchInfo() {}
|
||||
|
||||
public SearchInfo(String username, String title, String publicKey, int verified, NetworkStatus networkStatus) {
|
||||
this.username = username;
|
||||
this.title = title;
|
||||
this.publicKey = publicKey;
|
||||
this.verified = verified;
|
||||
this.networkStatus = networkStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает имя пользователя.
|
||||
* @return Имя пользователя.
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package com.rosetta.im.service.services;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import com.rosetta.im.client.tags.ECIAuthentificate;
|
||||
import com.rosetta.im.database.QuerySession;
|
||||
import com.rosetta.im.database.entity.User;
|
||||
import com.rosetta.im.database.repository.UserRepository;
|
||||
import com.rosetta.im.service.Service;
|
||||
|
||||
import io.orprotocol.client.Client;
|
||||
|
||||
public class UserService extends Service<UserRepository> {
|
||||
|
||||
public UserService(UserRepository repository) {
|
||||
@@ -13,12 +18,46 @@ public class UserService extends Service<UserRepository> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск пользователей по части имени пользователя.
|
||||
* Поиск пользователей по части имени пользователя и публичному ключу.
|
||||
* @param query часть имени пользователя
|
||||
* @param take сколько пользователей отдать
|
||||
* @return список пользователей, соответствующих запросу
|
||||
*/
|
||||
public List<User> searchUsers(String query) {
|
||||
return getRepository().likeSearchAll("username", query);
|
||||
public List<User> searchUsers(String query, int take) {
|
||||
String hql = "FROM User WHERE username LIKE :query OR publicKey = :queryExact ORDER BY verified ASC";
|
||||
HashMap<String, Object> parameters = new HashMap<>();
|
||||
parameters.put("query", "%" + query + "%");
|
||||
parameters.put("queryExact", query);
|
||||
try(QuerySession<User> querySession = this.getRepository().buildQuery(hql, parameters)){
|
||||
return querySession.getQuery().setMaxResults(take).list();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает User из клиента, так же на всякий случай проверяется авторизован ли пользователь,
|
||||
* если нет то User не будет найден
|
||||
* @param client сетевой клиент
|
||||
* @return пользователь
|
||||
*/
|
||||
public User fromClient(Client client) {
|
||||
ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class);
|
||||
if(eciAuthentificate == null){
|
||||
return null;
|
||||
}
|
||||
if(!eciAuthentificate.hasAuthorized()){
|
||||
return null;
|
||||
}
|
||||
return this.getRepository().findByField("publicKey", eciAuthentificate.getPublicKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет занятость имени пользователя
|
||||
* @param username имя пользователя
|
||||
* @return true если имя занято, иначе false
|
||||
*/
|
||||
public boolean isUsernameTaken(String username) {
|
||||
User user = this.getRepository().findByField("username", username);
|
||||
return user != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ public class Server extends WebSocketServer {
|
||||
|
||||
@Override
|
||||
public void onError(WebSocket arg0, Exception arg1) {
|
||||
arg1.printStackTrace();
|
||||
if(this.listener == null){
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user