오늘의 목표

더보기

✔️ 프로그래머스 코테 문제 풀기

✔️ 개인 과제


⏱️ 오늘의 일정

프로그래머스 코테 문제 풀기

개인 과제


📜 프로그래머스 코테 문제 풀기

 

이진 변환 반복하기

https://github.com/YamSaeng/AlgorithmCodingTest/tree/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/2/70129.%E2%80%85%EC%9D%B4%EC%A7%84%E2%80%85%EB%B3%80%ED%99%98%E2%80%85%EB%B0%98%EB%B3%B5%ED%95%98%EA%B8%B0

 

AlgorithmCodingTest/프로그래머스/2/70129. 이진 변환 반복하기 at main · YamSaeng/AlgorithmCodingTest

This is an auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - YamSaeng/AlgorithmCodingTest

github.com

 

function solution(s) {
    var answer = [0, 0];

    while (s.length > 1) {
        answer[0]++;

        for (let i = 0; i < s.length; i++) {
            if (s[i] == '0') {
                answer[1]++;
            }
        }

        s = s.split("0").join("");

        let tempSArray = [];
        let tempsLength = s.length;

        let quotient = 0;
        let remain = 0;
        while (true) {
            quotient = Math.floor(tempsLength / 2);
            remain = tempsLength - quotient * 2;

            tempsLength = quotient;

            tempSArray.push(remain);

            if (quotient == 0) {
                break;
            }
        }

        s = tempSArray.reverse().join("");        
    }

    return answer;
}

 

문제 설명대로 구현하니 다행히도 쉽게 해결할 수 있었다.

 

📜 개인과제

 

개인과제를 진행했다.

 

https://dinorunner.com/ko/

 

크롬 다이노 게임 온라인

T-Rex Dinosaur (Dinosaur Google) - 인터넷이 없을 때 Chrome 브라우저에 숨겨진 게임의 복제본. 시작하려면 스페이스바를 누르십시오. 스페이스바 또는 위쪽 화살표와 아래쪽 화살표(↓)를 사용하여 공룡

dinorunner.com

 

 

앞서 언급한대로 크롬 다이노 게임에서 다른 움직임을 줬다.

우선은 좌, 우 화살표로 공룡을 직접 움직일 수 있도록 수정했다.

if (event.code === "ArrowLeft") {
    this.speed = -PLAYER_SPEED;
}

if (event.code === "ArrowRight") {
    this.speed = PLAYER_SPEED;
}

 

keydown 이벤트에서 왼쪽 화살표를 입력하면, -speed 값을 주고,

keydown 이벤트에서 오른쪽 화살표를 입력하면, speed 값을 줘서 공룡 캐릭터를 움직인다.

 

 

if (event.code === "KeyA") {
   if (this.fireAttackTimer <= 0 && this.fireCount > 0) {
        let game = Game.GetInstance();
        if (game != null) {
           this.fireCount--;
           game.jobQue.push(new Job(JOB_TYPE_CREATE_OBJECT_FIRE,
              OBJECT_TYPE_FIRE, this.x, this.y));

           this.fireAttackTimer = FIRE_ATTACK_TIMER;
        }
    }
}

 

A 키를 누르면 공룡 입에서 불꽃을 발사하도록 코드를 수정했다.

불꽃은 일정 개수를 가지고, R키를 눌러 재장전할 수 있다.

 

불꽃은 다가오는 선인장을 제거할 수 있다.

지금은 재장전할때, 바로바로 되는데 재장전시간을 둬서 선인장을 바로 없앨 수는 없도록 하긴 해야겠다.

 

https://github.com/YamSaeng/DinoRunner

 

GitHub - YamSaeng/DinoRunner

Contribute to YamSaeng/DinoRunner development by creating an account on GitHub.

github.com

 

오늘의 목표

더보기

✔️ 모의 면접

✔️ 개인 과제 


⏱️ 오늘의 일정

모의 면접개인 과제챌린지반 수업


📜 모의 면접

 

어제 언급한대로 오늘 모의 면접을 봤다.

총 3가지의 질문을 받았다.

 

1. IP의 개념과 주소체계, 서브넷에 관한 설명

2. 브라우저에 도메인을 입력하면 발생하는 일련의 과정 설명

3. 라우터, 라우팅과 라우팅 프로토콜 RIP, BGP의 대한 설명

 

면접 시간 전까지 암기를 하고 면접에 들어갔는데, 

그게 독이 된것 같다. 머리 속에 입력한 내용을 장황하게 설명하느라 튜터님이 강조한 '대화'를 하지 못했다.

피드백도 알고 있는 지식을 얘기한다기보다는 암기를 해서 설명하는 점이 강하고,

암기를 해서 설명하다보니 모든 지식을 끌어모아서 장황하게 설명하는 점이 좋지 않았다라고 피드백을 받았다.

 

면접은 '대화'라는 점을 강조 받았듯이 어떤 개념을 간단하게 설명을하고,

추가로 질문을 받으며 말 그대로 대화를 해 나가는 것이 중요하다라는 것을 깨달았다.

 

이번 모의 면접이 마지막은 아니고, 2번 정도 더 모의 면접 시간을 가질 것이라고 공지를 받았다.

다음 모의 면접에서는 위에서 받은 피드백 내용을 기억해두고 명심해서 임해야겠다.

 

 

📜 개인 과제

 

어제 제공 받은 스켈레톤 코드의 구조를 참고해서 다시 구성을 해야겠다.

 

https://github.com/YamSaeng/DinoRunner

 

GitHub - YamSaeng/DinoRunner

Contribute to YamSaeng/DinoRunner development by creating an account on GitHub.

github.com

 

📜 스탠다드반, 챌린지반 수업

 

스탠다드반에서 DNS 서버에 대해 배우고,

