목차
  1. 간단한 소개: 간단한 작품 소개
  2. 사용 기술: 프로젝트에서 사용한 기술 스택
  3. 결과: 프로젝트의 주요 성과 또는 학습한 점
  4. 다이어그램: 프로젝트를 다이어그램으로 표현
  5. 링크: 프로젝트 관련 GitHub 링크


 

간단한 소개

이 사이트는 오목 매칭 사이트입니다. https://ok5pj.com/login

구글 로그인, 채팅, 전적확인, 닉네임 변경, 오목 매칭이 가능합니다.

 

먼저 가장 기본인 로그인한 화면입니다.

 

이건 매칭된 상태입니다.

 

오목의 룰중 렌주룰을 적용 구현하였습니다.

 

승판이 났을 경우

 

 

사용 기술

 

전체 구성

programming language JAVA
IDE IntelliJ IDEA
Framework JAVA SPRING
Cloud DataBase MongoDB Atlas, AWS Dynamo DB
Server Nginx
Security JWT, OAuth 2.0
Build Tool Gradle
Deployment AWS Elastic Beanstalk

 

Library


dependencies { //라이브러리 넣는곳
	//lombok
	compileOnly ("org.projectlombok:lombok")
	annotationProcessor("org.projectlombok:lombok")
	//thymeleaf
	implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
	//DynamoDB
	implementation(platform("software.amazon.awssdk:bom:2.20.85"))
	implementation("software.amazon.awssdk:dynamodb-enhanced")
	//Maper
	implementation ("org.modelmapper:modelmapper:2.4.4")
	//security
	implementation ("org.springframework.boot:spring-boot-starter-security") 
	implementation ("org.thymeleaf.extras:thymeleaf-extras-springsecurity6:latest.release")
	implementation ("org.springframework.security:spring-security-test")
	//JWT
	implementation ("io.jsonwebtoken:jjwt:0.9.1") 
	implementation ("javax.xml.bind:jaxb-api:2.3.1") 
	//OAuth2
	implementation ("org.springframework.boot:spring-boot-starter-oauth2-client")
	//jpa
	implementation("org.springframework.boot:spring-boot-starter-data-jpa")
	//mustache
	implementation("org.springframework.boot:spring-boot-starter-mustache")
	//web
	implementation("org.springframework.boot:spring-boot-starter-web")
	//start test
	testImplementation("org.springframework.boot:spring-boot-starter-test")
	//Mongo DB
	implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
	implementation ("org.springframework.boot:spring-boot-starter-webflux")
	//web socket
	implementation("org.springframework.boot:spring-boot-starter-websocket")
	//date -> json
	implementation ("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.0")
}

 

 

DataBase(MongoDB Atlas)

 

MongoDB Atlas DataBase Collections 구성

game chat

 

 

game

오목판을 저장하기 위한 Collections 입니다.

플레이어가 흑돌을 두었다면 세션 통신을 통하여 백돌에게 신호를 주기 때문에 game Collections 는 사실상 필요 없을것 같지만,

만약 게임 진행중 플레이어가 사이트를 나갔다 들어왔을 경우 game Collections의 Data를 불러와 게임을 이어서 플레이 할 수 있습니다.

 

chat

채팅 내역을 저장하기 위한 Collections 입니다.

 

 

 

Collections game구성

Key 설명
id 고유 값 부여(저장시간+uesr1,user2)
user1 흑돌을 플레이 할 유저의 Email을 저장
user2 백돌을 플레이 할 유저의 Email을 저장
table 진행 중인 오목판 저장

tableLog

오목판 로그를 저장
turn 누구의 턴인지 저장
time 오목 제한시간 저장

 

game Entity 구성


@Data
@Document(collection = "game")
public class Game_E {
  @Id
  private String id;
  @Indexed
  private String user1;
  @Indexed
  private String user2;
  @Indexed
  private String[][] table;
  @Indexed
  List<String [][]> tableLog;
  @Indexed
  private String turn;
  @Indexed
  private String time;
}

 

 

 

DataBase(AWS Dynamo DB)

 

AWS Dynamo DB DataBase Table구성

Account Connect RefreshToken

 

Account

회원의 계정 정보를 저장하기 위한  저장하기 위한 Table입니다.

 

Connect

