diff --git a/src/main/java/com/alterdekim/game/component/LongPoll.java b/src/main/java/com/alterdekim/game/component/LongPoll.java index c0bd214..7a8bbe7 100644 --- a/src/main/java/com/alterdekim/game/component/LongPoll.java +++ b/src/main/java/com/alterdekim/game/component/LongPoll.java @@ -1,23 +1,24 @@ package com.alterdekim.game.component; +import com.alterdekim.game.component.processors.ChatProcessor; +import com.alterdekim.game.component.processors.FriendProcessor; +import com.alterdekim.game.component.processors.Processor; +import com.alterdekim.game.component.processors.RoomProcessor; +import com.alterdekim.game.component.result.LongPollResult; +import com.alterdekim.game.component.result.LongPollResultSingle; +import com.alterdekim.game.component.result.LongPollResultType; import com.alterdekim.game.dto.*; -import com.alterdekim.game.entities.Chat; -import com.alterdekim.game.entities.Room; -import com.alterdekim.game.entities.RoomPlayer; -import com.alterdekim.game.entities.User; import com.alterdekim.game.service.*; import com.alterdekim.game.util.Hash; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.util.ArrayList; -import java.util.Comparator; +import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; @@ -33,6 +34,8 @@ public class LongPoll { private final BlockingQueue longPollingQueue = new ArrayBlockingQueue<>(100); private final ConcurrentHashMap map = new ConcurrentHashMap<>(); + private final List processors = new ArrayList<>(); + @Autowired private UserServiceImpl userService; @@ -48,28 +51,32 @@ public class LongPoll { @Autowired private FriendServiceImpl friendService; + public LongPoll() { + processors.addAll(Arrays.asList(new ChatProcessor(this), new FriendProcessor(this), new RoomProcessor(this))); + } + @Scheduled(fixedRate = 3000) private void longPoll() { getLongPollingQueue().removeIf(e -> e.getDeferredResult().isSetOrExpired()); roomService.clearEmptyRooms(); getMap().keySet().forEach(k -> { - if( (System.currentTimeMillis() - getMap().get(k).getLastRequest()) >= 60000 ) getMap().remove(k); + if( (System.currentTimeMillis() - getMap().get(k).getLastRequest()) >= 55000 ) getMap().remove(k); + }); + roomService.getAll().forEach(r -> { + roomPlayerService.findByRoomId(r.getId()).stream() + .filter(p -> !map.containsKey(p.getUserId())) + .forEach(p -> roomPlayerService.leaveByUserId(p.getUserId())); }); getLongPollingQueue().forEach(longPollingSession -> { try { if( !map.containsKey(longPollingSession.getUserId())) map.put(longPollingSession.getUserId(), new LongPollConfig(0L,new ArrayList<>(), 0, Hash.rnd(), new ArrayList<>(), System.currentTimeMillis(), new ArrayList<>())); LongPollConfig config = map.get(longPollingSession.getUserId()); LongPollResult result = process(longPollingSession.getUserId(), config); - if( !result.getRooms().isEmpty() ) - config.setRooms(result.getRooms().stream().filter(r -> r.getAction() == RoomResultState.ADD_CHANGE).map(r -> new RoomResult(r.getId(), r.getPlayerCount(), r.getPlayers())).collect(Collectors.toList())); - if( !result.getFriends().isEmpty() ) - config.setFriends_online(result.getFriends().stream().filter(r -> r.getAction() == FriendState.ADD).map(r -> new UserResult(r.getId(), r.getUsername())).collect(Collectors.toList())); - config.setSession_pass(config.getSession_pass()+1); - - if( !result.getInvites().isEmpty() || !result.getFriends().isEmpty() || !result.getRooms().isEmpty() || !result.getMessages().isEmpty() || config.getSession_pass() >= iterations) { + if( !result.getResultWithType(LongPollResultType.InviteResult, InviteResult.class).isEmpty() || !result.getResultWithType(LongPollResultType.FriendResult, FriendResult.class).isEmpty() || !result.getResultWithType(LongPollResultType.RoomResult, RoomResultV2.class).isEmpty() || !result.getResultWithType(LongPollResultType.ChatResult, ChatResult.class).isEmpty() || config.getSession_pass() >= iterations) { longPollingSession.getDeferredResult().setResult(result); config.setSession_pass(0); + config.setInvites(new ArrayList<>()); } map.put(longPollingSession.getUserId(), config); } catch (Exception e) { @@ -80,156 +87,10 @@ public class LongPoll { } private LongPollResult process(Long userId, LongPollConfig config) { - Integer onlineCount = map.size(); - List results; - List users = new ArrayList<>(); - List clientRooms = config.getRooms(); - List roomResults = new ArrayList<>(); - List clientFriends = config.getFriends_online(); - List friendsResult = new ArrayList<>(); - - results = chatService.getAfterLastChatId(config.getLast_chat_id()); - - // Chat part - if( !results.isEmpty() ) { - users = results.stream() - .map(Chat::getUserId) - .distinct() - .map(l -> userService.findById(l)) - .map(u -> new UserResult(u.getId(), u.getUsername())) - .collect(Collectors.toList()); - results = results.stream().peek(c -> { - String message = c.getMessage(); - for (int i = 0; i < message.length(); i++) { - if (message.charAt(i) == '@') { - int u = message.substring(i).indexOf(' ') + i; - String username = message.substring(i + 1, u); - User user = userService.findByUsername(username); - if (user != null) { - Long uid = user.getId(); - message = message.substring(0, i) + "" + username + "" + message.substring(u + 1); - } else { - message = message.substring(0, i) + username + message.substring(u + 1); - } - i = 0; - } - } - c.setMessage(message); - }).collect(Collectors.toList()); - } - - // Rooms part - if( !clientRooms.isEmpty() ) { - List rooms = roomService.getAllActive().stream() - .map( r -> new RoomResult(r.getId(), r.getPlayerCount(), roomPlayerService.findByRoomId(r.getId()).stream() - .map(p -> userService.findById(p.getUserId())) - .map(p -> new UserResult(p.getId(), p.getUsername())) - .collect(Collectors.toList()))) - .collect(Collectors.toList()); - if( !isEqualListRoom(rooms, clientRooms) ) { - List rr = new ArrayList<>(rooms); - List resultV2List = new ArrayList<>(); - for( RoomResult r : clientRooms ) { - if( rooms.stream().noneMatch(t -> t.getId().longValue() == r.getId().longValue()) ) { - resultV2List.add(new RoomResultV2(RoomResultState.REMOVE, r.getId(), r.getPlayerCount(), r.getPlayers())); - } else { - RoomResult r1 = rooms.stream().filter( t -> t.getId().longValue() == r.getId().longValue()).findFirst().get(); - if( !isEqual(r, r1) ) { - resultV2List.add(new RoomResultV2(RoomResultState.ADD_CHANGE, r1.getId(), r1.getPlayerCount(), r1.getPlayers())); - } - rr.remove(r1); - } - } - for( RoomResult r : rr ) { - resultV2List.add(new RoomResultV2(RoomResultState.ADD_CHANGE, r.getId(), r.getPlayerCount(), r.getPlayers())); - } - roomResults = resultV2List.stream().sorted(Comparator.comparingLong(RoomResultV2::getId)).collect(Collectors.toList()); - } else { - roomResults = new ArrayList<>(); - } - } else { - List rooms = roomService.getAllActive(); - roomResults = rooms.stream() - .map( r -> new RoomResultV2(RoomResultState.ADD_CHANGE, r.getId(), r.getPlayerCount(), roomPlayerService.findByRoomId(r.getId()).stream() - .map(p -> userService.findById(p.getUserId())) - .map(p -> new UserResult(p.getId(), p.getUsername())) - .collect(Collectors.toList()))) - .collect(Collectors.toList()); - } - - // Friends part - if( !clientFriends.isEmpty() ) { - List userResults = friendService.getFriendsOfUserId(userId) - .stream() - .map(id -> userService.findById(id)) - .map(u -> new UserResult(u.getId(), u.getUsername())) - .filter(f -> getMap().keySet().stream().anyMatch(l -> l.longValue() == f.getId().longValue())) - .collect(Collectors.toList()); - - if( !isEqualListUser(userResults, clientFriends) ) { - List urr = new ArrayList<>(userResults); - List fr = new ArrayList<>(); - for( UserResult r : clientFriends ) { - if( userResults.stream().noneMatch(t -> t.getId().longValue() == r.getId().longValue()) ) { - fr.add(new FriendResult(FriendState.REMOVE, r.getId(), r.getUsername())); - } else { - UserResult r1 = userResults.stream().filter( t -> t.getId().longValue() == r.getId().longValue()).findFirst().get(); - if( !isEqualUser(r, r1) ) { - fr.add(new FriendResult(FriendState.ADD, r1.getId(), r1.getUsername())); - } - urr.remove(r1); - } - } - for( UserResult r : urr ) { - fr.add(new FriendResult(FriendState.ADD, r.getId(), r.getUsername())); - } - friendsResult = fr.stream().sorted(Comparator.comparingLong(FriendResult::getId)).collect(Collectors.toList()); - } else { - friendsResult = new ArrayList<>(); - } - } else { - friendsResult = friendService.getFriendsOfUserId(userId) - .stream() - .map(id -> userService.findById(id)) - .filter(f -> getMap().keySet().stream().anyMatch(l -> l.longValue() == f.getId().longValue())) - .map(f -> new FriendResult(FriendState.ADD, f.getId(), f.getUsername())) - .collect(Collectors.toList()); - } - - return new LongPollResult(onlineCount, results, users, roomResults, friendsResult, config.getInvites().stream().map(i -> new InviteResult(i.getRoomId(), i.getUserId(), i.getUsername())).collect(Collectors.toList())); - } - - private Boolean isEqual(RoomResult r1, RoomResult r2) { - return r1.getId().longValue() == r2.getId().longValue() && - r1.getPlayerCount().intValue() == r2.getPlayerCount().intValue() && - isEqualListUser(r1.getPlayers(), r2.getPlayers()); - } - - private Boolean isEqualUser(UserResult r1, UserResult r2) { - return r1.getId().longValue() == r2.getId().longValue() && - r1.getUsername().equals(r2.getUsername()); - } - - private Boolean isEqualListRoom(List r1, List r2) { - r1 = r1.stream().sorted(Comparator.comparingLong(RoomResult::getId)).collect(Collectors.toList()); - r2 = r2.stream().sorted(Comparator.comparingLong(RoomResult::getId)).collect(Collectors.toList()); - if( r1.size() != r2.size() ) return false; - for( int i = 0; i < r1.size(); i++ ) { - if( !isEqual(r1.get(i), r2.get(i)) ) return false; - } - return true; - } - - private Boolean isEqualListUser(List r1, List r2) { - r1 = r1.stream().sorted(Comparator.comparingLong(UserResult::getId)).collect(Collectors.toList()); - r2 = r2.stream().sorted(Comparator.comparingLong(UserResult::getId)).collect(Collectors.toList()); - if( r1.size() != r2.size() ) return false; - for( int i = 0; i < r1.size(); i++ ) { - if( r1.get(i).getId().longValue() != r2.get(i).getId().longValue() || - !r1.get(i).getUsername().equals(r2.get(i).getUsername()) ) { - return false; - } - } - return true; + List result = new ArrayList<>(); + result.add(new LongPollResultSingle<>(LongPollResultType.OnlineUsers, Arrays.asList(map.size()))); + processors.forEach(p -> result.add(p.process(config, userId))); + result.add(new LongPollResultSingle<>(LongPollResultType.InviteResult, config.getInvites().stream().map(i -> new InviteResult(i.getRoomId(), i.getUserId(), i.getUsername())).collect(Collectors.toList()))); + return new LongPollResult(result); } } diff --git a/src/main/java/com/alterdekim/game/component/LongPollingSession.java b/src/main/java/com/alterdekim/game/component/LongPollingSession.java index c69c8f7..0aa09ee 100644 --- a/src/main/java/com/alterdekim/game/component/LongPollingSession.java +++ b/src/main/java/com/alterdekim/game/component/LongPollingSession.java @@ -1,6 +1,6 @@ package com.alterdekim.game.component; -import com.alterdekim.game.dto.LongPollResult; +import com.alterdekim.game.component.result.LongPollResult; import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.web.context.request.async.DeferredResult; diff --git a/src/main/java/com/alterdekim/game/component/processors/ChatProcessor.java b/src/main/java/com/alterdekim/game/component/processors/ChatProcessor.java new file mode 100644 index 0000000..07a60b6 --- /dev/null +++ b/src/main/java/com/alterdekim/game/component/processors/ChatProcessor.java @@ -0,0 +1,36 @@ +package com.alterdekim.game.component.processors; + +import com.alterdekim.game.component.LongPoll; +import com.alterdekim.game.component.LongPollConfig; +import com.alterdekim.game.component.result.LongPollResultSingle; +import com.alterdekim.game.component.result.LongPollResultType; +import com.alterdekim.game.dto.ChatResult; +import com.alterdekim.game.dto.UserResult; +import com.alterdekim.game.entities.Chat; +import com.alterdekim.game.service.ChatServiceImpl; +import com.alterdekim.game.service.UserServiceImpl; +import com.alterdekim.game.util.StringUtil; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class ChatProcessor extends Processor { + + public ChatProcessor(LongPoll longPoll) { + super(LongPollResultType.ChatResult, longPoll); + } + + @Override + public LongPollResultSingle process(LongPollConfig config, Long userId) { + List results = getParent().getChatService().getAfterLastChatId(config.getLast_chat_id()); + + if( results.isEmpty() ) return new LongPollResultSingle<>(getType(), new ArrayList<>()); + + return new LongPollResultSingle<>(getType(), results.stream() + .peek(c -> c.setMessage(StringUtil.escapeTags(getParent().getUserService(), c.getMessage()))) + .map(c -> new ChatResult(c, new UserResult( c.getUserId(), getParent().getUserService().findById(c.getUserId()).getUsername()))) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/com/alterdekim/game/component/processors/FriendProcessor.java b/src/main/java/com/alterdekim/game/component/processors/FriendProcessor.java new file mode 100644 index 0000000..ab16e88 --- /dev/null +++ b/src/main/java/com/alterdekim/game/component/processors/FriendProcessor.java @@ -0,0 +1,68 @@ +package com.alterdekim.game.component.processors; + +import com.alterdekim.game.component.LongPoll; +import com.alterdekim.game.component.LongPollConfig; +import com.alterdekim.game.component.result.LongPollResultSingle; +import com.alterdekim.game.component.result.LongPollResultType; +import com.alterdekim.game.dto.FriendResult; +import com.alterdekim.game.dto.FriendState; +import com.alterdekim.game.dto.UserResult; +import com.alterdekim.game.service.FriendServiceImpl; +import com.alterdekim.game.service.UserServiceImpl; +import com.alterdekim.game.util.ListUtil; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class FriendProcessor extends Processor { + + public FriendProcessor(LongPoll parent) { + super(LongPollResultType.FriendResult, parent); + } + + @Override + public LongPollResultSingle process(LongPollConfig config, Long userId) { + List clientFriends = config.getFriends_online(); + + if( !clientFriends.isEmpty() ) return new LongPollResultSingle<>(getType(), rearrangeFriends(clientFriends, userId)); + + return new LongPollResultSingle<>(getType(), friends2Users(getParent().getFriendService().getFriendsOfUserId(userId)) + .stream() + .map(f -> new FriendResult(FriendState.ADD, f.getId(), f.getUsername())) + .collect(Collectors.toList())); + } + + private List rearrangeFriends(List clientFriends, Long userId) { + List userResults = friends2Users(getParent().getFriendService().getFriendsOfUserId(userId)); + + if(ListUtil.isEqualListUser(userResults, clientFriends) ) return new ArrayList<>(); + + List urr = new ArrayList<>(userResults); + List fr = new ArrayList<>(); + for( UserResult r : clientFriends ) { + if( userResults.stream().noneMatch(t -> t.getId().longValue() == r.getId().longValue()) ) { + fr.add(new FriendResult(FriendState.REMOVE, r.getId(), r.getUsername())); + continue; + } + UserResult r1 = userResults.stream().filter( t -> t.getId().longValue() == r.getId().longValue()).findFirst().get(); + if( !r.equals(r1) ) { + fr.add(new FriendResult(FriendState.ADD, r1.getId(), r1.getUsername())); + } + urr.remove(r1); + } + urr.forEach(r -> fr.add(new FriendResult(FriendState.ADD, r.getId(), r.getUsername()))); + return fr.stream().sorted(Comparator.comparingLong(FriendResult::getId)).collect(Collectors.toList()); + } + + private List friends2Users(List friendIds) { + return friendIds.stream() + .map(id -> getParent().getUserService().findById(id)) + .map(u -> new UserResult(u.getId(), u.getUsername())) + .filter(f -> getParent().getMap().keySet().stream().anyMatch(l -> l.longValue() == f.getId().longValue())) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/alterdekim/game/component/processors/Processor.java b/src/main/java/com/alterdekim/game/component/processors/Processor.java new file mode 100644 index 0000000..85b9289 --- /dev/null +++ b/src/main/java/com/alterdekim/game/component/processors/Processor.java @@ -0,0 +1,19 @@ +package com.alterdekim.game.component.processors; + +import com.alterdekim.game.component.LongPoll; +import com.alterdekim.game.component.LongPollConfig; +import com.alterdekim.game.component.result.LongPollResultSingle; +import com.alterdekim.game.component.result.LongPollResultType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +@Getter +@AllArgsConstructor +public abstract class Processor { + private LongPollResultType type; + private LongPoll parent; + + public abstract LongPollResultSingle process(LongPollConfig config, Long userId); +} diff --git a/src/main/java/com/alterdekim/game/component/processors/RoomProcessor.java b/src/main/java/com/alterdekim/game/component/processors/RoomProcessor.java new file mode 100644 index 0000000..ce85e6b --- /dev/null +++ b/src/main/java/com/alterdekim/game/component/processors/RoomProcessor.java @@ -0,0 +1,78 @@ +package com.alterdekim.game.component.processors; + +import com.alterdekim.game.component.LongPoll; +import com.alterdekim.game.component.LongPollConfig; +import com.alterdekim.game.component.result.LongPollResultSingle; +import com.alterdekim.game.component.result.LongPollResultType; +import com.alterdekim.game.dto.RoomResult; +import com.alterdekim.game.dto.RoomResultState; +import com.alterdekim.game.dto.RoomResultV2; +import com.alterdekim.game.dto.UserResult; +import com.alterdekim.game.entities.Room; +import com.alterdekim.game.service.RoomPlayerService; +import com.alterdekim.game.service.RoomPlayerServiceImpl; +import com.alterdekim.game.service.RoomServiceImpl; +import com.alterdekim.game.service.UserServiceImpl; +import com.alterdekim.game.util.ListUtil; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class RoomProcessor extends Processor { + + public RoomProcessor(LongPoll longPoll) { + super(LongPollResultType.RoomResult, longPoll); + } + + @Override + public LongPollResultSingle process(LongPollConfig config, Long userId) { + List clientRooms = config.getRooms(); + + if( clientRooms.isEmpty() ) return new LongPollResultSingle<>(getType(), roomToRoomResult2AddChange(getParent().getRoomService().getAllActive())); + + List rooms = roomToRoomResult(getParent().getRoomService().getAllActive()); + + if( ListUtil.isEqualListRoom(rooms, clientRooms) ) return new LongPollResultSingle<>(getType(), new ArrayList<>()); + + List rr = new ArrayList<>(rooms); + List resultV2List = new ArrayList<>(); + + for( RoomResult r : clientRooms ) { + if( rooms.stream().noneMatch(t -> t.getId().longValue() == r.getId().longValue()) ) { + resultV2List.add(new RoomResultV2(RoomResultState.REMOVE, r.getId(), r.getPlayerCount(), r.getPlayers())); + continue; + } + + RoomResult r1 = rooms.stream().filter( t -> t.getId().longValue() == r.getId().longValue()).findFirst().get(); + if( !r.equals(r1) ) { + resultV2List.add(new RoomResultV2(RoomResultState.ADD_CHANGE, r1.getId(), r1.getPlayerCount(), r1.getPlayers())); + } + rr.remove(r1); + } + + rr.forEach(r -> resultV2List.add(new RoomResultV2(RoomResultState.ADD_CHANGE, r.getId(), r.getPlayerCount(), r.getPlayers()))); + + return new LongPollResultSingle<>(getType(), resultV2List.stream().sorted(Comparator.comparingLong(RoomResultV2::getId)).collect(Collectors.toList())); + } + + private List roomToRoomResult(List rooms) { + return rooms.stream() + .map( r -> new RoomResult(r.getId(), r.getPlayerCount(), getParent().getRoomPlayerService().findByRoomId(r.getId()).stream() + .map(p -> getParent().getUserService().findById(p.getUserId())) + .map(p -> new UserResult(p.getId(), p.getUsername())) + .collect(Collectors.toList()))) + .collect(Collectors.toList()); + } + + private List roomToRoomResult2AddChange(List rooms) { + return rooms.stream() + .map( r -> new RoomResultV2(RoomResultState.ADD_CHANGE, r.getId(), r.getPlayerCount(), getParent().getRoomPlayerService().findByRoomId(r.getId()).stream() + .map(p -> getParent().getUserService().findById(p.getUserId())) + .map(p -> new UserResult(p.getId(), p.getUsername())) + .collect(Collectors.toList()))) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/alterdekim/game/component/result/LongPollResult.java b/src/main/java/com/alterdekim/game/component/result/LongPollResult.java new file mode 100644 index 0000000..e81a02e --- /dev/null +++ b/src/main/java/com/alterdekim/game/component/result/LongPollResult.java @@ -0,0 +1,18 @@ +package com.alterdekim.game.component.result; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class LongPollResult { + private List result; + + public List getResultWithType(LongPollResultType type, T elementClass) { + return (List) result.stream().filter(r -> r.getType() == type).findFirst().get().getArray(); + } +} diff --git a/src/main/java/com/alterdekim/game/component/result/LongPollResultSingle.java b/src/main/java/com/alterdekim/game/component/result/LongPollResultSingle.java new file mode 100644 index 0000000..5dd314c --- /dev/null +++ b/src/main/java/com/alterdekim/game/component/result/LongPollResultSingle.java @@ -0,0 +1,13 @@ +package com.alterdekim.game.component.result; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +@AllArgsConstructor +@Getter +public class LongPollResultSingle { + private LongPollResultType type; + private List array; +} diff --git a/src/main/java/com/alterdekim/game/component/result/LongPollResultType.java b/src/main/java/com/alterdekim/game/component/result/LongPollResultType.java new file mode 100644 index 0000000..7efbbf0 --- /dev/null +++ b/src/main/java/com/alterdekim/game/component/result/LongPollResultType.java @@ -0,0 +1,9 @@ +package com.alterdekim.game.component.result; + +public enum LongPollResultType { + OnlineUsers, + ChatResult, + RoomResult, + FriendResult, + InviteResult +} diff --git a/src/main/java/com/alterdekim/game/controller/APIController.java b/src/main/java/com/alterdekim/game/controller/APIController.java index db86900..7cd4892 100644 --- a/src/main/java/com/alterdekim/game/controller/APIController.java +++ b/src/main/java/com/alterdekim/game/controller/APIController.java @@ -4,12 +4,16 @@ package com.alterdekim.game.controller; import com.alterdekim.game.component.LongPoll; import com.alterdekim.game.component.LongPollConfig; import com.alterdekim.game.component.LongPollingSession; +import com.alterdekim.game.component.result.LongPollResult; import com.alterdekim.game.dto.*; import com.alterdekim.game.entities.Chat; import com.alterdekim.game.entities.Room; import com.alterdekim.game.entities.User; import com.alterdekim.game.service.*; import com.alterdekim.game.util.Hash; +import com.alterdekim.game.util.StringUtil; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -55,34 +59,12 @@ public class APIController { private FriendServiceImpl friendService; @GetMapping("/api/v1/chat/history/{count}/") - public ResponseEntity chatList(@PathVariable Integer count ) { + public ResponseEntity> chatList(@PathVariable Integer count ) { List results = chatService.getLastChats(count); - results = results.stream().map( c -> { - String message = c.getMessage(); - for( int i = 0; i < message.length(); i++ ) { - if( message.charAt(i) == '@' ) { - int u = message.substring(i).indexOf(' ') + i; - String username = message.substring(i+1, u); - User user = userService.findByUsername(username); - if( user != null ) { - Long uid = user.getId(); - message = message.substring(0, i) + "" + username + "" + message.substring(u + 1); - } else { - message = message.substring(0, i) + username + message.substring(u + 1); - } - i = 0; - } - } - c.setMessage(message); - return c; - }).collect(Collectors.toList()); - List users = results.stream() - .map(Chat::getUserId) - .distinct() - .map( l -> userService.findById(l) ) - .map( u -> new UserResult(u.getId(), u.getUsername()) ) - .collect(Collectors.toList()); - return ResponseEntity.ok(new ChatResult(results, users)); + return ResponseEntity.ok(results.stream() + .peek(c -> c.setMessage(StringUtil.escapeTags(userService, c.getMessage()))) + .map(c -> new ChatResult(c, new UserResult( c.getUserId(), userService.findById(c.getUserId()).getUsername()))) + .collect(Collectors.toList())); } @GetMapping("/api/v1/market/banners/{lang}/") @@ -126,7 +108,7 @@ public class APIController { @PostMapping("/api/v1/rooms/create/") public ResponseEntity createRoom( @RequestParam("is_private") Boolean is_private, - @RequestParam("players_count") Integer players_count) { + @RequestParam("players_count") Integer players_count ) { if( !(players_count >= 2 && players_count <= 5) ) return ResponseEntity.badRequest().build(); Long id = roomService.createRoom(new Room(players_count, is_private, "0")); Authentication auth = SecurityContextHolder.getContext().getAuthentication(); @@ -136,6 +118,16 @@ public class APIController { return ResponseEntity.accepted().build(); } + @PostMapping("/api/v1/rooms/join/") + public ResponseEntity joinRoom( @RequestParam("room_id") Long roomId ) { + if( !roomService.findById(roomId).isPresent() ) return ResponseEntity.badRequest().build(); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + Long userId = userService.findByUsername(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername()).getId(); + roomPlayerService.leaveByUserId(userId); + roomPlayerService.joinRoom(roomId, userId); + return ResponseEntity.accepted().build(); + } + @PostMapping("/api/v1/rooms/invite/") public ResponseEntity inviteToRoom( @RequestParam("friend_id") Long friend_id ) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); @@ -160,7 +152,9 @@ public class APIController { public DeferredResult getNotify(@RequestParam("last_chat_id") Long last_chat_id, @RequestParam("accessToken") String accessToken, @RequestParam("uid") Long userId, - @RequestParam("poll_token") String poll_token) { + @RequestParam("poll_token") String poll_token, + @RequestParam("rids") String rooms_str, + @RequestParam("fids") String friends_str) { try { User u = userService.findById(userId); if (u == null) return null; @@ -171,18 +165,28 @@ public class APIController { } if( poll_token.length() != 32 ) return null; final DeferredResult deferredResult = new DeferredResult<>(); + List rooms = new ArrayList<>(); + List friends = new ArrayList<>(); + try { + rooms = new ObjectMapper().readValue(rooms_str, new TypeReference>() {}); + friends = new ObjectMapper().readValue(friends_str, new TypeReference>() {}); + } catch (Exception e) { + log.error(e.getMessage(), e); + } if( longPoll.getMap().containsKey(userId) ){ LongPollConfig c = longPoll.getMap().get(userId); if( !c.getPoll_token().equals(poll_token) ) { - c = new LongPollConfig(last_chat_id, new ArrayList<>(), 0, poll_token, new ArrayList<>(), System.currentTimeMillis(), new ArrayList<>()); + c = new LongPollConfig(last_chat_id, rooms, 0, poll_token, friends, System.currentTimeMillis(), new ArrayList<>()); longPoll.getLongPollingQueue().removeIf(q -> q.getUserId().longValue() == userId.longValue()); } + c.setRooms(rooms); + c.setFriends_online(friends); c.setLast_chat_id(last_chat_id); c.setSession_pass(0); c.setLastRequest(System.currentTimeMillis()); longPoll.getMap().put(userId, c); } else { - longPoll.getMap().put(userId, new LongPollConfig(last_chat_id, new ArrayList<>(), 0, poll_token, new ArrayList<>(), System.currentTimeMillis(), new ArrayList<>())); + longPoll.getMap().put(userId, new LongPollConfig(last_chat_id, rooms, 0, poll_token, friends, System.currentTimeMillis(), new ArrayList<>())); } longPoll.getLongPollingQueue().add(new LongPollingSession(userId, deferredResult)); return deferredResult; diff --git a/src/main/java/com/alterdekim/game/controller/ImageController.java b/src/main/java/com/alterdekim/game/controller/ImageController.java new file mode 100644 index 0000000..9cc1627 --- /dev/null +++ b/src/main/java/com/alterdekim/game/controller/ImageController.java @@ -0,0 +1,36 @@ +package com.alterdekim.game.controller; + +import com.alterdekim.game.entities.Image; +import com.alterdekim.game.repository.ImageRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; + +@RestController +class ImageController { + + @Autowired + ImageRepository imageRepository; + + @PostMapping(value = "/image/upload/") + private Long uploadImage(@RequestParam MultipartFile multipartImage) throws Exception { + Image dbImage = new Image(); + dbImage.setContent(multipartImage.getBytes()); + return imageRepository.save(dbImage) + .getId(); + } + + @GetMapping(value = "/image/store/{imageId}", produces = MediaType.IMAGE_JPEG_VALUE) + Resource downloadImage(@PathVariable Long imageId) { + byte[] image = imageRepository.findById(imageId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)) + .getContent(); + + return new ByteArrayResource(image); + } +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/game/dto/ChatResult.java b/src/main/java/com/alterdekim/game/dto/ChatResult.java index 7ea5619..2908b9c 100644 --- a/src/main/java/com/alterdekim/game/dto/ChatResult.java +++ b/src/main/java/com/alterdekim/game/dto/ChatResult.java @@ -11,6 +11,6 @@ import java.util.List; @NoArgsConstructor @Getter public class ChatResult { - private List messages; - private List users; + private Chat message; + private UserResult user; } diff --git a/src/main/java/com/alterdekim/game/dto/LongPollResult.java b/src/main/java/com/alterdekim/game/dto/LongPollResult.java deleted file mode 100644 index 24e23b1..0000000 --- a/src/main/java/com/alterdekim/game/dto/LongPollResult.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.alterdekim.game.dto; - -import com.alterdekim.game.entities.Chat; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.List; - -@AllArgsConstructor -@NoArgsConstructor -@Getter -public class LongPollResult { - private Integer onlineCount; - private List messages; - private List users; - private List rooms; - private List friends; - private List invites; -} diff --git a/src/main/java/com/alterdekim/game/dto/RoomResult.java b/src/main/java/com/alterdekim/game/dto/RoomResult.java index 64d0040..e427ab1 100644 --- a/src/main/java/com/alterdekim/game/dto/RoomResult.java +++ b/src/main/java/com/alterdekim/game/dto/RoomResult.java @@ -3,6 +3,7 @@ package com.alterdekim.game.dto; import com.alterdekim.game.entities.Room; import com.alterdekim.game.entities.RoomPlayer; import com.alterdekim.game.entities.User; +import com.alterdekim.game.util.ListUtil; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -16,4 +17,10 @@ public class RoomResult { private Long id; private Integer playerCount; private List players; + + public boolean equals(RoomResult r2) { + return this.getId().longValue() == r2.getId().longValue() && + this.getPlayerCount().intValue() == r2.getPlayerCount().intValue() && + ListUtil.isEqualListUser(this.getPlayers(), r2.getPlayers()); + } } diff --git a/src/main/java/com/alterdekim/game/dto/UserResult.java b/src/main/java/com/alterdekim/game/dto/UserResult.java index e938b71..af2534a 100644 --- a/src/main/java/com/alterdekim/game/dto/UserResult.java +++ b/src/main/java/com/alterdekim/game/dto/UserResult.java @@ -10,4 +10,9 @@ import lombok.NoArgsConstructor; public class UserResult { private Long id; private String username; + + public boolean equals(UserResult r2) { + return this.getId().longValue() == r2.getId().longValue() && + this.getUsername().equals(r2.getUsername()); + } } diff --git a/src/main/java/com/alterdekim/game/entities/Image.java b/src/main/java/com/alterdekim/game/entities/Image.java new file mode 100644 index 0000000..e812ccd --- /dev/null +++ b/src/main/java/com/alterdekim/game/entities/Image.java @@ -0,0 +1,25 @@ +package com.alterdekim.game.entities; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Entity +public class Image { + + @Id + @GeneratedValue + Long id; + + @Lob + byte[] content; +} diff --git a/src/main/java/com/alterdekim/game/entities/User.java b/src/main/java/com/alterdekim/game/entities/User.java index 9d7df9d..68599d6 100644 --- a/src/main/java/com/alterdekim/game/entities/User.java +++ b/src/main/java/com/alterdekim/game/entities/User.java @@ -28,6 +28,9 @@ public class User { @Column(nullable=false) private String password; + @Column(nullable = false) + private Long avatarId; + @ManyToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL) @JoinTable( name="users_roles", diff --git a/src/main/java/com/alterdekim/game/repository/ImageRepository.java b/src/main/java/com/alterdekim/game/repository/ImageRepository.java new file mode 100644 index 0000000..53259ed --- /dev/null +++ b/src/main/java/com/alterdekim/game/repository/ImageRepository.java @@ -0,0 +1,8 @@ +package com.alterdekim.game.repository; + +import com.alterdekim.game.entities.Image; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ImageRepository extends JpaRepository {} diff --git a/src/main/java/com/alterdekim/game/security/SpringSecurity.java b/src/main/java/com/alterdekim/game/security/SpringSecurity.java index 11c35cb..629fc4a 100644 --- a/src/main/java/com/alterdekim/game/security/SpringSecurity.java +++ b/src/main/java/com/alterdekim/game/security/SpringSecurity.java @@ -34,6 +34,7 @@ public class SpringSecurity { .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/async/**").permitAll() + .requestMatchers("/image/**").hasAnyAuthority("ROLE_ADMIN") .requestMatchers("/game").hasAnyAuthority("ROLE_ADMIN") .requestMatchers("/games").hasAnyAuthority("ROLE_ADMIN") .requestMatchers("/profile/**").hasAnyAuthority("ROLE_ADMIN") diff --git a/src/main/java/com/alterdekim/game/util/ListUtil.java b/src/main/java/com/alterdekim/game/util/ListUtil.java new file mode 100644 index 0000000..7310a13 --- /dev/null +++ b/src/main/java/com/alterdekim/game/util/ListUtil.java @@ -0,0 +1,33 @@ +package com.alterdekim.game.util; + +import com.alterdekim.game.dto.RoomResult; +import com.alterdekim.game.dto.UserResult; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class ListUtil { + public static Boolean isEqualListRoom(List r1, List r2) { + r1 = r1.stream().sorted(Comparator.comparingLong(RoomResult::getId)).collect(Collectors.toList()); + r2 = r2.stream().sorted(Comparator.comparingLong(RoomResult::getId)).collect(Collectors.toList()); + if( r1.size() != r2.size() ) return false; + for( int i = 0; i < r1.size(); i++ ) { + if( !r1.get(i).equals(r2.get(i)) ) return false; + } + return true; + } + + public static Boolean isEqualListUser(List r1, List r2) { + r1 = r1.stream().sorted(Comparator.comparingLong(UserResult::getId)).collect(Collectors.toList()); + r2 = r2.stream().sorted(Comparator.comparingLong(UserResult::getId)).collect(Collectors.toList()); + if( r1.size() != r2.size() ) return false; + for( int i = 0; i < r1.size(); i++ ) { + if( r1.get(i).getId().longValue() != r2.get(i).getId().longValue() || + !r1.get(i).getUsername().equals(r2.get(i).getUsername()) ) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/com/alterdekim/game/util/StringUtil.java b/src/main/java/com/alterdekim/game/util/StringUtil.java new file mode 100644 index 0000000..e019fda --- /dev/null +++ b/src/main/java/com/alterdekim/game/util/StringUtil.java @@ -0,0 +1,24 @@ +package com.alterdekim.game.util; + +import com.alterdekim.game.entities.User; +import com.alterdekim.game.service.UserServiceImpl; + +public class StringUtil { + public static String escapeTags(UserServiceImpl userService, String message) { + for (int i = 0; i < message.length(); i++) { + if (message.charAt(i) == '@') { + int u = message.substring(i).indexOf(' ') + i; + String username = message.substring(i + 1, u); + User user = userService.findByUsername(username); + if (user != null) { + Long uid = user.getId(); + message = message.substring(0, i) + "" + username + "" + message.substring(u + 1); + } else { + message = message.substring(0, i) + username + message.substring(u + 1); + } + i = 0; + } + } + return message; + } +} diff --git a/src/main/resources/static/javascript/games.js b/src/main/resources/static/javascript/games.js index e087c19..1daa4d0 100644 --- a/src/main/resources/static/javascript/games.js +++ b/src/main/resources/static/javascript/games.js @@ -1,5 +1,9 @@ var isPollingActive = true; +var last_chat_id = 0; +var rids = []; +var fids = []; + function createRoom() { let isprivate = $("#flexSwitchCheckDefault").is(':checked'); let playerscnt = $("#players-count-range").val(); @@ -21,27 +25,74 @@ function sendInviteMessage(uid) { method: "POST", data: { friend_id: uid + }, + statusCode: { + 400: function() { + $(".toast-container").append(''); + bootstrap.Toast.getOrCreateInstance($(".toast").last()).show(); + }, + 200: function() { + $(".toast-container").append(''); + bootstrap.Toast.getOrCreateInstance($(".toast").last()).show(); + } } - }).done(function(data) { - console.log(data); }); } +function joinRoom(obj) { + let roomId = $(obj).attr("data-room-id"); + $.ajax({ + url: "/api/v1/rooms/join/", + data: { + room_id: roomId + }, + method: "POST" + }); +} + +function takeInviteMessage(obj) { + // attr data-roomId +} + function successPolling(data) { console.log(data); - let onlineCount = data.onlineCount; + data = data.result; + + let onlineCount = 0; + let messages = []; + let friends = []; + let rooms = []; + let invites = []; + + for(let i = 0; i < data.length; i++ ) { + let res = data[i]; + switch(res.type) { + case "OnlineUsers": + onlineCount = res.array[0]; + break; + case "ChatResult": + messages = res.array; + break; + case "FriendResult": + friends = res.array; + break; + case "RoomResult": + rooms = res.array; + break; + case "InviteResult": + invites = res.array; + break; + } + } + $(".chat-title").find("span").html(onlineCount + " online"); - let messages = data.messages; - let users = data.users; - let rooms = data.rooms; - let friends = data.friends; if( messages.length > 0 ) { last_chat_id = messages[0].id; } for( let i = 0; i < messages.length; i++ ) { - let obj = messages[i]; + let obj = messages[i].message; let time = parseTime(obj.createdAt); - let username = findUser(users, obj.userId).username; + let username = messages[i].user.username; let userid = obj.userId; let msgtext = obj.message; let html = '
'+time+'
'+username+''+msgtext+'
'; @@ -50,13 +101,20 @@ function successPolling(data) { for( let i = 0; i < rooms.length; i++ ) { let room = rooms[i]; if( room.action == 'ADD_CHANGE' ) { + for( let u = 0; u < rids.length; u++ ) { + if( rids[u].id == room.id ) { + rids.splice(u, 1); + break; + } + } + rids.push({id: room.id, playerCount: room.playerCount, players: room.players}); let room_p_html = ''; for( let u = 0; u < room.players.length; u++ ) { let room_player = room.players[u]; room_p_html += ''; } for( let u = 0; u < (room.playerCount - room.players.length); u++ ) { - room_p_html += '
Join
'; + room_p_html += '
Join
'; } let room_html = '
Game
'+room_p_html+'
'; let has_element = false; @@ -70,6 +128,12 @@ function successPolling(data) { $(".rooms-list").append(room_html); } } else if( room.action == 'REMOVE' ) { + for( let u = 0; u < rids.length; u++ ) { + if( rids[u].id == room.id ) { + rids.splice(u, 1); + break; + } + } $(".games-room-one").each(function() { if( $(this).attr("data-room-id") == room.id ) { $(this).remove(); @@ -80,9 +144,22 @@ function successPolling(data) { for( let i = 0; i < friends.length; i++ ) { let friend = friends[i]; if( friend.action == 'ADD' ) { + for( let u = 0; u < fids.length; u++ ) { + if( fids[u].id == friend.id ) { + fids.splice(u, 1); + break; + } + } + fids.push({id: friend.id, username: friend.username}); let fr_html = '
'+friend.username+'
'; $(".friends-online-list").append(fr_html); } else if( friend.action == 'REMOVE' ) { + for( let u = 0; u < fids.length; u++ ) { + if( fids[u].id == friend.id ) { + fids.splice(u, 1); + break; + } + } $(".friend-one").each(function() { if( $(this).attr("data-friend-id") == friend.id ) { $(this).remove(); @@ -91,6 +168,12 @@ function successPolling(data) { } } + for( let i = 0; i < invites.length; i++ ) { + let _i = invites[i]; + $(".toast-container").append(''); + bootstrap.Toast.getOrCreateInstance($(".toast").last()).show(); + } + if( friends.length > 0 ) { $(".friends-online-list").css("display", ""); $("#friends_list").find(".block-content").css("display", "none"); @@ -118,6 +201,8 @@ function pollServer() { last_chat_id: last_chat_id, accessToken: accessToken, poll_token: poll_token, + rids: JSON.stringify(rids), + fids: JSON.stringify(fids), uid: uid }, method: "POST" @@ -141,16 +226,6 @@ $.fn.pressEnter = function(fn) { }); }; - var last_chat_id = 0; - -function findUser(users, id) { - for( let i = 0; i < users.length; i++ ) { - if( users[i].id == id ) { - return users[i]; - } - } -} - function parseTime(unix_timestamp) { var date = new Date(unix_timestamp * 1000); var hours = "0" + date.getHours(); @@ -188,15 +263,14 @@ $(document).ready(function() { method: "GET" }).done(function(data) { console.log(data); - let messages = data.messages; - let users = data.users; + let messages = data; if( messages.length > 0 ) { - last_chat_id = messages[0].id; + last_chat_id = messages[0].message.id; } for( let i = messages.length-1; i >= 0; i-- ) { - let obj = messages[i]; + let obj = messages[i].message; let time = parseTime(obj.createdAt); - let username = findUser(users, obj.userId).username; + let username = messages[i].user.username; let userid = obj.userId; let msgtext = obj.message; let html = '
'+time+'
'+username+''+msgtext+'
'; diff --git a/src/main/resources/templates/games.html b/src/main/resources/templates/games.html index 17d65ba..d2392ac 100644 --- a/src/main/resources/templates/games.html +++ b/src/main/resources/templates/games.html @@ -37,6 +37,7 @@
+