Skip to content

Charge-Evolution-Battle-on-Earth/Server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ•ΉοΈμ‹€μ‹œκ°„ μ–‘λ°©ν–₯ ν„΄μ œ RPG

μ§„ν™”μ „νˆ¬_둜고

  • λ‹€μš΄λ‘œλ“œ: GitHub Release
  • λ””μžμΈ: Figma
  • ERD: ERD

ν”„λ‘œμ νŠΈ μ†Œκ°œ

μ•ˆμ •μ μΈ μ‹€μ‹œκ°„ μ–‘λ°©ν–₯ 톡신 μ„œλΉ„μŠ€λ₯Ό μ œκ³΅ν•˜κΈ° μœ„ν•΄ 순수 WebSocket을 ν™œμš©ν•˜μ—¬ 메세지 νŒŒμ‹±κ³Ό λΌμš°νŒ…, μ˜ˆμ™Έ 처리 μ‹œμŠ€ν…œμ„ κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€. 이 κ³Όμ •μ—μ„œ 메세지 ν”„λ‘œν† μ½œ μ •μ˜, λ™μ‹œμ„± 문제 ν•΄κ²°, AOP을 ν†΅ν•œ μ˜ˆμ™Έμ²˜λ¦¬ λ“± λ‹€μ–‘ν•œ 어렀움을 ν•΄κ²°ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

  • CEBONE은 μ‹€μ‹œκ°„ μ–‘λ°©ν–₯ 톡신을 톡해 μƒλŒ€ ν”Œλ ˆμ΄μ–΄μ™€ 턴을 λ²ˆκ°ˆμ•„ κ°€λ©° μ „νˆ¬λ₯Ό ν•˜λŠ” κ²Œμž„μž…λ‹ˆλ‹€.
  • ν”Œλ ˆμ΄μ–΄λŠ” μƒμ μ—μ„œ μ›ν•˜λŠ” 무기와 μž₯λΉ„ λ“± μ•„μ΄ν…œμ„ 톡해 μžμ‹ μ˜ 캐릭터λ₯Ό κ°•ν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • λ‘œλΉ„μ—μ„œ 맀칭방을 μ°Ύμ•„ μ „νˆ¬ν•  ν”Œλ ˆμ΄μ–΄λ₯Ό 선택할 수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ „νˆ¬λ₯Ό μ™„λ£Œν•˜λ©΄ νšλ“ν•˜λŠ” κ²½ν—˜μΉ˜λ₯Ό 톡해 레벨 업을 ν•  수 있으며 μ΄λ•Œ μ „λž΅μ μœΌλ‘œ μƒˆλ‘œμš΄ μŠ€ν‚¬μ„ μŠ΅λ“ν•˜μ—¬ μ„±μž₯ λ°©ν–₯을 κ²°μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

πŸ’» 개발 ν™˜κ²½

  • Version : Java 17
  • IDE : IntelliJ
  • Framework : SpringBoot 3.1.5
  • ORM : JPA
  • Real Time Networking : WebSocket

μ£Όμš” κΈ°λŠ₯

  • νšŒμ› κ°€μž… 및 둜그인
  • λ‘œλΉ„ - λ°© 생성 및 μž…μž₯
  • 상점 - μ•„μ΄ν…œ ꡬ맀
  • 인벀토리 - μ•„μ΄ν…œ μž₯μ°©, ν•΄μ œ, 판맀
  • μƒνƒœμ°½ - μžμ‹ μ˜ 정보 확인, κ²½ν—˜μΉ˜
  • μΈκ²Œμž„ - μ‹€μ‹œκ°„ ν†΅μ‹ μœΌλ‘œ κ²Œμž„ 진행

🧩 ERD

CEBONE_ERD

μΈκ²Œμž„ μ‹œμ—°

CEBONE

μ‹€μ‹œκ°„ μ–‘λ°©ν–₯ 톡신 μ‹œμŠ€ν…œ

πŸ“ WebSocket 메세지 ν”„λ‘œν† μ½œ

{
    "command": "COMMAND",
    "matchId": "long",
    "request":{ }
}

πŸ“ WebSocket μ„Έμ…˜ 관리 - λ™μ‹œμ„± 문제 ν•΄κ²°

@Component
public class WebSocketSessionManager {

    private Map<Long, WebSocketSession> webSocketSessionMap = new ConcurrentHashMap<>();

    public synchronized void addWebSocketSessionMap(Long key, WebSocketSession socketSession) {
        if (!validateSession(socketSession)) {
            throw new WebSocketSessionInvalidException();
        }
        this.webSocketSessionMap.put(key, socketSession);
    }

