[React 프로젝트] 숫자야구 프로젝트 - MySQL

Hyuk·2023년 1월 28일
0

개요

해당 페이지에선 MySQL의 주요 기능별로 기록한다.

MySQL문을 작성하면서 DBeaver 라는 데이터베이스 관리도구를 이용해 편리하게 관리했다. 해당 페이지에서 나오는 이미지들은 DBeaver 라는 관리도구의 모습이다.

기능별 정리

회원가입

회원가입에 대한 SQL문을 작성하기 전에 알맞은 column과 함께 사용자 계정 관리 테이블을 생성해준다. 해당 프로젝트의 사용자 계정 관리 테이블은 다음과 같이 생성했다.

다음은 회원가입에 대한 MySQL 전체코드이다.

// index.js
app.post('/join', (request, response) => {
    const { id, password, nickname } = request.body

    connection.query(
      `select * from user where user_id='${id}'`,
      (error, results) => {
          if (results.length >= 1) {
              response.status(403).json({
                  code: 1,
                  message: '중복된 아이디'
              })
              return
          }
          connection.query(
              `SELECT * FROM user WHERE nickname='${nickname}'`,
              (error, results) => {
                  if (results.length >= 1) {
                      response.status(403).json({
                          code: 2,
                          message: '중복된 닉네임'
                      })
                      return
                  }
                  connection.query(
                      `INSERT INTO user (user_id, password, nickname) 
                      VALUES ("${id}", SHA2("${password}", 512), "${nickname}")`,
                      (error, results) => {
                          if (error) {
                              console.log(error)
                              response.status(403).json({
                                  message: '서비스 처리 불가'
                              })
                              return
                          }
                          response.status(200).json(results)
                      })
                }
            )
        }
    )
})

회원가입을 함에 있어서 SELECT를 이용해 중복된 아이디가 있는지, 중복된 닉네임이 있는지부터 확인을 하고 없다면 INSERT를 이용해 회원가입 테이블에 id, password, nickname 을 삽입해주는 방식이다.

다음은 클라이언트에서 axios를 이용해 백엔드와 연동하기 위한 코드이다.

// src/view/JoinMemberShip/index.js
const [profile, setProfile] = useState({
    id: '',
    password: '',
    repassword: '',
    nickname: ''
})

const onJoinMembershipClick = useCallback(() => {
    axios.post('http://localhost:65100/join', profile)
    .then(() => {
        alert("회원가입이 완료되었습니다.")
        history.push('/')
    })
    .catch((error) => {
        const { code } = error.response.data
        if (code === 1){
            setCode(code)
        }
        if (code === 2){
            setCode(code)
        }
    })
}, [profile])

먼저, 클라이언트에서 axios 를 이용해서 /join 이라는 주소에 profile변수를 보내고, 백엔드에선 request.body를 통해 각 변수를 구조분해 할당으로 받는다. 그 후에 다음과 같은 SELECT 문으로 id가 중복된 데이터를 반환해준다.

connection.query(
      `SELECT * FROM user WHERE nickname='${nickname}'`,
      (error, results) => {
          if (results.length >= 1) {
              response.status(403).json({
                  code: 2,
                  message: '중복된 닉네임'
              })
              return
          }
      }
  )

SELECT를 통해 중복된 아이디와 닉네임이 없다면 테이블에 데이터를 INSERT를 이용해 삽입해준다.

connection.query(
  `INSERT INTO user (user_id, password, nickname) 
  VALUES ("${id}", SHA2("${password}", 512), "${nickname}")`,
  (error, results) => {
      if (error) {
          console.log(error)
          response.status(403).json({
              message: '서비스 처리 불가'
          })
          return
      }
      response.status(200).json(results)
  })

패스워드에 대해 해시저장을 다음과 같이 해줌으로써 비밀번호에 대해 암호화해준다.

  • 최근 SHA256에 대해 문제점이 발견되면서 SHA512으로 암호화해줘야 한다.
SHA2("${password}", 512)

로그인

회원가입할 때 사용자 계정 관리 테이블을 생성했는데, 이때 사용자가 입력한 계정 정보를 조회해주면 되기 때문에 별도의 테이블은 생성하지 않았다.

다음은 로그인에 대한 MySQL의 전체코드이다.

// index.js
app.post('/auth', (request, response) => {
    
    const { id, password } = request.body
    console.log(request.body)
    
    connection.query(`SELECT * FROM user WHERE user_id="${id}" and password=SHA2("${password}", 512)`, 
        (error, results) => {
            if (error) {
                response.status(500).json({
                    message: '서비스 처리 불가'
                })  
                return
            }
            if (results <= 0){
                response.status(403).json({
                    code: 4,
                    message: '아이디를 다시 확인해주세요.'
                })
                return
            }
        response.status(200).json(results[0])
    })
})