챌린지반에서는 데이터베이스 모델링에 관한 수업을 들었다.

오늘의 목표

더보기

✔️ 프로그래머스 코테 문제 풀이

✔️ 면접 특강

✔️ Node.js 게임서버 개발 강의 듣기


⏱️ 오늘의 일정

프로그래머스 코테 문제 풀이

면접 특강

Node.js 게임서버 개발 강의 듣기


📜 프로그래머스 코테 문제 풀이

 

피보나치 수

https://github.com/YamSaeng/AlgorithmCodingTest/tree/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/2/12945.%E2%80%85%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98%E2%80%85%EC%88%98

 

AlgorithmCodingTest/프로그래머스/2/12945. 피보나치 수 at main · YamSaeng/AlgorithmCodingTest

This is an auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - YamSaeng/AlgorithmCodingTest

github.com

 

처음에 딱 보자마자 생각난 것이 재귀 함수라서, 바로 구현해봤다.

function Fibonacci(n) {
    if (n == 0) {
        return 0;
    }
    else if (n == 1) {
        return 1;
    }
    else {
        return (Fibonacci(n - 2) + Fibonacci(n - 1)) % 1234567;
    }
}

 

설렜던 기분과는 다르게.. 시간초과가 나는 바람에 다른 방법을 고민해봤다.

function solution(n) {      
    let answer = new Array(100001);
    
    answer[0] = 0;
    answer[1] = 1;

    for (let i = 2; i <= n; i++) {
        answer[i] = (answer[i - 2] + answer[i - 1]) % 1234567;
    }

    return answer[n];
}

 

미리 배열을 선언하고

[0] = 0

[1] = 1 

 

0과 1을 저장해두고 피보나치 공식에 맞게 배열을 채워나간후에, n번째 배열 값을 반환해준다.

 

 

📜 면접 특강

 

면접.. 면접에 대한 특강 시간을 가졌다. 

내일 16시에 모의 면접 시간이 30분간 잡혀있는데, 매우 많이 긴장된다.

면접에 나올 문제를 캠프에서 알려준게 있어서, 그 내용만큼은 암기하고 모의 면접 시간을 가져야겠다..

 

 

📜 Node.js 게임서버 개발 강의 듣기

 

Node.js 게임서버 개발 강의를 모두 수강했다.

이번 개인과제와 관련된 강의로

서버를 만들고, 클라를 만드는 강의다.

 

서버는 웹소켓을 이용해 만들고,클라는 

 

https://dinorunner.com/ko/

 

크롬 다이노 게임 온라인

T-Rex Dinosaur (Dinosaur Google) - 인터넷이 없을 때 Chrome 브라우저에 숨겨진 게임의 복제본. 시작하려면 스페이스바를 누르십시오. 스페이스바 또는 위쪽 화살표와 아래쪽 화살표(↓)를 사용하여 공룡

dinorunner.com

 

일명 크롬 다이노 게임이라고 불리는 게임코드를 제공받았다.

 

강의에서는 기본적인 틀을 제공해주고, 개인과제 조건에 맞게 서버와 클라를 다듬어 제출해야한다.

지금 드는 생각은 단조롭게 장애물을 점프해서 피하기보다는 좀 더 역동적인 움직임을 갖춰서 구현할 생각이다.

 

좀 더 자세한 내용은 내일 서버를 구현해보면서 생각해봐야겠다.

 

오늘의 목표

더보기

✔️ 새로운 팀 편성

✔️ 개인과제 발제

✔️ Node.js 심화 주차 수업 듣기


⏱️ 오늘의 일정

  • 개인 과제 발제
  • 새로운 팀 노션 작성
  • Node.js 심화 주차 수업 듣기

📜 개인 과제 발제

 

개인 과제 발제 시간이 있어서 참가했다.

이번 주차는 심화 숙련 주차로, 드디어 웹 소켓을 활용해 TCP로 통신하는 게임을 만드는 개인과제를 만들고,

그다음에 팀 프로젝트를 수행하는 주차라고 공지를 받았다.

 

10월 7일까지 캠프에서 제시한 과제를 수행해야하는데,

 

클라는 위 게임을 강의를 보면서 직접 구현해보고, 이에 대응하는 서버는 직접 짜는 방식이라고 설명을 받았다.

드디어 TCP로 작동하는 게임 주차가 시작되긴 했는데, Node.js 에서는 소켓 프로그래밍을 어떻게 구현할지 기대가 된다.

 

팀 프로젝트는 간단히 소개만 받았는데, 타워 디펜스라는 게임을 만들거라고 들었다.

 

📜 새로운 팀 편성

 

새로운 주차에 진입하면서 앞서 언급한대로 새로운 팀에 편성되었다.

총 6명이 배정되었는데, 한분이 입문숙련으로 빠져서, 5명이 되었다. 

아마도.. 중간에 한명이 들어올수도...?

 

 

📜 Node.js 심화 수업 듣기

새로운 주차에 진입하면서 강의도 새로 제공 받았다.

강의는 총 3가지로,

 Node.js 심화, 게임서버 개발, 컴퓨터 CS 수업으로 구성된다.

 

Node.js 심화부터 들을 생각인데, 내용을 살펴보니 주로 객체 프로그래밍에 대한 내용이였다.

지겹게 들은 객체 프로그래밍이지만, 이번에 한번더 강의를 들으면서 정리를 또 해야겠다.

 

2024.09.26 - [IT] - [IT] 객체 지향 프로그래밍 ( Object-Oriented Programming, OOP )

 

[IT] 객체 지향 프로그래밍 ( Object-Oriented Programming, OOP )