회원의 접속에 대한 정보를 저장합니다.

실시간 데이터 스트림 기능을 사용했습니다.

만약 Connect Tableconnect data가 바뀐다면 접속한 모든 유저에게 data를 보냅니다.

 

RefreshToken

JWT방식의 인증 방식을 사용함으로 RefreshToken을 저장하는 Table입니다.

 

Account Table 구성

Column 설명
id 랜덤한 숫자를 이용한 고유 ID
email Google Email을 저장
nickname Google 닉네임을 저장
profile_url Google 프로필 URL을 저장
subnuckname 오목 사이트에 사용할 nickname을 저장
victory 승리 횟수 저장
defeat 패배 횟수 저장

 

Connect Table 구성

Column 설명
email Google Email을 저장
connect 0,1,2,3으로 이루어졌다. 순서대로 미접속, 접속, 매칭중, 게임중으로 나뉜다.
matching 매칭버튼을 클릭 했을 시간을 저장, 매칭이 되었을 경우 매칭 시간으로 흑돌과 백돌을 결정함
position Mongo DB에 Collections인 game의 Primary Key를 저장한다. 
socket 접속시 생성되는 Socket ID를 저장한다.

 

RfreshToken Table 구성

Column 설명
id 고유한 id값을 저장한다.
refresh_token 헤더, 페이로드, 서명(HS256)을 포함한 JWT 토큰을 저장한다.
user_id Account Table에 id값을 저장한다.

 

 

핵심 로직

세션

최초 접속시 세션 저장 및 Dyanmo DB Table에 connect값 변경

  @Override
  public void afterConnectionEstablished(WebSocketSession session) throws Exception {
    // Principal 대신에 attributes에서 username 가져오기
    String username = (String) session.getAttributes().get("username");
    System.out.println("afterConnectionEstablished: username: " + username);
       Connect_E connectE = connectService.loadByEmail(username);

    if (connectE == null) {
      connectE = new Connect_E();
      connectE.setConnect("1");
      connectE.setMatching(0);
      connectE.setEmail(username);
      connectE.setSocket(session.getId());
      connectService.save(connectE);
    } else {
      connectE.setConnect("1");
      connectE.setMatching(0);
      connectE.setSocket(session.getId());
      connectService.updateConnect(connectE);
    }

    sessions.add(session);
    socketMap.put(session.getId(), session);
    System.out.println("WebSocket connection Open. Session ID: " + session.getId() + " // Username: " + username);
  }

 

 

렌주룰 확인 및 돌 카운트 로직