    public synchronized void removeWebSocketSessionMap(Long key) {
        this.webSocketSessionMap.remove(key);
    }

    public synchronized void sendMessage(String message, List<Long> keys) throws IOException {
        for (Long key : keys) {
            WebSocketSession socketSession = this.webSocketSessionMap.get(key);
            if (validateSession(socketSession)) {
                socketSession.sendMessage(new TextMessage(message));
            }
        }
    }

    public synchronized boolean validateSession(WebSocketSession socketSession) {
        return socketSession != null && socketSession.isOpen();
    }
}

πŸ“ WebSocket 메세지 νŒŒμ‹± 및 λΌμš°νŒ…

λ©”μ‹œμ§€ νŒŒμ‹±

/**
     * μΈκ²Œμž„ ν”Œλ ˆμ΄ 쀑 λ°œμƒν•˜λŠ” WebSocket 기반 μš”μ²­λ“€μ„ μ²˜λ¦¬ν•˜λŠ” 핡심 λ©”μ†Œλ“œ. 메세지 νŒŒμ‹±κ³Ό λΌμš°νŒ…μ„ μˆ˜ν–‰ν•¨.
     *
     * @param session WebSocketSession
     * @param message WebSocketMessageProtocol ν˜•μ‹μ„ μ€€μˆ˜ν•œ 메세지
     * @throws IOException
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message)
        throws IOException {
        Long characterId = null;
        try {
            characterId = extractCharacterId(session, Long.class);
            WebSocketMessageRequest messageRequest = jsonUtil.parseWebSocketMessage(
                message.getPayload());
            Long matchId = messageRequest.matchId();
            JsonObject request = messageRequest.request();
            String command = messageRequest.command();

            MatchPlayer matchPlayers = matchService.findPlayerByMatchId(matchId);
            List<Long> matchPlayersList = matchPlayers.toList();

            if (!matchPlayers.isContainsPlayer(characterId)) {
                throw new CharacterNotInMatchException(matchId);
            }

            WebSocketCommand webSocketCommand = findWebSocketCommand(command);

            dispatcher(characterId, matchId, request, matchPlayersList, webSocketCommand);
        } catch (Exception e) {
            webSocketSessionManager.sendMessage(e.getMessage(),
                Collections.singletonList(characterId));
        }
    }

메세지 λΌμš°νŒ…

private void dispatcher(Long characterId, Long matchId, JsonObject request,
        List<Long> matchPlayers, WebSocketCommand webSocketCommand)
        throws Exception {
        String responseMessage = new String();

        //message routing
        switch (webSocketCommand) {
            case GREETING -> {
                responseMessage = playController.greeting(characterId);
            }
            case READY -> {
                PlayReadyRequest playReadyRequest = jsonUtil.fromJson(request,
                    PlayReadyRequest.class);
                responseMessage = playController.ready(characterId, matchId
                    , playReadyRequest);
            }
            case START -> {
                responseMessage = playController.start(characterId, matchId);
            }
            case TURN_GAME -> {
                responseMessage = playController.turnGame(characterId, matchId,
                    jsonUtil.fromJson(request, PlayTurnRequest.class));
            }
            case END_GAME -> {
                responseMessage = playController.endGame(characterId, matchId);
            }
            case SURRENDER_GAME -> {
                responseMessage = playController.surrenderGame(characterId, matchId);
            }
            case QUIT_GAME -> {
                responseMessage = playController.quitGame(characterId, matchId);
            }
            case EMPTY -> throw new InvalidWebSocketMessageException(matchId);
        }
        webSocketSessionManager.sendMessage(responseMessage, matchPlayers);
    }

AOP 기반 WebSocket μ˜ˆμ™Έ 처리

@Aspect
@Component
@RequiredArgsConstructor
public class WebSocketExceptionAspect {

    private final JsonUtil jsonUtil;

    @Around("execution(* com.project.game.play.controller.PlayController.*(..))")
    public Object handleExceptionAndLog(ProceedingJoinPoint joinPoint) {
        try {
            return joinPoint.proceed();
        } catch (WebSocketException we) {
            ErrorResponse response = new ErrorResponse(we);
            return jsonUtil.toJson(response);
        } catch (Throwable e) {
            ErrorResponse response = new ErrorResponse(e);
            return jsonUtil.toJson(response);
        }
    }
}

About

Charge! Evolution Battle on Earth Server by Spring

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published