사용자 계정 정보를 SELECT를 이용해 조회하면서 알맞은 아이디와 비밀번호를 입력했다면 로그인이 되고 그 결과를 클라이언트에 보내주는 형식이다.

다음은 클라이언트에서 axios를 이용해 백엔드와 연동하기 위한 코드이다.

// src/view/Home/index.js
const onLoginClick = useCallback(async () => {
    await axios.post('http://localhost:65100/auth', profile).then((response) => {
        alert('로그인을 성공하셨습니다.')
        setGlobalCookie(`${response.data.id}`, `${response.data.nickname}`) 
        goGameStart()
    }).catch((error) => {
        const {code, message} = error.response.data
        if(code === 3) {
            setCode(code)
        }
        if(code === 4) {
            setCode(code)
        }
    })
}, [profile])

게임 티켓 발행

사용자가 게임에 입장하면서 게임기록 등에 대한 데이터를 보관하기 위해 게임 기록 관리 테이블을 생성했어야 했다. 해당 프로젝트의 게임 기록 관리 테이블은 다음과 같이 생성했다.

다음은 게임티켓 발행에 대한 MySQL의 전체코드이다.

// index.js
app.post('/ticket', (request, response) => {
    const { id, nickname } = request.body
    connection.query(
        `INSERT INTO 
            game_round (user_id, user_nickname) 
        VALUES ("${id}", "${nickname}")
        `,
        (error, results) => {
            if (error) {
                response.status(500).json({
                    message: error
                })
                return
            }
            response.status(200).json(results)
        })   
})

게임에 입장하면 (해당 프로젝트에선 게임 버튼을 클릭했을 때) 티켓을 발행해주는 것이기 때문에 게임에 입장할 때 클라이언트에서 요청을 보내준다.

// src/view/Home/index.js
const onGameStartButtonClick = useCallback(() => {
    axios.post('http://localhost:65100/ticket', {id: cookies.data.id, nickname: cookies.data.nickname})
    setLevelOfDifficult(inputStatus)
}, [inputStatus])

타겟 업데이트 - 게임 종료 시

게임 기록 관리 테이블을 생성했는데, 해당 테이블에 게임종료 시간, 정답 등의 데이터를 삽입해줌으로써 게임기록확인시에 사용자가 얼마나 게임을 진행했는지 알수 있도록 했다.

다음은 게임티켓 업데이트에 대한 MySQL의 전체코드이다.

app.post('/gameEnd', (request, response) => {
    const { id, scoreSum, answer } = request.body
    connection.query(
        `UPDATE 
            game_round
        SET 
            termniated_at = NOW(),
            score = ${scoreSum},
            answer = ${answer}
        WHERE 1 = 1
        AND user_id = ${id}
        ORDER BY created_at DESC
        LIMIT 1
        `,
        (error, results) => {
            if (error) {
                response.status(500).json({
                    message: error
                })
                return
            }
            response.status(200).json(results)
        })   
})

사용자가 정답을 맞췄을 때 게임티켓을 업데이트 해줘야 하기 때문에 정답을 맞췄을때 클라이언트에서 요청을 다음과 같이 보내준다.

// src/view/Game/index.js
useEffect(() => {
    if (strike === level) {
      axios.post('http://localhost:65100/gameEnd', {
        id: cookies.data?.id,
        scoreSum: scoreSum,
        answer: answer,
      })
      alert("정답입니다!!!")
      history.push({
        pathname: '/End',
        state: {
          answer: answer,
          scoreSum: scoreSum,
        },
        nickname: cookies.data?.nickname
      })
    }
  }, [life])

게임 히스토리

해당게임의 정답과 사용자가 정답을 시도했던 기록들 그리고 그 시도에 따른 결과들을 데이터베이스에서 관리해줌으로써 사용자가 게임을 끝냈을때 볼수 있도록하였다. 때문에 테이블을 생성해주었다.

다음은 게임 히스토리에 대한 MySQL의 전체코드이다.