public Map<String, String> countingLock1(int countBean, int left, int leftDiagonal, int up, int upRightDiagonal, int row, int col, int wayCount, String[] point, String[][] table, String color) {
    switch (wayCount) {
      case 0:
        row--;
        if (isInBounds(table, row, col) && checkLockThere(table, row, col, color)) {
          left++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && countBean < 1 && !checkLockThere(table, row, col, "White")) {
          countBean++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && checkLockThere(table, row, col, "White")) {
          countBean = 0;
          left = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 1, point, table, color);
        } else {
          countBean = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 4, point, table, color);
        }

      case 1:
        row--;
        col--;
        if (isInBounds(table, row, col) && checkLockThere(table, row, col, color)) {
          leftDiagonal++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && countBean < 1 && !checkLockThere(table, row, col, "White")) {
          countBean++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && checkLockThere(table, row, col, "White")) {
          countBean = 0;
          leftDiagonal = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 2, point, table, color);
        } else {
          countBean = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 5, point, table, color);
        }
      case 2:
        col--;
        if (isInBounds(table, row, col) && checkLockThere(table, row, col, color)) {
          up++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && countBean < 1 && !checkLockThere(table, row, col, "White")) {
          countBean++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && checkLockThere(table, row, col, "White")) {
          countBean = 0;
          up = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 3, point, table, color);
        } else {
          countBean = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 6, point, table, color);
        }
      case 3:
        col--;
        row++;
        if (isInBounds(table, row, col) && checkLockThere(table, row, col, color)) {
          upRightDiagonal++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && countBean < 1 && !checkLockThere(table, row, col, "White")) {
          countBean++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && checkLockThere(table, row, col, "White")) {
          upRightDiagonal = 0;
          Map<String, String> result = new HashMap<>();
          result.put("left", Integer.toString(left));
          result.put("leftDiagonal", Integer.toString(leftDiagonal));
          result.put("up", Integer.toString(up));
          result.put("upRightDiagonal", Integer.toString(upRightDiagonal));
          return result;
        } else {
          countBean = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 7, point, table, color);
        }
      case 4:
        row++;
        if (isInBounds(table, row, col) && checkLockThere(table, row, col, color)) {
          left++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && countBean < 1 && !checkLockThere(table, row, col, "White")) {
          countBean++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && checkLockThere(table, row, col, "White")) {
          left = 0;
          countBean = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 1, point, table, color);
        } else {
          countBean = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 1, point, table, color);
        }
      case 5:
        row++;
        col++;
        if (isInBounds(table, row, col) && checkLockThere(table, row, col, color)) {
          leftDiagonal++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && countBean < 1 && !checkLockThere(table, row, col, "White")) {
          countBean++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && checkLockThere(table, row, col, "White")) {
          leftDiagonal = 0;
          countBean = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 2, point, table, color);
        } else {
          countBean = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 2, point, table, color);
        }
      case 6:
        col++;
        if (isInBounds(table, row, col) && checkLockThere(table, row, col, color)) {
          up++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && countBean < 1 && !checkLockThere(table, row, col, "White")) {
          countBean++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && checkLockThere(table, row, col, "White")) {
          up = 0;
          countBean = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 3, point, table, color);
        } else {
          countBean = 0;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, Integer.parseInt(point[0]), Integer.parseInt(point[1]), 3, point, table, color);
        }
      case 7:
        row--;
        col++;
        if (isInBounds(table, row, col) && checkLockThere(table, row, col, color)) {
          upRightDiagonal++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && countBean < 1 && !checkLockThere(table, row, col, "White")) {
          countBean++;
          return countingLock1(countBean, left, leftDiagonal, up, upRightDiagonal, row, col, wayCount, point, table, color);
        } else if (isInBounds(table, row, col) && checkLockThere(table, row, col, "White")) {
          upRightDiagonal = 0;
          Map<String, String> result = new HashMap<>();
          result.put("left", Integer.toString(left));
          result.put("leftDiagonal", Integer.toString(leftDiagonal));
          result.put("up", Integer.toString(up));
          result.put("upRightDiagonal", Integer.toString(upRightDiagonal));
          return result;
        } else {
          Map<String, String> result = new HashMap<>();
          result.put("left", Integer.toString(left));
          result.put("leftDiagonal", Integer.toString(leftDiagonal));
          result.put("up", Integer.toString(up));
          result.put("upRightDiagonal", Integer.toString(upRightDiagonal));
          return result;
        }
    }
    return null;
  }

