
체육대회 메인 종목 (축구, 농구, 배구) 우승팀을 예측하고 배팅하는 웹 서비스이다.
프로젝트 노션
❗ 횡설수설주의 ❗
이 학교에 입학 하고 2주? 정도 지났을 때 웹 개발에 관심이 생겨서 공부하기 시작했다!
처음에 우리반 친구들 몇 명이서 모여서 팀 같은걸 만들었다!! 팀 이름은 여기서 굳이 언급 안 하겠다.
그러던 도중에 팀 안에서
??? : 체육대회 경기들 스포츠 토토 만들어 볼래? ㅋㅋ
라는 이야기가 나왔다.
물론 기획할 땐 사행성 같은 문제될 만한 요소들은 최대한 배제하면서 기획했다.
3월 샛째주 정도에 본격적으로 프로젝트 팀원들을 찾기 시작했다.
먼저 나는 Fornt-End를 맡았고 주은이는 보안쪽을 봐주기로 했다.
생각보다 학기초 이기도 하고 웹 개발에 관심이 있는 친구들이 많이 없어서 팀원 모집하는데 시간이 좀 걸렸다..
그러던 중에 준서형이 2반에 디자인 좀 하다 온 친구가 있다고 소개해줘서 다음날에 바로 찾아갔다.
예슬이는 Figma를 다뤄본 적이 없다고 해서 일단 배워오라고 했는데 생각보다 빨리 배워오고 디자인도 잘해서 놀랐다😮
솔직히 처음에 프로젝트를 시작했을 당시에 나도 아는게 상당히 없었다. 무슨 정신으로 하자고 한진 모르겠지만 일단 프로젝트 기획 후 한달 정도 시간 여유가 있어서 죽어라 공부했다.
한창 React를 공부하던 도중 진헌이를 꼬셨다!
진헌이는 안드로이드 전공이였지만 같이 프로젝트 하자고 꼬시니까 넘어왔다. 미안해 지성아
그렇게 진헌이를 실컷 억압하면서 함께 재미있게 공부했다! 2주만에 React 얼래벌래 끝냄
결론적으로 백엔드 자리는 2학년 선배님이 맡아주시면서 팀원은 총 5명으로 맞춰졌다.
처음에 노트에 끄적거리면서 구조? 같은걸 만들었다.
노션에 이런 것들 정리하면서 최대한 체계적으로 계획해보고 싶어서 노력했다.
공동 작업은 프로젝트 개발 당시에는 git hub 사용법을 몰라서 vs code Live Share 기능을 사용하여 공유했다.
간단하게 페이지 별 기능들의 구조는 이렇다.
SignUp -> Login -> MainPage
MainPage : 이미지 Swiper, 급식, 일정, 이벤트, 미니게임 이동 버튼
VotePage : (축구, 농구, 배구) 투표 버튼, 포인트 배팅 input, 투표 완료 버튼
RatingPage : 모든 학생들의 포인트 내림차순으로 정렬, 상위(1등,2등,3등) 학생들 따로 표시
MiniGamePage : 앞 / 뒷면 선택 버튼, 포인트 배팅 input, 동전굴리기 버튼
/ : 로그인 페이지
/signup : 회원가입 페이지
/main : 메인화면
/main/vote : 투표화면
/main/rating : 랭킹화면
/main/minigame : 미니게임 화면
일단 프로젝트가 체육대회를 목적으로 만들다 보니 모바일에서 접속했을 때도 잘 작동 했으면 좋겠어서 반응형 이란걸 구현해봤다.
Figma 디자인 -
장예슬
import React from "react";
import { useMediaQuery } from "react-responsive";
import "./reactResponsive.css";
export const Mobile = ({ children }) => {
const isMobile = useMediaQuery({
query: "(max-width:768px)",
});
return <>{isMobile && children}</>;
};
export const PC = ({ children }) => {
const isPc = useMediaQuery({
query: "(min-width:769px)",
});
return (
<div className="PcContainer">
<div>{isPc && children}</div>
</div>
);
};
체육대회 며칠전 부터 배포할 계획이여서 투표 기능만 있으면 밋밋할까봐
동전던지기 미니게임을 추가했다. 결론적으로 이 미니게임이 가장 성공했다..
s*****형태 / `lenght : 6 lenght : min - 8 , max - 20학교 이메일 입력하는 부분은 우리 학교 기숙사 관리 서비스인 Dotori 의 로그인 애니메이션을 참고하여 만들었다.
ID input을 클릭하면 @gsm.hs.kr span 테그가 오른쪽으로 이동한다.
input 테그가 active 된다면 span 테그가 이동되도록 구현했다.
Login Page로 리다이렉트 된다.Main / Vote / Rate / MiniGame Page 들은 SideBar or NavBar 로 페이지간 이동이 편리하게했다이미지 Swiper, 오늘 급식, 일정, 이벤트 같은 정보를 확인 할 수 있다.{
id: id,
select_1: select_1,
select_2: select_2,
select_3: select_3,
point_1: point_1,
point_2: point_2,
point_3: point_3,
}
3종류이고 게임마다 포인트를 배팅하기 때문에useEffect(() => {
const startVotingTime = new Date("2023-05-18T13:00:00+09:00"); // 시작 시간 (한국 시간)
const endVotingTime = new Date("2023-05-18T23:59:59+09:00"); // 종료 시간 (한국 시간)
const currentTime = new Date(); // 현재 시간 (한국 시간)
if (currentTime >= startVotingTime && currentTime <= endVotingTime) {
setAbleVote(true); // 현재 시간이 시작 시간과 종료 시간 사이이면 ableVote 스테이트를 true로 설정
} else {
setAbleVote(false); // 그렇지 않으면 ableVote 스테이트를 false로 설정
}
}, [isResult]);
오후 1시 ~ 오후 11시 59분 59초 이 시간에만 투표를 가능하게 구현하고 싶어서Date() 객체로 현재시간을 가져와서 시작시간, 종료시간과 비교하여 투표가능 여부를 구현했다.useEffect(() => {
if (gameInfo) {
setRatingA1(
(gameInfo[0]?.bet_rate?.split(",")[1] /
gameInfo[0]?.bet_rate?.split(",")[0]) *
100
);
setRatingA2(
(gameInfo[0]?.bet_rate?.split(",")[2] /
gameInfo[0]?.bet_rate?.split(",")[0]) *
100
);
setRatingB1(
(gameInfo[1]?.bet_rate?.split(",")[1] /
gameInfo[1]?.bet_rate?.split(",")[0]) *
100
);
...
);
}
}, [gameInfo]);
해당 팀에 배팅된 포인트 / 해당 게임의 총 배팅된 포인트 이렇게 비율이 표시된다.포인트 순으로 내림차순 정렬하여 순위를 표시하였다.이게 메인이 아니였는데 다들 동전던지기만 해서 배포한지 2시간도 안되서 빈털털이가 된 학생들이 속출했다..
여담으로 배포후 모바일에서 미니게임을 하면 서버에 request가 안가는 오류가 발생했다.
승패를 프론트에 정해서 서버로 보내주기 때문에(보안에 취약했다..) 승패는 뜨지만 포인트, 남은 횟수 변화는 없었다.
선배님 죄송해요
백엔드 완성은 배포 4일 전에 완료가 되어서 시간이 살짝 부족했다..
그래도 정리를 깔끔하게 해주셔서 연결하기 수월했다.
import axios from "axios";
const BASE_URL = "";
// 회원가입
export async function Signup(id, password, student) {
try {
const response = await axios.post(
`${BASE_URL}/acounts/signup/`,
{
id: id,
password: password,
student: student,
}
);
// console.log(response);
return true;
} catch (error) {
// console.log(error);
return false;
}
}
...
솔직히 백엔드 연결하는데 실질적인 시간이 하루정도 밖에 없었고, 게다가 연결 해본 경험도 없고
async / await 비동기 동작에 대한 이해도 부족했기 때문에 힘들었다. 😥 아직도 이해가 잘 안되어 공부해야한다 ㅠㅠ
로그인을 하면 이런 정보를 응답 받는다.
{
"email": "s22009@gsm.hs.kr", # 이메일primary key
"last_login": null,
"password": "12345678", # 비번
"student_number": 1234, # 학번
"name": "변도진", # 유저 이름
"point": 2500, # 점수 default 1500
"minigame_count": 3, # 미니게임 횟수 default 3
"is_active": true,
"is_admin": false
}
여기서 이메일 @ 기호 앞의 6글자(s00000)를 클라이언트에 저장 후 유저 정보를 불러올 때 사용한다.
저장하는 방법은 로컬 스토리지를 사용했다.
하하.. 그런데 로컬 스토리지는 변조하기 너무 쉬웠다. 나 변조 해주세요 이러는 것 같았다.
솔직히 하루 쓸 페이지고 시간도 정말정말 부족했기 때문에 백엔드에서도 보안에 대해서 신경을 별로 쓰지 않았다.
이번 프로젝트를 경험하면서 보안이 정말 중요하다는 것을 뼈 빠지게 깨달았다..
일단 관리자 페이지를 열어서 저 value를 다른 학생 id로 바꾸면
짜잔~ 해당 학생의 계정으로 로그인 되는거나 마찬가지였다.
일단 어쩔 수 없이 crypto.js 라이브러리를 사용하여 암호화 해 저장하고 필요할 때 복호화하여 사용했다.
물론 이 암호화 복호화 작업을 프론트에서 한다면 해킹 당하는건 시간 문제 이지만 어쩔 수 없었다.
문제가 정말 많았지만 어쨋든 5월 18일 오전에 배포 후 홍보했다.
회원가입 해주신 137명 학생들과 투표에 참가해주신 97명 학생들 모두 정말 감사합니다!!
😍
셀 수 없이 많다.. 프로젝트가 끝나고 나서 보안 취약점들이나 문제점들을 찾아봤는데 끝도 없이 나온다 ㅎㅎ..
우선 가장 큰 문제들만 작성해보면
필요 없는 컨포넌트가 너무 많고 정리가 안되어있다..
처음에 컴포넌트가 몇 없었을 땐 Pages 폴더 / Components 폴더 이렇게만 나누어 사용했는데
점점 늘어나니 좀 더 세분화 하여 정리하지 못 한게 후회되었다.
그리고 컴포넌트들을 재사용하지 않아 필요 없는 컴포넌트들이 늘어났고 지저분해졌다.
Point 같은 전역 상태 관리가 필요한 데이터를 Redux 같은 도구를 사용하여 관리하지 않아 프롭스 드릴링이 심했다.
처참한 프롭스 드릴링
id를 로컬 스토리지에 보관할 때 시도한 암호화 키를 클라이언트에서 가지고 있었다.일 안하는 준서형😎
우선 보안 부분에서 많은 걸 느꼈다.. 프론트 코드가 다 보이는 줄 몰랐고 찾아보니 프론트 코드는 숨기는게 없어야 한다고 한다.
쿠키, 세션, 토큰에 대해서도 공부해야할 필요를 느꼈고 중요한 데이터는 물론 클라이언트 <-> 서버 에서 데이터를 주고 받을 땐 꼭 암호화 과정을 거쳐야 한다는 것을 알게되었다.
그리고 나중에 유지 보수가 편하게 여러 전역 상태 관리 도구와 컴포넌트 관리의 필요성도 알게 되었고 관심이 생겼다.
진헌이는 처음엔 안드로이드 전공을 하고 싶어 했다. 근데 프로젝트 하자고 꼬시니까 쉽게 넘어왔다. 😁
그 일 때문에 같이 공부하던 지성이에게 질투를 많이 받았다. 근데 지성이도 웹으로 넘어왔다
진헌이는 학교 오기 전에 프론트쪽 공부를 조금이라도 하고 와서 빠르게 배웠다!!
나와 매주 주말에 거의 새벽까지 같이 공부했다. 히히
하지만 프론트엔드 인재 이진헌은 2023 모바일 로보틱스로 떠났다..
아무래도 프로젝트 주제가 사행성과 연관되어 있기도 하고, 전교생을 대상으로 하는 프로젝트이기 때문에 학생 회장님과 많은 상의를 했다.
항상 물어봤을 때 자세히 대답해주시고 늦은 시간에 연락 드려도 바쁘실텐데 정성스럽게 답변 해주신 학생 회장님께 감사의 말씀을 전한다.
그리고 서버 호스팅을 무료로 제공해준 사업가 김유준
실상 한 건 없지만 옆에서 응원해준 보안 김주은 문혜성
내 정신적 지주가 되준 미래의 모바일 로보틱스의 희망 이진헌
실력이 엄청난 변도진 선배님
열정 넘치는 디자이너 장예슬
그리고 가입해주신 137명의 학생 여러분들
모두 ㄳ하다.
그리고 GSMGOGO 담당 선생님 임은영 선생님 감사합니다. 😄
우당탕탕 신희성의 토토사이트 개발일지