// index.js
app.post('/game', (request, response) => {
    const { id, answer, guess } = request.body;
    const getScore = (guess, answer) => {
        let strike = 0, ball = 0, out = 0
    
        for (let answerIndex = 0; answerIndex < answer.length; answerIndex++) {
          if (answer[answerIndex] === guess[answerIndex]) {
            strike += 1
          }
          else if (guess.includes(answer[answerIndex])) {
            ball += 1
          }
          else {
            out += 1
          }
        }
    
        return { strike, ball, out }
      }
      const { strike, ball, out } = getScore(guess, answer)

    connection.query(
        `
        SELECT
            id FROM game_round 
        WHERE user_id="${id}"
        ORDER BY created_at DESC LIMIT 1
        `,
        (error, result) => {
            if (error) {
                console.log(error)
                return
            }
            connection.query(
                `INSERT INTO game_history (gameticket_id, user_id, guess, strike, ball, out_count) 
                VALUES ('${result[0].id}', '${id}', '${guess}', '${strike}', '${ball}', '${out}')`,
                (error, result) => {
                    if(error) {
                        console.log(error)
                        return
                    }
                }
            )
        }
    )
})

사용자가 정답을 시도할때마다 게임히스토리 테이블에 데이터가 저장될거기 때문에 UserPanel 컴포넌트에서 요청을 보내준다.

// src/view/Game/UserPanel/index.js
if (checkIfDuplicateValue && checkIsProperLengthValue) {
    axios.post('http://localhost:65100/game', {
        id: cookies.data?.id,
        guess: guess,
        answer: answer,
    })
}

백엔드에서도 strike, ball, out 의 값을 다루는 getScore 함수를 가지고 있으면서 return 된 값들을 게임 히스토리 테이블에 INSERT를 해주면된다.

명예의 전당

게임점수에 따라 사용자의 닉네임을 나열해서 저장하고, 또한 상위 10명에 대해 명예의 전당에 올리기도 해주기 때문에 명예의전당 테이블을 생성해 관리해 주었다.

다음은 명예의 전당에 대한 MySQL의 전체코드이다.

app.post('/rank', (request, response) => {
    connection.query(
        `SELECT distinct user_nickname, score 
        FROM game_round 
        WHERE 1 = 1 
        and score = score ORDER BY score DESC`,
        (error, results) => {
            if (error) {
                response.status(500).json({
                    message: '서비스 처리 불가'
                })
                return
            }
            response.status(200).json(results) 
        }
    )
})

distinct를 이용해서 중복된 값들을 제외하고 단일의 데이터만을 조회했다.

명예의 전당은 게임이 종료된 후 /End 페이지가 렌더링 되면서 클라이언트가 요청해서 그 명단을 페이지에 보여줘야하기떄문에 useEffect로 요청해준다.

// src/view/End/index.js
useEffect(() => {
    axios.post('http://localhost:65100/rank')
    .then((response) => {
        setScoreArray(response.data)
    })
}, [])

게임 히스토리 전달하기

사용자가 게임을 어떻게 플레이 하였는지 게임시간을 얼마나 진행했는지 데이터를 뿌려주기 위해 게임을 진행하면서 기록했던 테이블을 이용했다.

다음은 게임히스토리 데이터 조회에 대한 MySQL의 전체 코드이다.

// index.js
app.post('/gameHistory', (request, response) => {
    const { id } = request.body;
    connection.query(
        `SELECT
            id FROM game_round 
        WHERE user_id="${id}"
        ORDER BY created_at DESC LIMIT 1`,
        (error, result) => {
            if(error) {
                console.log(error)
                return
            }
            connection.query(
                `
                SELECT guess, strike, ball FROM game_history
                WHERE 1 = 1
                AND gameticket_id = "${result[0].id}"
                ORDER BY id ASC
                `,
                (error, result) => {
                    if(error) {
                        console.log(error)
                        return
                    }
                    response.status(200).json(result)
                }
            )
        }
    )
})

app.post('/playTime', (request, response) => {
    const { id } = request.body;
    connection.query(
        `SELECT RUN_TIME FROM (SELECT *, TIME_TO_SEC(TIMEDIFF(termniated_at, created_at)) AS RUN_TIME
        FROM game_round) AS GAME_TICKET_WITH_RUN_TIME WHERE 1 = 1 AND user_id = "${id}" ORDER BY created_at DESC LIMIT 1`,
        (error, result) => {
            if (error) {
                console.log(error)
                return
            }
            response.status(200).json(result)
        }
    )
})

다음과 같이 클라이언트에서 요청했다

// src/view/End/index.js
useEffect(() => {
    axios.post('http://localhost:65100/gameHistory', {id: cookies.data.id})
    .then((response) => {
        setHistoryArray(response.data)
    })
}, [])

useEffect(() => {
    axios.post('http://localhost:65100/playTime', {id: cookies.data.id})
    .then((response) => {
        setplayTime(response.data[0].RUN_TIME)
    })
}, [])
profile
프론트엔드 개발자

0개의 댓글