사용 예시

 public void board3x3Check(String[][] table, String[] point, Map<String, String> map, Map<String, Object> msg) {
    int point1 = Integer.parseInt(point[0]);
    int point2 = Integer.parseInt(point[1]);
    int rowM = Math.max(point1 - 6, 0);
    int rowP = Math.min(point1 + 6, table.length - 1);
    int colM = Math.max(point2 - 6, 0);
    int colP = Math.min(point2 + 6, table[0].length - 1);
    int key = 0;
    int key1 = 0;
    for (int row = rowM; row <= rowP; row++) {
      for (int col = colM; col <= colP; col++) {
        if ("0".equals(table[row][col]) || "3".equals(table[row][col])) {
          int x33Count = 0;
          boolean x66Count = false;
          Map<String, String> lockCount_2S = countingLock1(0, 0, 0, 0, 0, row, col, 0, new String[]{Integer.toString(row), Integer.toString(col)}, table, "Black");
          Map<String, String> lockCount_6S = countingLock(1,1,1,1,row, col,0,new String[]{Integer.toString(row), Integer.toString(col)},table,"Black");

          if ("2".equals(lockCount_2S.get("left"))) {
            System.out.println("left2");
            x33Count++;
          }
          if ("2".equals(lockCount_2S.get("leftDiagonal"))) {
            System.out.println("leftDiagonal2");
            x33Count++;
          }
          if ("2".equals(lockCount_2S.get("up"))) {
            System.out.println("up2");
            x33Count++;
          }
          if ("2".equals(lockCount_2S.get("upRightDiagonal"))) {
            System.out.println("upRightDiagonal2");
            x33Count++;
          }

          if (Integer.parseInt(lockCount_6S.get("left")) >= 6 ||
              Integer.parseInt(lockCount_6S.get("leftDiagonal")) >=6 ||
              Integer.parseInt(lockCount_6S.get("up")) >= 6 ||
              Integer.parseInt(lockCount_6S.get("upRightDiagonal")) >= 6) {

            map.put("TthreeRow" + key, Integer.toString(row));
            map.put("TthreeCol" + key, Integer.toString(col));
            msg.put("TthreeRow" + key, Integer.toString(row));
            msg.put("TthreeCol" + key, Integer.toString(col));
            x66Count = true;
          }



          if (x33Count >= 2) {
            table[row][col] = "3";
            map.put("TthreeRow" + key, Integer.toString(row));
            map.put("TthreeCol" + key, Integer.toString(col));
            msg.put("TthreeRow" + key, Integer.toString(row));
            msg.put("TthreeCol" + key, Integer.toString(col));
            key++;
            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@33입니다.");
          } else if ("3".equals(table[row][col]) && !x66Count) {
            table[row][col] = "0";
            map.put("DTthreeRow" + key1, Integer.toString(row));
            map.put("DTthreeCol" + key1, Integer.toString(col));
            msg.put("DTthreeRow" + key1, Integer.toString(row));
            msg.put("DTthreeCol" + key1, Integer.toString(col));
            key1++;
            System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@33이 아니게 됐습니다..");
          }
        } else if ("1".equals(table[row][col])) {
        }
      }
    }
  }

 

승패시 상대방에게 승패 여부 전송

  public void UserDefeatAddOrSocketSend(String userName) throws JsonProcessingException {
    Map<String, String> msg = new HashMap<>();
    Connect_E connectE =  connectService.loadByEmail(userName);
    User_E userE = userDetailService.loadUserByUsername(userName);
    userE.setDefeat(userE.getDefeat() + 1);
    connectE.setConnect("1");
    connectService.updateConnect(connectE);
    userDetailService.update(userE);
    msg.put("type", "deFeat");
    webSocketHandler.sendMessageToTarget(objectMapper.writeValueAsString(msg),connectE.getSocket());
    gameService.findById(connectE.getPosition())
        .flatMap(data -> gameService.delete(data))
        .subscribe();
  }

  public void UserVictoryAddOrSocketSend(String userName) throws JsonProcessingException {
    Map<String, String> msg = new HashMap<>();
    Connect_E connectE =  connectService.loadByEmail(userName);
    connectE.setConnect("1");
    connectService.updateConnect(connectE);
    User_E userE = userDetailService.loadUserByUsername(userName);
    userE.setVictory(userE.getVictory() + 1);
    userDetailService.update(userE);
    msg.put("type", "vicTory");
    webSocketHandler.sendMessageToTarget(objectMapper.writeValueAsString(msg),connectE.getSocket());
  }

 

