diff --git a/pom.xml b/pom.xml index cbfbd17..5711a5f 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,11 @@ org.springframework.boot spring-boot-starter-security + + org.apache.commons + commons-collections4 + 4.4 + diff --git a/src/main/java/com/alterdekim/game/component/LongPoll.java b/src/main/java/com/alterdekim/game/component/LongPoll.java index 60028ce..acabd37 100644 --- a/src/main/java/com/alterdekim/game/component/LongPoll.java +++ b/src/main/java/com/alterdekim/game/component/LongPoll.java @@ -1,30 +1,233 @@ package com.alterdekim.game.component; -import com.alterdekim.game.dto.LongPollResult; +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.List; +import java.util.Objects; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; @Component @Getter @Slf4j public class LongPoll { - private final BlockingQueue longPollingQueue = new ArrayBlockingQueue<>(100); + private final Integer iterations = 6; - @Scheduled(fixedRate = 5000) + private final BlockingQueue longPollingQueue = new ArrayBlockingQueue<>(100); + private final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + @Autowired + private UserServiceImpl userService; + + @Autowired + private ChatServiceImpl chatService; + + @Autowired + private RoomServiceImpl roomService; + + @Autowired + private RoomPlayerServiceImpl roomPlayerService; + + @Autowired + private FriendServiceImpl friendService; + + @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); + }); getLongPollingQueue().forEach(longPollingSession -> { try { - longPollingSession.getDeferredResult().setResult(new LongPollResult()); + if( !map.containsKey(longPollingSession.getUserId())) map.put(longPollingSession.getUserId(), new LongPollConfig(0L,new ArrayList<>(), 0, Hash.rnd(), new ArrayList<>(), System.currentTimeMillis())); + 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.getFriends().isEmpty() || !result.getRooms().isEmpty() || !result.getMessages().isEmpty() || config.getSession_pass() >= iterations) { + longPollingSession.getDeferredResult().setResult(result); + config.setSession_pass(0); + } + map.put(longPollingSession.getUserId(), config); } catch (Exception e) { log.error(e.getMessage(), e); } }); getLongPollingQueue().removeIf(e -> e.getDeferredResult().isSetOrExpired()); } + + private LongPollResult process(Long userId, LongPollConfig config) { + userService.updateOnline(userId); + + Integer onlineCount = userService.countByIsOnline(); + 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()); + + 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()); + } + 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()); + } + + 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); + } + + 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; + } } diff --git a/src/main/java/com/alterdekim/game/component/LongPollConfig.java b/src/main/java/com/alterdekim/game/component/LongPollConfig.java new file mode 100644 index 0000000..0dcd26f --- /dev/null +++ b/src/main/java/com/alterdekim/game/component/LongPollConfig.java @@ -0,0 +1,23 @@ +package com.alterdekim.game.component; + +import com.alterdekim.game.dto.RoomResult; +import com.alterdekim.game.dto.UserResult; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class LongPollConfig { + private Long last_chat_id; + private List rooms; + private Integer session_pass; + private String poll_token; + private List friends_online; + private Long lastRequest; +} diff --git a/src/main/java/com/alterdekim/game/component/LongPollingSession.java b/src/main/java/com/alterdekim/game/component/LongPollingSession.java index 5599a2b..c69c8f7 100644 --- a/src/main/java/com/alterdekim/game/component/LongPollingSession.java +++ b/src/main/java/com/alterdekim/game/component/LongPollingSession.java @@ -7,8 +7,7 @@ import org.springframework.web.context.request.async.DeferredResult; @AllArgsConstructor @Getter - public class LongPollingSession { - private Long last_chat_id; + private Long userId; private DeferredResult deferredResult; } diff --git a/src/main/java/com/alterdekim/game/controller/APIController.java b/src/main/java/com/alterdekim/game/controller/APIController.java index fc03868..f24ca7d 100644 --- a/src/main/java/com/alterdekim/game/controller/APIController.java +++ b/src/main/java/com/alterdekim/game/controller/APIController.java @@ -2,6 +2,7 @@ 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.dto.*; import com.alterdekim.game.entities.Chat; @@ -20,10 +21,9 @@ import org.springframework.web.context.request.async.DeferredResult; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j @@ -51,17 +51,6 @@ public class APIController { @Autowired private LongPoll longPoll; - @GetMapping("/api/v1/games/list") - public ResponseEntity> gamesList() { - List results = roomService.getAll() - .stream() - .map(room -> new RoomResult(room.getId(), room.getPlayerCount(), roomPlayerService.findByRoomId(room.getId()).stream() - .map(p -> userService.findById(p.getId())) - .map(p -> new UserResult(p.getId(), p.getUsername())).collect(Collectors.toList()))) - .collect(Collectors.toList()); - return ResponseEntity.ok(results); - } - @GetMapping("/api/v1/chat/history/{count}/") public ResponseEntity chatList(@PathVariable Integer count ) { List results = chatService.getLastChats(count); @@ -132,11 +121,24 @@ public class APIController { return ResponseEntity.accepted().build(); } - @GetMapping("/async/notify/get/") + @PostMapping("/api/v1/rooms/create/") + public ResponseEntity createRoom( @RequestParam("is_private") Boolean is_private, + @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(); + Long userId = userService.findByUsername(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername()).getId(); + roomPlayerService.leaveByUserId(userId); + roomPlayerService.joinRoom(id, userId); + return ResponseEntity.accepted().build(); + } + + @PostMapping("/async/notify/get/") @ResponseBody public DeferredResult getNotify(@RequestParam("last_chat_id") Long last_chat_id, @RequestParam("accessToken") String accessToken, - @RequestParam("uid") Long userId) { + @RequestParam("uid") Long userId, + @RequestParam("poll_token") String poll_token) { try { User u = userService.findById(userId); if (u == null) return null; @@ -145,48 +147,22 @@ public class APIController { } catch ( Exception e ) { log.error(e.getMessage(), e); } + if( poll_token.length() != 32 ) return null; final DeferredResult deferredResult = new DeferredResult<>(); - longPoll.getLongPollingQueue().add(new LongPollingSession(last_chat_id, deferredResult)); - /*Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - Long userId = userService.findByUsername(((org.springframework.security.core.userdetails.User) authentication.getPrincipal()).getUsername()).getId(); - userService.updateOnline(userId); - - Integer onlineCount = userService.countByIsOnline(); - List results = chatService.getAfterLastChatId(last_chat_id); - List 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().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; - } + 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()); + longPoll.getLongPollingQueue().removeIf(q -> q.getUserId().longValue() == userId.longValue()); } - c.setMessage(message); - return c; - }).collect(Collectors.toList()); - // Room stuff - - List rooms = roomService.getAllActive(); - List roomResults = rooms.stream() - .map( r -> new RoomResult(r.getId(), r.getPlayerCount(), roomPlayerService.findByRoomId(r.getId()).stream() - .map(p -> userService.findById(p.getId())) - .map(p -> new UserResult(p.getId(), p.getUsername())).collect(Collectors.toList()))) - .collect(Collectors.toList()); - return ResponseEntity.ok(new LongPollResult(onlineCount, results, users, roomResults));*/ + 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())); + } + longPoll.getLongPollingQueue().add(new LongPollingSession(userId, deferredResult)); return deferredResult; } } diff --git a/src/main/java/com/alterdekim/game/controller/StaticController.java b/src/main/java/com/alterdekim/game/controller/StaticController.java index 0f80c1b..041244d 100644 --- a/src/main/java/com/alterdekim/game/controller/StaticController.java +++ b/src/main/java/com/alterdekim/game/controller/StaticController.java @@ -31,7 +31,7 @@ public class StaticController { User u = userService.findByUsername(((org.springframework.security.core.userdetails.User) authentication.getPrincipal()).getUsername()); Long userId = u.getId(); String apiKey = Hash.sha256((u.getId() + u.getUsername() + u.getPassword()).getBytes()); - model.addAttribute("auth_obj", new AuthApiObject(apiKey, userId)); + model.addAttribute("auth_obj", new AuthApiObject(apiKey, userId, Hash.rnd())); } catch ( Exception e ) { log.error(e.getMessage(), e); } diff --git a/src/main/java/com/alterdekim/game/dto/AuthApiObject.java b/src/main/java/com/alterdekim/game/dto/AuthApiObject.java index f04f681..7f67954 100644 --- a/src/main/java/com/alterdekim/game/dto/AuthApiObject.java +++ b/src/main/java/com/alterdekim/game/dto/AuthApiObject.java @@ -10,4 +10,5 @@ import lombok.NoArgsConstructor; public class AuthApiObject { private String accessToken; private Long uid; + private String poll_token; } diff --git a/src/main/java/com/alterdekim/game/dto/FriendResult.java b/src/main/java/com/alterdekim/game/dto/FriendResult.java new file mode 100644 index 0000000..b8cfaca --- /dev/null +++ b/src/main/java/com/alterdekim/game/dto/FriendResult.java @@ -0,0 +1,14 @@ +package com.alterdekim.game.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class FriendResult { + private FriendState action; + private Long id; + private String username; +} diff --git a/src/main/java/com/alterdekim/game/dto/FriendState.java b/src/main/java/com/alterdekim/game/dto/FriendState.java new file mode 100644 index 0000000..478d7d9 --- /dev/null +++ b/src/main/java/com/alterdekim/game/dto/FriendState.java @@ -0,0 +1,6 @@ +package com.alterdekim.game.dto; + +public enum FriendState { + ADD, + REMOVE +} diff --git a/src/main/java/com/alterdekim/game/dto/LongPollResult.java b/src/main/java/com/alterdekim/game/dto/LongPollResult.java index de218c2..1879e20 100644 --- a/src/main/java/com/alterdekim/game/dto/LongPollResult.java +++ b/src/main/java/com/alterdekim/game/dto/LongPollResult.java @@ -14,5 +14,6 @@ public class LongPollResult { private Integer onlineCount; private List messages; private List users; - private List rooms; + private List rooms; + private List friends; } diff --git a/src/main/java/com/alterdekim/game/dto/RoomResultState.java b/src/main/java/com/alterdekim/game/dto/RoomResultState.java new file mode 100644 index 0000000..7732ab3 --- /dev/null +++ b/src/main/java/com/alterdekim/game/dto/RoomResultState.java @@ -0,0 +1,6 @@ +package com.alterdekim.game.dto; + +public enum RoomResultState { + ADD_CHANGE, + REMOVE +} diff --git a/src/main/java/com/alterdekim/game/dto/RoomResultV2.java b/src/main/java/com/alterdekim/game/dto/RoomResultV2.java new file mode 100644 index 0000000..ee1aebf --- /dev/null +++ b/src/main/java/com/alterdekim/game/dto/RoomResultV2.java @@ -0,0 +1,17 @@ +package com.alterdekim.game.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class RoomResultV2 { + private RoomResultState action; + private Long id; + private Integer playerCount; + private List players; +} diff --git a/src/main/java/com/alterdekim/game/repository/FriendRepository.java b/src/main/java/com/alterdekim/game/repository/FriendRepository.java new file mode 100644 index 0000000..bbd381c --- /dev/null +++ b/src/main/java/com/alterdekim/game/repository/FriendRepository.java @@ -0,0 +1,16 @@ +package com.alterdekim.game.repository; + +import com.alterdekim.game.entities.FriendStatus; +import com.alterdekim.game.entities.Room; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface FriendRepository extends JpaRepository { + @Query(value = "SELECT IF(f.firstUserId = :userId, f.secondUserId, f.firstUserId) FROM FriendStatus f WHERE (f.firstUserId = :userId OR f.secondUserId = :userId) AND f.status = 2") + List getFriendsOfUserId(@Param("userId") Long userId); +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/game/repository/RoomPlayerRepository.java b/src/main/java/com/alterdekim/game/repository/RoomPlayerRepository.java index a34efe6..8dcb8b5 100644 --- a/src/main/java/com/alterdekim/game/repository/RoomPlayerRepository.java +++ b/src/main/java/com/alterdekim/game/repository/RoomPlayerRepository.java @@ -3,11 +3,22 @@ package com.alterdekim.game.repository; import com.alterdekim.game.entities.Room; import com.alterdekim.game.entities.RoomPlayer; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @Repository public interface RoomPlayerRepository extends JpaRepository { - List findByRoomId(Long roomId); + + @Query(value = "SELECT new RoomPlayer(r.id, r.roomId, r.userId) FROM RoomPlayer r WHERE r.roomId = :roomId ORDER BY r.userId ASC") + List findByRoomId(@Param("roomId") Long roomId); + + @Transactional + @Modifying + @Query(value = "DELETE FROM RoomPlayer r WHERE r.userId = :userId") + void deleteAllByUserId(@Param("userId") Long userId); } diff --git a/src/main/java/com/alterdekim/game/repository/RoomRepository.java b/src/main/java/com/alterdekim/game/repository/RoomRepository.java index a8a319e..0dfa747 100644 --- a/src/main/java/com/alterdekim/game/repository/RoomRepository.java +++ b/src/main/java/com/alterdekim/game/repository/RoomRepository.java @@ -3,14 +3,22 @@ package com.alterdekim.game.repository; import com.alterdekim.game.entities.Room; import com.alterdekim.game.entities.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @Repository public interface RoomRepository extends JpaRepository { - @Query(value = "SELECT r FROM Room r WHERE r.isPrivate = false") + @Query(value = "SELECT r FROM Room r WHERE r.isPrivate = false ORDER BY r.id ASC") List findAllByActiveTrue(); + + @Transactional(propagation = Propagation.REQUIRES_NEW) + @Modifying + @Query(value = "DELETE FROM room WHERE room.id NOT IN (SELECT t.room_id FROM (SELECT COUNT(id) as UID, room_id FROM room_player GROUP BY room_id) t)", nativeQuery = true) + void clearEmptyRooms(); } \ No newline at end of file diff --git a/src/main/java/com/alterdekim/game/service/FriendServiceImpl.java b/src/main/java/com/alterdekim/game/service/FriendServiceImpl.java new file mode 100644 index 0000000..d1a0cd5 --- /dev/null +++ b/src/main/java/com/alterdekim/game/service/FriendServiceImpl.java @@ -0,0 +1,17 @@ +package com.alterdekim.game.service; + +import com.alterdekim.game.repository.FriendRepository; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@AllArgsConstructor +public class FriendServiceImpl { + private final FriendRepository repository; + + public List getFriendsOfUserId(Long userId) { + return repository.getFriendsOfUserId(userId); + } +} diff --git a/src/main/java/com/alterdekim/game/service/RoomPlayerServiceImpl.java b/src/main/java/com/alterdekim/game/service/RoomPlayerServiceImpl.java index c75f660..1e12c79 100644 --- a/src/main/java/com/alterdekim/game/service/RoomPlayerServiceImpl.java +++ b/src/main/java/com/alterdekim/game/service/RoomPlayerServiceImpl.java @@ -24,4 +24,12 @@ public class RoomPlayerServiceImpl implements RoomPlayerService{ public List findByRoomId(Long roomId) { return repository.findByRoomId(roomId); } + + public void joinRoom(Long id, Long userId) { + repository.save(new RoomPlayer(id, userId)); + } + + public void leaveByUserId(Long userId) { + repository.deleteAllByUserId(userId); + } } diff --git a/src/main/java/com/alterdekim/game/service/RoomServiceImpl.java b/src/main/java/com/alterdekim/game/service/RoomServiceImpl.java index 98e9690..1a58589 100644 --- a/src/main/java/com/alterdekim/game/service/RoomServiceImpl.java +++ b/src/main/java/com/alterdekim/game/service/RoomServiceImpl.java @@ -5,6 +5,7 @@ import com.alterdekim.game.repository.RoomRepository; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; @Service public class RoomServiceImpl implements RoomService { @@ -22,4 +23,16 @@ public class RoomServiceImpl implements RoomService { public List getAllActive() { return roomRepository.findAllByActiveTrue(); } + + public Optional findById(Long id) { + return roomRepository.findById(id); + } + + public Long createRoom(Room room) { + return roomRepository.save(room).getId(); + } + + public void clearEmptyRooms() { + roomRepository.clearEmptyRooms(); + } } diff --git a/src/main/java/com/alterdekim/game/util/Hash.java b/src/main/java/com/alterdekim/game/util/Hash.java index c854697..adf7696 100644 --- a/src/main/java/com/alterdekim/game/util/Hash.java +++ b/src/main/java/com/alterdekim/game/util/Hash.java @@ -1,6 +1,9 @@ package com.alterdekim.game.util; import java.security.MessageDigest; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; public class Hash { public static String sha256( byte[] b ) throws Exception { @@ -19,4 +22,12 @@ public class Hash { } return hexString.toString(); } + + public static String rnd() { + return IntStream.generate(() -> new Random().nextInt(0, 62)) + .limit(32) + .boxed() + .map(i -> "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".charAt(i)+"") + .collect(Collectors.joining()); + } } diff --git a/src/main/resources/static/css/games.css b/src/main/resources/static/css/games.css new file mode 100644 index 0000000..7ed1a62 --- /dev/null +++ b/src/main/resources/static/css/games.css @@ -0,0 +1,414 @@ +.grid { + display: grid !important; + grid-template-columns: repeat(12,1fr); + grid-column-gap: 25px; +} + +.g-col-4 { + grid-column-end: span 4; + min-width: 0; +} + +.g-col-8 { + grid-column-end: span 8; + min-width: 0; +} + +.block { + display: block; + border-bottom: 2px solid rgba(0,0,0,.1); + border-radius: 5px; + padding-top: .1px; + padding-bottom: .1px; + width: 100%; + min-height: 1px; + background-color: #fff; +} + +.container { + width: 1000px; +} + +.title { + margin-bottom: 15px; + font-size: 18px; + margin-top: 20px; + margin-right: 20px; + margin-left: 20px; +} + +.block-content { + justify-content: center; + display: flex; + margin-bottom: 15px; +} + +.market { + height: 160px; + align-items: center; + justify-content: center; + display: flex; +} + +.transparent-override { + background-color: transparent !important; +} + +.text-glowing { + color: rgba(55, 188, 157, 1) !important; +} + +.mission-one { + margin-top: 10px; + border-top: 1px solid #e9eaec; + padding: 10px; +} + +.mission-target>span { + font-weight: 700; +} + +.mission-block>span { + font-weight: 700; +} + +.progress { + margin-top: 5px; + height: 7px; +} + +.progress-bar { + background-color: rgba(55, 188, 157, 1) !important; +} + +.mission-block, .mission-target { + font-size: 13px; + margin-top: 5px; + display: flex; + align-items: center; + gap: 0.3em; +} + +.mission-text { + font-size: 13px; +} + +.friend-search { + margin: 20px; + line-height: 1.6; +} + +.empty-list-message { + text-align: center; + margin: 20px; +} + +.empty-list-message>p { + font-size: 13px; +} + +.title > span { + color: #a7adb5; + font-size: 14px; +} + +.chat-history { + padding: 20px; + margin-bottom: 15px; + width: 100%; + height: 280px; + overflow-x: hidden; + overflow-y: scroll; + word-wrap: break-word; +} + +#market-carousel { + width: 100%; +} + +.carousel-title-text { + font-size: 19px; + font-weight: 700; + color: #fff; +} + +.carousel-body-text { + margin-top: 10px; + font-size: 14px; + line-height: 20px; + color: #fff; +} + +.carousel-item-body { + padding: 0 70px; +} + +.carousel-item-background { + display: flex; + flex-direction: column; + justify-content: center; +} + +.chat-history-one { + margin: 5px 0; +} + +.chat-history-info { + display: inline-flex; + align-items: center; + margin-right: 5px; + color: #989aa4; + transform: translateY(1px); +} + +.chat-history-user { + padding: 0 6px; + text-decoration: none; + background-color: #b9bac1; + border-radius: 5px; +} + +.chat-history-user>._nick { + color: #fff; + font-weight: 600; +} + +.chat-history-text { + margin-left: 5px; +} + +.chat-history-one>* { + font-size: 14px; + vertical-align: middle; + line-height: 21px; +} + +.chat-history-info>.time { + margin-right: 5px; +} + +.message-input { + margin: 20px; + line-height: 1.6; +} + +.games-room-one { + display: flex; + flex-flow: column nowrap; + position: relative; + border-top: 1px solid; + border-color: #e6e6e6; +} + +.games-room-one-body { + padding: 10px 20px; +} + +.games-room-one-body-head { + display: flex; + flex-flow: row nowrap; + height: 25px; +} + +.games-room-one-body-head-info { + flex-grow: 999; + display: flex; + flex-flow: row nowrap; + gap: 10px; +} + +.games-room-one-body-members { + display: flex; + flex-flow: row nowrap; + margin-top: 13px; + margin-bottom: 5px; + justify-content: center; +} + +.games-room-one-body-members-one { + position: relative; + margin: 0 5px; + width: calc(20% - 2 * 5px); +} + +.games-room-one-body-members-one-nick { + margin-top: 5px; + height: 21px; + text-align: center; + line-height: 21px; + white-space: nowrap; +} + +.games-room-one-body-members-one-nick > a { + color: #656d78; + font-size: 14px; +} + +.games-room-one-body-members-one-nick > span { + color: #656d78; + font-size: 14px; + text-decoration: none; +} + +.games-room-one-body-members-one-avatar { + display: flex; + justify-content: center; + align-items: center; + position: relative; + margin: auto; + border-radius: 100%; + width: 75px; + height: 75px; + background: center/cover no-repeat; + font: 50px Ionicons; +} + +.games-room-one-body-head-info > ._type > div { + font-size: 15px; +} + +.games-room-one-body-head-info > ._with_wormhole > div { + font-size: 15px; +} + +._slot_join { + cursor: pointer; +} + +#waiting_list > .title { + align-items: center; + display: flex; + position: relative; +} + +#waiting_list > .title > a { + position: absolute; + right: 0; +} + +body.modal-open > :not(.modal) { + -webkit-filter: blur(3px); + -moz-filter: blur(3px); + -o-filter: blur(3px); + -ms-filter: blur(3px); + filter: blur(3px); +} + +.game-creation-modal-modes { + background-color: #f7f7f7; + flex-shrink: 0; + border-radius: 5px 0 0 5px; + padding: 10px 15px 10px 10px; + width: 250px; + overflow: hidden; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.game-creation-modal-block { + flex-shrink: 0; + position: relative; + left: -5px; + border-radius: 5px; + padding: 20px 0; + width: 375px; + background-color: #fff; + box-shadow: 0 0 25px rgba(0,0,0,.1); +} + +.game-creation-modal-body { + display: flex; + position: relative; + box-shadow: 0 0 100px rgba(0,0,0,.333); + border-radius: 5px; + width: 620px; + text-align: left; +} + +.game-creation-modal-modes-one._regular._selected { + background: #8cc152; +} + +.game-creation-modal-modes-one._selected { + color: #fff; +} + +.game-creation-modal-modes-one { + border-radius: 5px; + padding: 10px 15px; + cursor: pointer; +} + +.game-creation-modal-modes-one-title { + font-weight: 600; + font-size: 18px; + line-height: 21px; +} + +.game-creation-modal-modes-one-subtitle { + margin-top: 4px; + font-size: 12px; + line-height: 15px; + opacity: .85; +} + +.game-creation-modal-block-title { + margin-bottom: 10px; + font-weight: 600; + margin-top: 0; + font-size: 18px; + position: relative; + margin-left: 20px; + margin-right: 20px; +} + +.game-creation-modal-block-subtitle { + margin-right: 20px; + margin-left: 20px; + margin-top: 10px; + margin-bottom: 10px; + font-size: 16px; + position: relative; + font-weight: 400; +} + +.form-switch-game { + transform: scale(1.2); + margin-left: 3.5rem; +} + +.form-check-input:checked { + background-color: #37bc9d; + border-color: #37bc9d; +} + +#players-count-range, .players-range-value { + margin-left: 20px; + width: 80%; +} + +.form-range::-webkit-slider-thumb { + background-color: #37bc9d; +} + +.reply-button { + cursor: pointer; +} + +.friend-one { + align-items: center; + display: flex; + justify-content: space-around; + padding: 10px; +} + +.friend-one > span { + font-size: 14px; +} + +.friend-one > ion-icon { + font-size: 18px; + color: #37bc9d; + cursor: pointer; +} \ No newline at end of file diff --git a/src/main/resources/static/css/index.css b/src/main/resources/static/css/index.css new file mode 100644 index 0000000..499bb24 --- /dev/null +++ b/src/main/resources/static/css/index.css @@ -0,0 +1,110 @@ +.navbar-brand-custom { + color: #ffffff; +} + +.navbar-brand-custom:hover { + color: #ffffff; +} + +.navbar { + background-color: #37bc9d; +} + +.btn-outline-primary { + border-color: transparent; + color: #fff; +} + +.btn-primary { + background-color: rgba(255,255,255,.85); + color: #37bc9d; + border-color: rgba(255,255,255,.85); +} + +.btn-primary:hover { + background-color: #ffffff; + border-color: #ffffff; + color: #37bc9d; +} + +.btn-outline-primary:hover { + background-color: rgba(255, 255, 255,0.25); + border-color: rgba(255, 255, 255,0.25); +} + +.page-title { + font: 700 3.5em Montserrat; + color: #fff; +} + +.page-title-small { + margin-top: .7em; + font: 500 1.32em/1.4em Montserrat; + color: #fff; +} + +.main-billboard { + background-color: #37bc9d; + height: 700px; + text-align: center; + padding-top: 50px; +} + +.main-button { + color: #5b5d67; + background-color: #fff; + border-color: #fff; + font: 600 1.5rem Montserrat; + padding: 0 0.9em; + height: 2.1em; + align-items: center; + display: inline-flex; +} + +.main-button:hover { + color: #5b5d67; + background-color: rgba(255, 255, 255, 0.85); + border-color: rgba(255, 255, 255, 0.85); + font: 600 1.5rem Montserrat; + padding: 0 0.9em; + height: 2.1em; + align-items: center; + display: inline-flex; +} + +.page-main-button { + margin-top: 25px; +} + +.page-title-second { + margin-top: 1.25em; + margin-right: 0px; + margin-bottom: 1.25em; + margin-left: 0px; + font: 700 3.25em Montserrat; + color: #26272b; + text-align: center; +} + +.billboard-img { + box-shadow: 0 0 100px rgba(0,0,0,.33); + width: 900px; + overflow: hidden; + pointer-events: none; +} + +.billboard-container { + display: flex; + justify-content: center; + margin-top: 40px; +} + +h4 { + margin: 0 0 .4em; + font: 700 2em Montserrat; +} + +.col_text { + font-size: 1.1em; + text-align: left; +} \ No newline at end of file diff --git a/src/main/resources/static/javascript/games.js b/src/main/resources/static/javascript/games.js index a8407bf..28108c8 100644 --- a/src/main/resources/static/javascript/games.js +++ b/src/main/resources/static/javascript/games.js @@ -1,10 +1,119 @@ -$.fn.pressEnter = function(fn) { +var isPollingActive = true; +function createRoom() { + let isprivate = $("#flexSwitchCheckDefault").is(':checked'); + let playerscnt = $("#players-count-range").val(); + $.ajax({ + url: "/api/v1/rooms/create/", + data: { + is_private: isprivate, + players_count: playerscnt + }, + method: "POST" + }).done(function(data) { + $("#game-creation-menu-modal").modal('hide'); + }); +} + +function successPolling(data) { + console.log(data); + let onlineCount = data.onlineCount; + $(".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 time = parseTime(obj.createdAt); + let username = findUser(users, obj.userId).username; + let userid = obj.userId; + let msgtext = obj.message; + let html = ''+time+''+username+''+msgtext+''; + $(".chat-history").append(html); + } + for( let i = 0; i < rooms.length; i++ ) { + let room = rooms[i]; + if( room.action == 'ADD_CHANGE' ) { + let room_p_html = ''; + for( let u = 0; u < room.players.length; u++ ) { + let room_player = room.players[u]; + room_p_html += ''+room_player.username+''; + } + for( let u = 0; u < (room.playerCount - room.players.length); u++ ) { + room_p_html += 'Join'; + } + let room_html = 'Game'+room_p_html+''; + let has_element = false; + $(".games-room-one").each(function() { + if( $(this).attr("data-room-id") == room.id ) { + $(this).replaceWith(room_html); + has_element = true; + } + }); + if( !has_element ) { + $(".rooms-list").append(room_html); + } + } else if( room.action == 'REMOVE' ) { + $(".games-room-one").each(function() { + if( $(this).attr("data-room-id") == room.id ) { + $(this).remove(); + } + }); + } + } + for( let i = 0; i < friends.length; i++ ) { + let friend = friends[i]; + if( friend.action == 'ADD' ) { + let fr_html = ''+friend.username+''; + $(".friends-online-list").append(fr_html); + } else if( friend.action == 'REMOVE' ) { + $(".friend-one").each(function() { + if( $(this).attr("data-friend-id") == friend.id ) { + $(this).remove(); + } + }); + } + } + var clh = $('#chat_list').find(".chat-history"); + clh.scrollTop(clh.prop("scrollHeight")); + $("#chat_list").find(".chat-history").css("display", ""); + $("#chat_list").find(".message-input").css("display", ""); + $("#chat_list").find(".block-content").css("display", "none"); +} + +function pollServer() { + let accessToken = $("api-tag").attr("data-access-token"); + let uid = $("api-tag").attr("data-uid"); + let poll_token = $("api-tag").attr("data-poll-token"); + if (isPollingActive) { + window.setTimeout(function () { + $.ajax({ + url: "/async/notify/get/", + data: { + last_chat_id: last_chat_id, + accessToken: accessToken, + poll_token: poll_token, + uid: uid + }, + method: "POST" + }).done(function(data) { + successPolling(data); + //SUCCESS LOGIC + pollServer(); + }); + }, 1500); + } +} + +$.fn.pressEnter = function(fn) { return this.each(function() { $(this).bind('enterPress', fn); - $(this).keyup(function(e){ - if(e.keyCode == 13) - { + $(this).keyup(function(e) { + if(e.keyCode == 13) { $(this).trigger("enterPress"); } }) @@ -34,41 +143,6 @@ function replyButtonClicked(obj) { $('#chat-message-input').val($('#chat-message-input').val() + " @" + username + ""); } -setInterval(function() { - let accessToken = $("api-tag").attr("data-access-token"); - let uid = $("api-tag").attr("data-uid"); - $.ajax({ - url: "/async/notify/get/", - data: { - last_chat_id: last_chat_id, - accessToken: accessToken, - uid: uid - }, - method: "GET" - }).done(function(data) { - console.log(data); - let onlineCount = data.onlineCount; - $(".chat-title").find("span").html(onlineCount + " online"); - let messages = data.messages; - let users = data.users; - if( messages.length > 0 ) { - last_chat_id = messages[0].id; - } - for( let i = 0; i < messages.length; i++ ) { - let obj = messages[i]; - let time = parseTime(obj.createdAt); - let username = findUser(users, obj.userId).username; - let userid = obj.userId; - let msgtext = obj.message; - let html = ''+time+''+username+''+msgtext+''; - $(".chat-history").append(html); - } - $("#chat_list").find(".chat-history").css("display", ""); - $("#chat_list").find(".message-input").css("display", ""); - $("#chat_list").find(".block-content").css("display", "none"); - }); -}, 5000); - $(document).ready(function() { $("#players-count-range").on("input", function() { $("label[for='players-count-range']").find("span").text($(this).val()); @@ -86,13 +160,6 @@ $(document).ready(function() { } }); - $.ajax({ - url: "/api/v1/games/list", - method: "GET" - }).done(function(data) { - console.log(data); - }); - // block-content $.ajax({ @@ -114,6 +181,8 @@ $(document).ready(function() { let html = ''+time+''+username+''+msgtext+''; $(".chat-history").append(html); } + var clh = $('#chat_list').find(".chat-history"); + clh.scrollTop(clh.prop("scrollHeight")); $("#chat_list").find(".chat-history").css("display", ""); $("#chat_list").find(".message-input").css("display", ""); $("#chat_list").find(".block-content").css("display", "none"); @@ -140,10 +209,8 @@ $(document).ready(function() { $(".market").find("#market-carousel").css("display", ""); }); - /*$.ajax({ - url: "/api/v1/games/list", - method: "GET" - }).done(function(data) { - console.log(data); - });*/ + pollServer(); + + $(".rooms-list").css("display", ""); + $("#waiting_list").find(".block-content").css("display", "none"); }); \ No newline at end of file diff --git a/src/main/resources/templates/games.html b/src/main/resources/templates/games.html index 0ec05db..17d65ba 100644 --- a/src/main/resources/templates/games.html +++ b/src/main/resources/templates/games.html @@ -2,406 +2,8 @@ - - + + @@ -425,7 +27,7 @@ Private room - + Start @@ -484,15 +86,15 @@ Loading... - + --> + @@ -559,8 +161,8 @@ Create game - + --> + Loading... diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index cadabb0..acc16c7 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -2,118 +2,7 @@ - +