객체 지향 ( Object - Oriented )객체 ( Object )는 현실 세계의 물체나 개념을 소프트웨어 세계로 옮긴 것이다.예를 들면, '자동차'나 '사람'처럼 생각하면 된다. 여기서, 객체는 여러 속성과 행동(메서드

program-yam.tistory.com

 

 

 

오늘의 목표

더보기

✔️ 프로그래머스 코테 문제 풀이

✔️ 팀 프로젝트 최종 점검

✔️ 챌린지반 수업


⏱️ 오늘의 일정

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀이
10:00 ~ 17:00 - 팀 프로젝트 최종 점검

17:00 ~ 19:00 - SQL 문제 풀기

19:00 ~ 20:00 - 챌린지반 수업


📜 프로그래머스 코테 문제 풀이

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀이

 

둘만의 암호

https://github.com/YamSaeng/AlgorithmCodingTest/tree/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/1/155652.%E2%80%85%EB%91%98%EB%A7%8C%EC%9D%98%E2%80%85%EC%95%94%ED%98%B8

 

AlgorithmCodingTest/프로그래머스/1/155652. 둘만의 암호 at main · YamSaeng/AlgorithmCodingTest

This is an auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - YamSaeng/AlgorithmCodingTest

github.com

 

function solution(s, skip, index) {
    let answer = [];

    for (let i = 0; i < s.length; i++) {
        let c = s[i];
        for (let j = 0; j < index;) {
            c = String.fromCharCode(c.charCodeAt() + 1);

            // 다시 a로 바꿈
            if (c === String.fromCharCode('z'.charCodeAt() + 1)) {
                c = 'a';
            }

            let isSkip = false;

            for (let k = 0; k < skip.length; k++) {
                if (c === skip[k]) {
                    isSkip = true;
                    break;
                }
            }

            if (isSkip === false) {
                j++;
            }            
        }

        answer.push(c);
    }

    return answer.join('');
}

 

📜 팀 프로젝트 최종 점검

10:00 ~ 17:00 - 팀 프로젝트 최종 점검

 

팀 프로젝트를 최종으로 점검하고, ppt를 작성했다.

ppt를 작성하면서 트러블 슈팅에 대해 작성하게 되었는데, 

우리 팀의 트러블 슈팅은 총 2가지였다.

 

첫번째는 다음과 같다.

 

 

api/Rating/Play URL과 api/:target/Play URL을 동시에 사용할 경우,

api/:target/Play  가 상위에 선언이 되어 있고,

api/Rating/Play  가 아래에 선언 되어 있을 때, api/Rating/Play로 접근하려고 하면

 

api/:target/Play로만 접근하게 된다. 이유는 :target에 Rating이라는 값이 들어가기 때문..

따라서 :target을 맨 뒤로 보내 문제를 해결했다.

 

두번째는 node-schedule의 사용 문제였다.

node-schdule로 일정 시간마다 호출해주려고 했으나,

예를 들어 5초마다 반복하려고 구성했는데, 실제로 5초가 될경우에만 호출이 되는 방식으로 스케줄링을 하는 패키지였다.

만약, 2초에 호출하면 7초에 호출이 되어야하는데, 5초, 10초, 5초 에 실행이 되는 방식..

setInterval을 통해 호출하는 방식으로 변경했다.

 

시연영상 링크

https://www.youtube.com/watch?v=GysPHl-4oYc

 

 

 

📜  SQL 문제 풀기

17:00 ~ 19:00 - SQL 문제 풀기

 

챌린지반 숙제인 SQL문제를 풀었다.

마땅한 문제집이 없어서 프로그래머스에 있는 문제중에서 풀었다.

 

-- 루시와 엘라 찾기
SELECT ANIMAL_ID, NAME, SEX_UPON_INTAKE
FROM ANIMAL_INS
WHERE NAME IN ('Lucy','Ella','Pickle','Rogan','Sabrina','Mitty')
ORDER BY ANIMAL_ID;

-- 이름이 없는 동물의 아이디
SELECT ANIMAL_ID
FROM ANIMAL_INS
WHERE NAME IS NULL
ORDER BY ANIMAL_ID

-- 조건에 맞는 회원수 구하기
SELECT COUNT(USER_ID) USERS
FROM USER_INFO
WHERE JOINED LIKE '2021%' AND AGE >= 20 AND AGE <= 29

-- 카테고리 별 상품 개수 구하기
SELECT ANIMAL_TYPE, COUNT(*) AS count
FROM ANIMAL_INS
GROUP BY ANIMAL_TYPE
ORDER BY ANIMAL_TYPE

 

오늘의 목표

더보기

✔️ 팀프로젝트 진행


⏱️ 오늘의 일정

9:00 ~ 21:00 팀프로젝트 진행


📜 팀프로젝트 진행

9:00 ~ 21:00 팀프로젝트 진행

 

팀프로젝트를 진행했다.

저번주 주말에 랭킹 조회를 구현했다.

 

랭킹 전체 조회

rankingRouter.get('/Ranking/AllCheck', async (req, res, next) => {
    // rankingScore 컬럼을 기준으로 내림차순한 전체 데이터 값들을 가져옴
    const currentRankings = await prisma.ranking.findMany({
        orderBy: {
            rankingScore: 'desc'
        }
    });
    
    return res.status(200).json({ ... currentRankings });
});

 

ranking Table에 들어있는 값을 전부 읽어온다.

rankingScore 값을 기준으로 내림차순해서 클라이언트에게 전달한다.

 

 

특정 유저 랭킹 조회

rankingRouter.get('/Ranking/SingleCheck/:userId', async (req, res, next) => {
    const { userId } = req.params;

    const userRanking = await prisma.ranking.findFirst({
        where: {
            userId: +userId
        }
    })

    return res.status(200).json({ userRanking });
});

 

조회하고자 하는 유저의 id값을 받아서 ranking table에 읽어온 후 클라이언트에게 전달한다.

 

DBRankingChangeScore ( 순위 변동 계산 )

export async function DBRankingChangeScore() {    
    const currentRankings = await prisma.ranking.findMany({
        orderBy: {
            rankingScore: 'desc'
        }
    });

    // 순서대로 순회하며 랭킹 설정
    let rank = 1;
    currentRankings.map((currentRanking) => {
        currentRanking.rank = rank;
        rank++;
    });


    // 순위 변동 계산
    for (let pRankKey in previousRankings) {
        const pRankData = previousRankings[pRankKey];
        for (let cRankKey in currentRankings) {
            const cRankData = currentRankings[cRankKey];
            if (pRankData.userId == cRankData.userId) {
                const rankChangeScore = pRankData.rank - cRankData.rank;

                cRankData.rankChangeScore = rankChangeScore;
                break;
            }
        }
    }

    const rankingTableUpdateFinish = async (tx) => {
        // ranking Table에 순위와 순위 변동을 기록
        await Promise.all(
            currentRankings.map(async (currentRanking) => {
                await tx.ranking.update({
                    where: {
                        userId: currentRanking.userId
                    },
                    data: {
                        rank: currentRanking.rank,
                        rankChangeScore: currentRanking.rankChangeScore
                    }
                })
            })
        );
    };
    await executeTransaction(rankingTableUpdateFinish);

    // 현재 시점 순위 변동 저장
    previousRankings = currentRankings;
}

 

이전 ranking table의 값과 DBRankingChangeScore을 호출했을 때의 ranking table의 값을 비교해서 순위변동을 계산하고,

DB에 저장한다.

 

app.js

DBRankingChangeScore();

setInterval(() => {
  DBRankingChangeScore();
}, process.env.RANKING_RANK_CHANGE_TIME);

 

서버가 켜질때, 우선 DBRankingChangeScore()를 호출해서 랭킹 데이터를 최신화 시켜준다.

서버에서 정한 일정 시간마다 DBRankingChangeScore()를 호출해서 순위를 계산해준다.

 

 

프론트엔드는 아래와 같이 구성했다.

로그인 화면

 

회원가입 화면

 

게임 화면

오늘의 목표

더보기

✔️ 팀 프로젝트

 


⏱️ 오늘의 일정

9:00 ~ 21:00 - 팀 프로젝트 


📜 팀 프로젝트

9:00 ~ 21:00 - 팀 프로젝트 

 

팀 프로젝트를 진행했다. 

테스트용으로 만든 회원가입과 로그인 부분을 내가 생각하는 로직으로 바꿧다.

 

우선 로그인 전용으로 인증을 검사하는 미들웨어를 만들었다.

export default async function (req, res, next) {
    const { authorization } = req.headers;
    const { id } = req.body;

    do {
        // AccessToken이 없음
        if (authorization === undefined || authorization.length == 0) {
            req.authLoginState = process.env.LOGIN_AUTH_FAIL_AUTHORIZATION_NOT_FOUND;
        }
        else {
            // AccessToken이 있음
            const [c2sTokenType, c2sAccessToken] = authorization.split(' ');
            if (c2sTokenType !== process.env.TOKEN_TYPE_CHECK) {
                req.authLoginState = process.env.LOGIN_AUTH_FAIL_TOKEN_TYPE_NOT_MATCH;
            }        

            const decodedToken = jwt.decode(c2sAccessToken);            
            if(decodedToken.id !== id)
            {
                req.authLoginState = process.env.LOGIN_AUTH_FAIL_DIFFERENT_ACCESS_TOKEN;
                break;
            }

            // 토큰 검증
            const verifyToken = ValidateToken(c2sAccessToken, process.env.ACCESS_TOKEN_SECRET_KEY);                        
            if (!verifyToken) {
                req.authLoginState = process.env.LOGIN_AUTH_FAIL_TOKEN_EXPIRED;
                break;
            }            

            req.authLoginState = process.env.LOGIN_AUTH_SUCCESS;
        }
    } while (0);

    next();
}

 

위 코드에서는 로그인 시도한 유저가 AccessToken을 가지고 있는지 확인한다.

AccessToken이 없거나 빈 내용을 입력하면 req.authLoginState에 LOGIN_AUTH_FAIL_AUTHORIZATION_NOT_FOUND를 저장한다.

 

AccessToken이 있는데 TokenType이 다를 경우에 LOGIN_AUTH_FAIL_TOKEN_TYPE_NOT_MATCH를 저장한다.

AccessToken이 있는데 다른 유저의 AccessToken을 가지고 왔을 경우에는 LOGIN_AUTH_FAIL_DIFFERENT_ACCESS_TOEKN을 저장한다.

 

AccessToken이 있는데 만료됐거나, 값이 다를 경우네는 LOGIN_AUTH_FAIL_TOKEN_EXPIRED를 저장한다.

 

그 외 경우에는 인증 성공으로 판단해서 LOGIN_AUTH_SUCCESS를 저장한다.

 

이후 메인 Login Router로 진행한다.

 

// 로그인

usersRouter.post('/Sign-In', loginAuthMiddleware, async (req, res, next) => {
    // 아이디, 비밀번호 가져오기
    const { id, password } = req.body;

    // userDB에 아이디가 있는지 확인
    const user = await prisma.users.findFirst({
        where: {
            id: id
        }
    });

    // 아이디 검사
    if (!user) {
        return res.status(404).json({ message: `${id}은 존재하지 않는 아이디 입니다.` });
    }

    // 비밀번호 검사
    if (!(await bcrypt.compare(password, user.password))) {
        return res.status(404).json({ message: `비밀번호가 일치하지 않습니다.` });
    }

    // RefreshTokens 테이블에 RefreshToken이 있는지 확인
    const refreshTokenExist = await prisma.refreshTokens.findFirst({
        where: {
            id: id
        }
    });

    switch (req.authLoginState) {
        case process.env.LOGIN_AUTH_FAIL_AUTHORIZATION_NOT_FOUND:
            // AccessToken이 없음
            FirstLogin(id, refreshTokenExist, res);
            break;
        case process.env.LOGIN_AUTH_FAIL_TOKEN_TYPE_NOT_MATCH:
            // AccessToken을 가지고 있지만 토큰 타입이 일치하지 않음
            return res.status(404).json({ message: '토큰 타입이 일치하지 않습니다.' });
        case process.env.LOGIN_AUTH_FAIL_TOKEN_EXPIRED:
            // AccessToken이 만료됨
            AccessTokenExpired(id, refreshTokenExist, res);
            break;
        case process.env.LOGIN_AUTH_FAIL_DIFFERENT_ACCESS_TOKEN:
            DifferentAccessToken(refreshTokenExist, res);
            break;
        case process.env.LOGIN_AUTH_SUCCESS:
            // 로그인 성공
            return res.status(200).json({ message: `${id}로 로그인에 성공했습니다.` });
    }
})

 

로그인에서는 기본적으로 아이디와 비밀번호를 검사하고,

RefreshToken Table에 RefreshToken이 있는지 찾은 후,

로그인 인증에서 저장한 authLoginState값에 따라 다른 로직으로 처리한다.

 

//------------------------------------------------------
// 처음 로그인
//------------------------------------------------------
async function FirstLogin(id, refreshTokenDBData, res) {
    if (refreshTokenDBData === null) {
        // RefreshToken이 Table에 없음
        // 처음 로그인 대상으로 해석
        // JWT로 AccessToken 생성
        const s2cAccessToken = CreateAccessToken(id);
        // JWT로 RefreshToken 생성
        const s2cRefreshToken = CreateRefreshToken(id);

        // 응답 헤더에 accessToken 기록
        res.header("authorization", s2cAccessToken);

        const nowTime = MakeTime(false);
        const expiredAtTokenTime = MakeTime(false, parseInt(process.env.REFRESH_TOKEN_EXPIRE_TIME_DB));

        // RefreshToken DB에 저장
        const refreshToken = await prisma.refreshTokens.create({
            data: {
                id: id,
                token: s2cRefreshToken,
                createdAt: nowTime,
                expiredAt: expiredAtTokenTime
            }
        });

        return res.status(200).json({ message: `${id}로 로그인에 성공했습니다.` });
    }
    else {        
        const s2cAccessToken = CreateAccessToken(id);
        res.header("authorization", s2cAccessToken);

        return res.status(200).json({ message: `${id}로 로그인에 성공했습니다.` });
    }
}

 

FirstLogin 에서는 로그인 시도한 대상이 처음으로 로그인한 대상인지를 판단한다.

만약 RefreshToken Table에서 RefreshToken을 찾지 못하면, 해당 유저를 처음 로그인한 대상으로 해석하고,

AccessToken과 RefreshToken을 발행하고, Refresh Token을 Table에 저장한다.

 

RefreshToken을 이미 가지고 있는 대상이면 AccessToken을 재발행해준다.

 

async function AccessTokenExpired(id, refreshTokenDBData, res) {
    if (refreshTokenDBData !== null) {
        // AccessToken을 가지고 있고, AccessToken이 만료됨
        // refreshToken을 DB에 가지고 있음

        // AccessToken을 재발급
        // RefreshToken이 만료되었는지 확인
        const refreshTokenCheck = ValidateToken(refreshTokenDBData, process.env.REFRESH_TOKEN_SECRET_KEY);
        if (!refreshTokenCheck) {
            // RefreshDB createdAt, expiredAt 컬럼 사용 용도 : 
            // JWT 없이 Refresh Token의 Expire 시간을 제어할 수 있는 용도로 사용
            // 예) JWT로 3시간 후 만료로 발급했으나 모종의 이유로 1시간 후 만료로 바꿔야하는 경우,
            //     JWT는 수정을 할 수 없기 때문에 DB에 있는 시간값으로 판정한다.

            // 현재 시간과 db에 저장되어 있는 시간 차를 구함
            const dbTimeDifferenceSecond = TimeDifference(refreshTokenDBData.expiredAt, "second");

            // 상수값으로 설정해둔 Refresh Token 만료 시간을 지나면 
            if (dbTimeDifferenceSecond >= parseInt(process.env.REFRESH_TOKEN_EXPIRE_DB_CHECK_TIME)) {
                // RefreshToken을 재발급
                const s2cRefreshToken = CreateRefreshToken(id);

                // 현재 시간을 구함
                const nowTime = MakeTime(false);
                // Refresh Token 만료 시간을 구함
                const expiredTime = MakeTime(false, parseInt(process.env.REFRESH_TOKEN_EXPIRE_TIME_DB));

                // Refresh Token을 업데이트
                await prisma.refreshTokens.update({
                    where: { id: id },
                    data: {
                        token: s2cRefreshToken,
                        createdAt: nowTime,
                        expiredAt: expiredTime
                    }
                });
            }
        }

        const s2cAccessToken = CreateAccessToken(id);
        res.header("authorization", s2cAccessToken);

        return res.status(200).json({ message: `AccessToken이 재발급 되었습니다. ${id}로 로그인에 성공했습니다. ` });
    }
    else {
        // AccessToken을 가지고 있고, AccessToken이 만료됨
        // refreshToken을 DB에 가지고 있지 않음
        // 불량 유저로 판정 ( 서버에 문제가 생겨 refreshToken을 기록 못할경우가 있어서 불량 유저로 판정하기엔 무리)      
        // return res.status(404).json({ message: `비정상 로그인 시도` });

        return res.status(404).json({ message: `refreshToken DB Empty` });
    }
}

 

AccessTokenExpired에서는 AccessToken을 재발행 해준다.

만약 RefreshToken을 DB에 가지고 있지 않을 경우에는 불량유저로 판단해 로그인 시켜주지 않는다.

( 이 부분은 실제 이런경우가 있는지 보고 싶어서 따로 처리한 것이고, 원래는 서버에 문제가 생겨 RefreshToken을 DB에 기록하지 못할 경우도 생길 수 있기 때문에 AccessToken을 재발행 해줘야한다고 튜터님에게 답을 얻었다. )

 

RefreshToken을 DB에 가지고 있으면, RefreshToken이 만료됐는지 JWT로 먼저 확인해주고,

추가로 DB에 있는 시간값을 기준으로 만료됐는지 확인 한 후,

( DB에 있는 시간값을 기준으로 만료됐는지 추가로 확인하는 이유는 다음과 같다. JWT로 발행을하고 나서 모종의 이유로 만료시간을 수정을 해야할 경우, JWT의 만료시간을 수정할 수 없어서 DB값을 기준으로 최종적으로 판단해준다. )

만료됐으면 RefreshToken을 재발행한다.

 

🌙 하루를 마치며

오늘 생각했던 회원가입과 로그인 부분을 구현할 수 있어서 기분이 좋았다.

로그인 부분에서 불량유저를 판단하는 부분이 애매해 튜터님에게 가서 물어보니,

다른 관점으로 대답을 해주셔서 판단에 많은 도움이 되었다.

 

주말에는 추가로 맡은 랭킹 부분을 완료해 월요일에 테스트 할 수 있도록 해야겠다.

오늘의 목표

더보기

✔️ 프로그래머스 코테 문제 풀기

✔️ 팀 프로젝트 ( 회원가입, 로그인 )


⏱️ 오늘의 일정

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기
10:00 ~ 11:00 - 스탠다드반 CS 강의

11:00 ~ 21:00 - 팀 프로젝트

19:00 ~ 20:00 - 챌린지반 수업 

 


📜 프로그래머스 코테 문제 풀기

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기

 

대충만든 자판

 

https://github.com/YamSaeng/AlgorithmCodingTest/tree/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/1/160586.%E2%80%85%EB%8C%80%EC%B6%A9%E2%80%85%EB%A7%8C%EB%93%A0%E2%80%85%EC%9E%90%ED%8C%90

 

AlgorithmCodingTest/프로그래머스/1/160586. 대충 만든 자판 at main · YamSaeng/AlgorithmCodingTest

This is an auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - YamSaeng/AlgorithmCodingTest

github.com

 

function solution(keymap, targets) {
    var answer = [];    
    for (let i = 0; i < targets.length; i++) {                
        answer.push(0);
        for (let j = 0; j < targets[i].length; j++) {
            const targetChar = targets[i][j];
            let minBtnClick = 1000;            
            let findChar = false;
            for (let k = 0; k < keymap.length; k++) {
                for (let l = 0; l < keymap[k].length; l++) {
                    if (keymap[k][l] == targetChar) {
                        if (l < minBtnClick) {
                            minBtnClick = l + 1;
                            findChar = true;
                        }
                    }
                }
            }
            if (findChar == false) {
                answer[i] = -1;
                break;
            }
            
            answer[i] += minBtnClick;
        }        
    }    
    return answer;
}

 

문제 설명에 있는 대로 반복문을 사용해 구현했다.

반복문이 많아서 시간초과가 날까 싶었는데, 초과하는 예시는 없었다.

targets 의 개수 만큼 answer에 0을 넣고 해당 값을 증가시켜주는 방식으로 구현했다.

 

 

📜 스탠다드반 CS 강의

9:00 ~ 10:00 - 스탠다드반 CS 강의

 

IP 주소와 클래스풀 주소 체계에 대한 내용과 서브넷 마스크를 배웠다.

스탠다드반 수업내용은 따로 정리해 포스팅하고 링크를 남겨야겠다.

 

 

📜 팀 프로젝트

11:00 ~ 21:00 - 팀 프로젝트

 

본격적인 팀 프로젝트 구현 첫 날을 맞았다.

우선 기초적인 회원가입과 로그인을 구현해 Push 했다.

 

회원가입 api

// 회원가입
usersRouter.post('/Sign-Up', async (req, res, next) => {
    const { name, nickname, id, password, confirmPassword } = req.body;
    const isExistUser = await prisma.users.findFirst({
        where: {
            id: id
        }
    });

    if (isExistUser) {
        return res.status(404).json({ message: `이미 존재하는 아이디입니다.` });
    }

    if (confirmPassword === undefined) {
        return res.status(404).json({ message: `비밀번호 확인을 입력하세요` });
    }

    if (password !== confirmPassword) {
        return res.status(404).json({ message: `비밀번호와 비밀번호 확인이 일치하지 않습니다.` });
    }

    const hashedPassword = await bcrypt.hash(password, 10);

    // 유저 생성해서 Users table에 저장
    const user = await prisma.users.create({
        data: {
            name: name,
            nickname: nickname,
            id: id,
            password: hashedPassword
        }
    });

    // 랭킹 생성해서 Ranking table에 저장
    const rank = await prisma.ranking.create({
        data: {
            userId: user.userId
        }
    });

    // 스쿼드 생성해서 Squad table에 저장
    const squad = await prisma.squad.create({
        data: {
            userId: user.userId
        }
    });

    return res
        .status(201)
        .json({ message: `${id}로 회원가입이 완료되었습니다.` });
});

 

회원가입은 위 코드에서 추가할 부분이 없어보이기는 한다.

아마 닉네임 중복 점검 정도 추가하지 않을까 싶다.

 

 

 

로그인 api

usersRouter.post('/Sign-In', async (req, res, next) => {
    // 아이디, 비밀번호 가져오기
    const { id, password } = req.body;
    // AccessToken이 있는지 확인
    const { authorization } = req.headers;

    // userDB에 아이디가 있는지 확인
    const user = await prisma.users.findFirst({
        where: {
            id: id
        }
    });

    // 아이디 검사
    if (!user) {
        return res.status(404).json({ message: `${id}은 존재하지 않는 아이디 입니다.` });
    }

    // 비밀번호 검사
    if (!(await bcrypt.compare(password, user.password))) {
        return res.status(404).json({ message: `비밀번호가 일치하지 않습니다.` });
    }

    // JWT로 AccessToken 생성
    const s2cAccessToken = CreateAccessToken(id);
    // JWT로 RefreshToken 생성
    const s2cRefreshToken = CreateRefreshToken(id);   

    // 응답 헤더에 accessToken 기록
    res.header("authorization", s2cAccessToken);

    return res.status(200).json({ message: `${id}로 로그인에 성공했습니다.` });
})

 

로그인은 기본적으로 jwt로 accesstoken을 발급하고, 헤더에 저장해 발급해주는 방법을 우선 사용했다.

테스트를 해야하기 때문에 인증은 간단하게!

 

로그인은 RefreshToken을 활용해 로직을 구현하려고 한다.RefreshToken을 DB에 저장해 관리한다.AccessToken이 만료되면, RefreshToken으로 AccessToken의 재발행 여부를 판단한다.

 

 

 

📜 챌린지반 수업

19:00 ~ 20:00 - 챌린지반 수업 

 

오늘 챌린지반 수업에서는 DB Query 중 집계함수와 그룹핑에 대한 내용을 배웠다.

추가로 숙제를 내줬는데 프로그래머스 SQL 문제 4개를 풀어오는 숙제다.

 

랜덤하게 챌린지반 수강생 중 3명을 골라 발표를 시킨다고 공지를 받았다.

SQL 문제를 풀고, TIL에 기록해야겠다.

오늘의 목표

더보기

✔️ 프로그래머스 코테 문제 풀기

✔️ 팀 프로젝트 과제 발제

✔️ 팀 회의


 

⏱️ 오늘의 일정

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기
10:00 ~ 11:00 - 팀 프로젝트 과제 발제

11:00 ~ 20:00 - 팀 회의


 

📜 프로그래머스 코테 문제 풀기

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기

 

문자열 나누기

 

https://github.com/YamSaeng/AlgorithmCodingTest/tree/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/1/140108.%E2%80%85%EB%AC%B8%EC%9E%90%EC%97%B4%E2%80%85%EB%82%98%EB%88%84%EA%B8%B0

 

AlgorithmCodingTest/프로그래머스/1/140108. 문자열 나누기 at main · YamSaeng/AlgorithmCodingTest

This is an auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - YamSaeng/AlgorithmCodingTest

github.com

 

문제를 한번에 이해를 못해서 여러번 읽고 이해한 후,

문제 설명에 맞게 코드를 짜니 쉽게 해결했다.

function solution(s) {
    let answer = 0;
    let isX = 0;
    let notX = 0;
    let x = "";

    for (let i = 0; i < s.length; i++) {
        if (x == "") {
            x = s[i];
            isX++;
            continue;
        }

        if (x == s[i]) {
            isX++;
        }            
        else {
            notX++;
        }            

        if (isX == notX) {
            answer++;
            isX = 0; notX = 0;
            x = "";
        }
    }

    if (isX != 0) {
        answer += 1;
    }        

    return answer;
}

 

 

📜 팀 과제 발제

10:00 ~ 11:00 - 팀 과제 발제

 

총 12일 간의 시간 기한으로 한 팀 과제가 발제되었다.

이번 팀 과제는 풋살 게임의 서버를 만드는 과제다.

 

 

필수 구현 기능은 다음과 같다.

 

1. 회원가입 / 로그인 기능

2. 캐시 구매 기능

3. 선수 데이터 준비

4. 선수 뽑기 기능

5. 나만의 팀 꾸리기 기능

6. 축구 게임 기능

 

도전 기능은 다음과 같다.

 

1. 승리 / 패배 시 게임 점수 조정 기능

2. 유저 랭킹 조회 기능

3. 점수 기반 자동 매치 메이킹 기능

4. 선수 강화 기능

 

제출은 9 / 25일 수 오전 12:00로 정해졌다.

 

 

📜  팀 회의

11:00 ~ 21:00 - 팀 회의

 

S.A를 작성해 21:00 까지 튜터님한테 피드백을 받으라는 공지가 있어서, 팀원들과 함께 S.A를 작성했다.

 

1. 와이어 프레임

 

회원가입 화면

 

로그인 화면

 

인벤토리 확인 및 뽑기 화면

 

대상 지정 게임

 

 

레이팅 게임

 

강화 화면

 

2. API 명세서

 

API 명세서를 팀원들끼리 작성하면서 서로 업무를 분담했다.

내가 맡은건 회원가입과 로그인, 그리고 전체 랭킹 조회, 로그를 맡았다.

 

 

3. 데이터 베이스 관계를 ERD DIAGRAM을 이용해 작성했다.

 

오늘의 목표

더보기

✔️ 스탠다드반 수업

✔️ 개인 과제 마무리

✔️ 데이터 베이스개념, 인증, 인가 특강

✔️ 챌린지반 수업


⏱️ 오늘의 일정

10:00 - 11:00 - 스탠다드 반 수업
11:00 ~ 12:00 - 개인 과제 마무리
 

12:00 ~ 13:00 - 개인 과제 다듬기

13:00 ~ 14:00 - 점심시간

14:00 ~ 16:00 - 데이터 베이스개념, 인증, 인가 특강

16:00 ~ 18:00 - 개인 과제 다듬기

18:00 ~ 19:00 - 저녁시간

19:00 ~ 20:00 - 챌린지반 수업20:00 ~ 21:00 - 팀원 코드 리뷰


📜 스탠다드 반 수업

10:00 - 11:00 - 스탠다드 반 수업

 

오늘 수업은 발제자분들의 발표로만 이루어졌다.

OSI 7계층 중 3계층인 네트워크 계층에 대한 주제로 발표가 시작되었다.

 

 

📜 개인 과제 마무리

11:00 - 12:00 - 개인과제 마무리

 

 

길고 길었던 개인과제를 마무리했다.

개인과제를 마무리하고 미처 구현하지 못했던 기능들도 구현해 추가로 제출했다.

 

아이템 구입할때 발견하지 못했던 부분을 발견해서 버그를 수정했다.

 

 

 

 // 캐릭터의 인벤토리를 찾는다.
 const chracterInventory = await prisma.inventory.findFirst({
     where: {
         characterId: character.characterId
     }
 });
 
 // 인벤토리에서 비어있는 공간을 찾는다.
 const emptyInventoryItem = await prisma.inventoryItem.findFirst({
     where: {
         inventoryId: chracterInventory.inventoryId,
         itemId : 0
     }
 });

 // 비어있는 공간에 아이템을 넣는다.
 await prisma.inventoryItem.update({
     where: {
         inventoryItemIndex: emptyInventoryItem.inventoryItemIndex,
         inventoryId : chracterInventory.inventoryId
     },
     data: {
         itemId: searchItem.itemId,
         inventoryItemCount: count
     }
 })

 

위 코드는 아이템을 구입하고 인벤토리에 넣을 때, 인벤토리에서 빈 공간을 찾은 후 해당 공간에 아이템을 넣는 코드다.

비어있는 공간에 아이템을 넣는 부분의 로직은 다음과 같다.

 

인벤토리 아이템을 업데이트 하는데, 인벤토리 아이템의 위치값이 위에서 찾은 비어있는 공간의 아이템의 위치값과 같고,해당 인벤토리 아이템이 캐릭터가 소유하고 있는 인벤토리일 경우,해당 인벤토리 아이템의 아이템 코드와 개수를 업데이트 한다.

 

이처럼 구성을하고 실행을 하니 에러가 났다.

 

 

에러 내용을 살펴보니 inventoryitemId를 활용하라는 의미의 버그였다.

생각해봐도 왜 굳이 저거를 써야할까..?

로우 쿼리 자체가 문제가 되는가 싶어서 MySQL Workbench에서 위 로직으로 쿼리를 짜서 실행해보니 문제 없이 실행됐다.

 

Prisma 자체의 문제인가 싶어서 튜터님에게 찾아가 문의를 하니, Prisma에서는 PK로 설정한 값을 활용해 where문을 작성해야한다는 말을 들었다.

 // 비어있는 공간을 찾는다.
 const emptyInventoryItem = await prisma.inventoryItem.findFirst({
     where: {
         inventoryId: chracterInventory.inventoryId,
         itemId: 0
     }
 });

// 비어있는 공간에 아이템을 넣는다.
await prisma.inventoryItem.update({
    where: {
        inventoryItemId: emptyInventoryItem.inventoryItemId                    
    },
    data: {
        itemId: searchItem.itemId,
        inventoryItemCount: count
    }
});

 

그래서 위처럼 수정을 했다.

 

인벤토리 아이템을 업데이트 하는데,

인벤토리 아이템의 ID 값이 비어있는 아이템의 ID 값이 같은 대상을 찾고,

해당 인벤토리 아이템의 아이템 코드와 개수를 업데이트한다.

 

이처럼 구성을하고 실행을 하니 깔끔하게 업데이트가 되었다.

위처럼 구성하는 것이 기본적인 방법이라고 튜터님에게서 조언을 받았다.

기본 ID 값을 활용해 업데이트 하는 방식으로 SQL 쿼리를 만들어야겠다.

 

📜 데이터베이스 개념, 인증 인가 특강

14:00 ~ 16:00 - 데이터 베이스개념, 인증, 인가 특강

 

앞서 노드 강의에서 배운 데이터베이스의 개념과 인증, 인가에 정리 차원에서 특강 수업이 있어서 들었다.

 

📜 챌린지반 수업

19:00 - 20:00 - 챌린지 반 수업

 

매주 화 목 진행하는 챌린지반 수업에 참여했다.

이번 강의에서는 RDBMS에 대한 개념을 주제로 해서 수업이 이루어졌다. 

중간 중간 쿼리에 대한 문제를 내주고 푸는 짧은 문제 풀이 시간도 있었다.

 

📜 팀원 코드 리뷰

20:00 - 21:00 - 팀원 코드 리뷰

 

20시 부터 팀원 코드 리뷰 시간이 있어서 참여했다.

다른 분들의 코드를 보면서 의아한 부분은 질문하고, 도움이 될만한 부분을 발견해 많은 도움이 되었다.

 

https://dbdiagram.io/home

 

dbdiagram.io - Database Relationship Diagrams Design Tool

 

dbdiagram.io

 

무료로 DB 다이어그램을 구성할 수 있는 사이트를 공유 받아서 앞으로 자주 이용해야겠다.

 

🌙 하루를 마치며

오늘로 개인과제가 마무리 되었다.

9월 13일 오전 10시에 팀 과제 발제가 있다고 공지를 받았다.

어떤 문제가 나올지 기대되고, 팀원 코드 리뷰를 하면서 팀 과제 분배를 어떻게 할지도 상의했다.

우선 지금 생각으로는 개인과제를 하며 발견한 router 단위로 분배를 하는법

아니면 어떤 큼직한 로직 담당으로 분배를 하는법 2가지 정도를 생각하고 의견을 나눴다.

 

최종으로는 팀 과제 발제를 듣고나서 적절하게 분배해 수행하는 것으로 이야기를 마루리햇다.

+ Recent posts