채팅 전송 로직

  @PostMapping("/api/chat")
  public Mono<Chat_E> sendMsg(@RequestBody Map<String, Object> chatE, Principal principal) {
    Chat_E chat_e = new Chat_E();
    User_E userE = userDetailService.loadUserByUsername(principal.getName());
    Flux<Game_E> gameUser1 = mongDBGameRepository.findByUser1(principal.getName());
    Flux<Game_E> gameUser2 = mongDBGameRepository.findByUser2(principal.getName());

    chat_e.setMsg((String) chatE.get("msg"));
    chat_e.setCreateAt(LocalDateTime.now());
    if(userE.getSubnickname() != null){
      chat_e.setSender(userE.getSubnickname());
    }else{
      chat_e.setSender(principal.getName());
    }
    Mono<Void> gameUser1Processing = gameUser1.collectList()
        .flatMap(list -> {
          if (list.isEmpty()) {
            return Mono.empty();
          } else {
            return gameUser1.flatMap(game -> {
              String userSocket = dynamoConnectRepository.findByEmail(game.getUser2()).getSocket();
              chat_e.setReceiver(game.getUser2());

              try {
                Map<String, Object> chatMap = objectMapper.convertValue(chat_e, Map.class);
                chatMap.put("type", "msg");
                String jsonMessage = objectMapper.writeValueAsString(chatMap);
                webSocketHandler.sendMessageToTarget(jsonMessage, userSocket);
              } catch (JsonProcessingException e) {
                return Mono.error(e);
              }
              return Mono.empty();
            }).then();
          }
        });

    Mono<Void> gameUser2Processing = gameUser2.collectList()
        .flatMap(list -> {
          if (list.isEmpty()) {
            return Mono.empty();
          } else {
            return gameUser2.flatMap(game -> {
              String userSocket = dynamoConnectRepository.findByEmail(game.getUser1()).getSocket();
              chat_e.setReceiver(game.getUser1());
              try {
                Map<String, Object> chatMap = objectMapper.convertValue(chat_e, Map.class);
                chatMap.put("type", "msg");
                String jsonMessage = objectMapper.writeValueAsString(chatMap);
                webSocketHandler.sendMessageToTarget(jsonMessage, userSocket);
              } catch (JsonProcessingException e) {
                return Mono.error(e);
              }
              return Mono.empty();
            }).then();
          }
        });

    return Mono.when(gameUser1Processing, gameUser2Processing)
        .then(Mono.defer(() -> {
          return chatService.saveChat(chat_e);
        }));
  }

 

매칭중인 다른 유저 찾는 로직


  @GetMapping("/api/matchingfind")
  public List<Connect_E> matchingUserFind(Principal principal) {

    String connect = connectService.loadByEmail(principal.getName()).getConnect();

    if (!Objects.equals(connect.trim(), "2")) {
      return null;
    }

    List<Connect_E> connectEs = connectService.loadByConnect("2");
    Connect_E opconnectE = new Connect_E();
    Connect_E meconnectE = new Connect_E();
    List<Connect_E> connect_es = new ArrayList<>();
    opconnectE.setMatching(9999999);

    for (Connect_E connectE1 : connectEs) {
      if (opconnectE.getMatching() > connectE1.getMatching() && !Objects.equals(principal.getName(), connectE1.getEmail())) {
        opconnectE = connectE1;
      }
      if (Objects.equals(principal.getName(), connectE1.getEmail())) {
        meconnectE = connectE1;
      }
    }
    connect_es.add(meconnectE);
    connect_es.add(opconnectE);

    return connect_es;
  }

 

게임 생성 로직

 @PostMapping("/api/firstgamesave")
  public Mono<Game_E> firstgamesave(@RequestBody Map<String, Object> requestData, Principal principal) {
    Connect_E connectE1 = connectService.loadByEmail(principal.getName());
    Map<String, Object> opconnectE = (Map<String, Object>) requestData.get("opconnectE");
  Connect_E connectE2 = connectService.loadByEmail((String)opconnectE.get("email"));
    Game_E gameE = new Game_E();
    if ((Integer) connectE1.getMatching() < (Integer) opconnectE.get("matching")) {
      gameE.setUser1(connectE1.getEmail());
      gameE.setUser2(opconnectE.get("email").toString());
    } else {
      return null;
    }
    String gameID = LocalDateTime.now().toString() + connectE1.getEmail() + connectE2.getEmail();
    gameE.setTurn("0");
    gameE.setTime("15");
    gameE.setId(gameID);
    gameE.setTable(getTable19n19());
    gameE.setTableLog(new ArrayList<>());
    connectE1.setPosition(gameID);
    connectE1.setConnect("3");
    connectE2.setPosition(gameID);
    connectE2.setConnect("3");

    connectService.updateConnect(connectE1);
    connectService.updateConnect(connectE2);
    return gameService.save(gameE);
  }

 

게임중인지 확인하는 로직


  @GetMapping("/api/gamefind")
  public Mono<Game_E> gameFindByUser(Principal principal) {
    System.out.println("gamefind");
    // 이메일로 Connect_E 객체 로드
    Connect_E connectE = connectService.loadByEmail(principal.getName());
    return gameService.findById(connectE.getPosition());
  }

 

 

다이어그램

 

배포 다이어그램

 

 

 

시퀀스 다이어그램

 

 

 

클래스 다이어그램

링크

github: https://github.com/asdd2557/yProject

Project URI: https://ok5pj.com/login

이상입니다.

감사합니다.