# 들어가며
웹 종합 강의를 듣고, 바로 첫 주차에 팀 단위 미니프로젝트를 시작하였다. 인공지능 국비교육을 들었을 때도, 미니프로젝트 위주로 자주 하였는데 끝나고 나서 반년만의 협업이라 조금 설레고 걱정되기도 하였다. 현재까지 진행한 프로젝트 결과물, 내용, 배웠던 점, 느낀점등을 간략하게 정리하려고 한다. 사실 여태까지 블로그에는 내가 배웠던 점만 간략하게 정리하였는데 이번엔 글로 풀어내서 적어보려고 한다.
# 주제 선정
처음 게더에서 모여 조원들의 얼굴과 목소리를 확인한 뒤, 주제를 논의하였는데 먼저 저번 기수의 조들이 어떻게 했는지 둘러보던 중, 웹 종합 강의에서 배웠던 스파르타피디아를 응용한 프로젝트가 많았음을 느꼈다. 로그인을 한 뒤영화 추천이나 음악 추천, 반려견 매칭등 상단에 배너와 버튼이나 요소가있고 내용에 다양한 card들을 볼수있는 프로젝트들이였다. 아무래도 전 기수들도 아직까진 배운게 없어서 했던 프로젝트를 응용하여 했구나 라는 생각이 들었다. 근데 뭐 아예 주어진것없이 쌩으로 만드는것도 이상한 것같고, 배웠던 프로젝트를 응용하면 재학습할수도 있으며, 조원 중에 정말 아무것도 모르는사람이 있을수 있으니 우리조도 이방향으로 가면 좋을 것 같다는 생각이 들었다.
따라서 위의 내용을 착안하여, 조금 신박한 주제를 찾다가 요즘 asmr종류가 다양해진 것 같아, 조원들에게 "저흰 asmr추천 해주는 사이트 어떠세요?"라고 물어봤는데 모두 찬성하여 바로 프로젝트에 돌입하게 되었다.
# 프로젝트 초기 단계
처음엔 S.A(Start Assignment)를 작성하여 프로젝트의 제목,설명,와이어프레임,기능(api)정리를 하였는데 처음엔 다들 아무것두 모르니 간단하게 생각하여 API 설계에서 추천 목록 전체 조회, 추천 목록 작성 두개만 적게 되었다. 학교에서 스토리보드나 DB설계같은 시스템 설계는 얼추 해봐서 그 내용을 토대로 게더내의 공간에 있는 칠판에 먼저 화면을 구상하여 적었고, 조원 모두가 각각 적게 되었는데 얼추 깔끔하게 완성된 것 같았다.
미니 프로젝트엔 필수 포함 사항 두가지가 있었다.
◾ Jinja2 템플릿 엔진을 이용한 서버사이드 렌더링
◾ JWT 인증 방식으로 로그인 구현
처음에 S.A를 완료한뒤, 역할을 나눠야하는데 얘기를 나눠볼수록 조원들이 다들 경험이 없는것같아 혹시나해서 물어봤는데 모두 비전공자에 프로그래밍 경험이 없었다.( 현재 기수로 진행되고있는 이노베이션 캠프는 다른 기수와 달리 특정 지역에서 무료로 진행되고 있었기에 전공자가 많이 올 줄 알았는데 알고보니 조에서 나혼자여서 조금 놀랐다. ) 따라서 우리 조는 스파르타피디아에서 대강 틀을 잡아놓고 강의를 들으며 조금씩 채워나가는 방식으로 진행하였다. 방식이 조금 막연해보일순 있지만 개인적으로 이끌어갈 자신감은 있었다.
# Jinja2 방식 , SSR 방식 활용
🔻
대충 위에서 아래와같은 모습으로 바꼈다. 프로젝트 내에서 SSR(서버사이드렌더링)을 어떻게 추가할지 고민해봤는데 원래는 Ajax로 서버를 호출하여 요소들을 찍어줬다면 서버에서 바로 main페이지를 렌더링하고, card들의 크롤링한 내용들을 SSR방식으로 보내주는 방식으로 제작하였다.
@app.route('/')
def home():
token_receive = request.cookies.get('mytoken')
try:
payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
user_info = db.users.find_one({"username": payload["id"]})
cards = list(db.ASMR.find({}, {"_id": False}))
return render_template('index.html', user_info=user_info, cards=cards)
except jwt.ExpiredSignatureError:
return redirect(url_for("login", msg="로그인 시간이 만료되었습니다."))
except jwt.exceptions.DecodeError:
return redirect(url_for("login", msg="로그인 정보가 존재하지 않습니다."))
위에서 로그인에 성공하게 되면 index.html(메인 페이지)로 redirect하게 되고 cards라는 카드안에 들어갈 요소들을 cards안에 모두 넣어서 redirect하게 된다.
<div class="wrap">
<div class="mycards">
<div class="row row-cols-1 row-cols-md-4 g-4" id="cards-box">
{% for asmr in cards %}
<div class="col">
<div class="card h-100">
<a href="{{ asmr.url }}"><img src="{{ asmr.thumbnail }}"
class="card-img-top"></a>
<div class="card-body">
<h5 class="card-title">{{ asmr.title }}</h5>
<p class="card-text">{{ asmr.content }}</p>
<p class="mycomment">{{ asmr.comment }}</p>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
...
html에선 Jinja2방식을 활용하여 html자체는 렌더링한 상태에서, 서버에서 불러온 값 cards의 요소만 각각 들어가게 된다.
메인 페이지에 우측아래에 있는 버튼들은 각각 분위기에 맞게 카테고리를 생성하여 카드를 재 배치해준다. 저 버튼들은 모두 ajax로 연결해줬으며 SSR이 아닌 CSR방식을 따르고있다.
따라서 위 프로젝트는 SSR+CSR이 혼합된 방식이라고 볼 수 있다.
# JWT방식 사용
@app.route('/sign_in', methods=['POST'])
def sign_in():
# 로그인에서 아이디와 비밀번호를 받는다.
username_receive = request.form['username_give']
password_receive = request.form['password_give']
# 로그인쪽과 같이 해시함수를 이용해서 암호화한다.
pw_hash = hashlib.sha256(password_receive.encode('utf-8')).hexdigest()
# 그래서 이 아이디와 비밀번호가 매칭되는 사람이 있는지 판단을 한다.
# 만약에 매칭되는 사람이 있다면 로그인성공!
# 매칭되는 사람이 없다면 둘중 하나 잘못입력한것
result = db.users.find_one({'username': username_receive, 'password': pw_hash})
if result is not None:
# 로그인 성공한 경우
# 아이디와 비밀번호를 제대로 설정했다면 서버에서 jwt토큰을 만들어서 발행한다.
# jwt토큰은 놀이공원에서 자유입장권 같은것. '어떤사람이 언제까지 입장이 유효하다'라는 사실을 적시해준다.
payload = {
'id': username_receive,
# 여기가 로그인 유효시간 정해주는곳.
# datetime.utcnow() :지금부터 + timedelta(seconds=60 * 60 * 24) 최대 24시간까지
'exp': datetime.utcnow() + timedelta(seconds=60 * 60 * 24) # 로그인 24시간 유지
}
# payload로 jwt토큰을 만들어서 SECRET_KEY로 암호화를 만들어주고
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256') #.decode('utf-8') 왜 이걸 뒤에 붙이면 오류가 나지...?? 저거 없애니까 로그인 성공함..(코드스니펫 코드임)
# 클라이언트에게('token': token) 넘겨주면 끝!
return jsonify({'result': 'success', 'token': token})
# 로그인 실패한 경우
else:
return jsonify({'result': 'fail', 'msg': '아이디/비밀번호가 일치하지 않습니다.'})
위의 각 주석은 같은 조원이 정리하며 단 것인데 엄청 꼼꼼하게 달아주셨다.
간략하게 로그인 방식은
1. 아이디,비밀번호를 받아서 비밀번호는 해시함수를 이용해서 암호화하여 db에있는 비밀번호 값과 비교하게 되고(db에 저장되어 있는 비밀번호값도 해시함수로 암호화하여 저장되어 있다)
2. 로그인에 성공하게 되면 서버에서 jwt토큰을 발행하게 되는데 secret_key과 payload를 포함하여 발행하게 된다.(secret_key는 서버에서 직접 설정한 비밀키이며 예시로 위 프로젝트에선 sparta로 설정되어있다.)
@app.route('/')
def home():
token_receive = request.cookies.get('mytoken')
try:
payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
user_info = db.users.find_one({"username": payload["id"]})
cards = list(db.ASMR.find({}, {"_id": False}))
return render_template('index.html', user_info=user_info, cards=cards)
except jwt.ExpiredSignatureError:
return redirect(url_for("login", msg="로그인 시간이 만료되었습니다."))
except jwt.exceptions.DecodeError:
return redirect(url_for("login", msg="로그인 정보가 존재하지 않습니다."))
토큰이 생성되면 토큰을 쿠키에 담아서 클라이언트로 전송하게 된다.
#후기
아직 제대로 된 프로젝트를 하기전 임시로한 미니 프로젝트이므로 진행했던 방식이 다소 미흡할지라도 요구조건을 잘 충족하여 결과물은 괜찮게 나온 것 같았다. 로그인이나 로그아웃은 어쩔수없이 대부분 강의를 보며 따라 쓴 정도에 css를 추가한것이며 Jinja2 방식도 자잘하게 들어간것같아 아쉬움이 남는다. 항상 드는 생각은 이런걸 따라하는 정도로 괜찮을까? 라는 생각인데 결국 그 생각의 해답은 많이 해봐야한다는 결론에 다다른다. 나중에 또 이것을 써먹을진 모르겠으나 내용은 확실히 알고가자고 생각은 드는것같다.
협업 부분에선 아무래도 전공자인 날 주축으로 질문을 많이 받게되었는데 질문받았던 내용이 대부분 내가 해결할수 있는 선이라서 다행이였다.
이번 프로젝트에서 얻어가는게 있다면 기존 방식과 달리 JWT방식이나 SSR방식의 차이점을 직접 코딩하며 느껴본점이며 협업 부분에선 의견이 부딪히는 부분도 없었고 처음부터 끝까지 분위기 좋게 진행하였다. 모두 배워가는 입장이였으며 조원 모두가 배려심도 깊었기에 면접에서 "의견이 갈리면 어떻게 대처하냐?" 라는 질문엔 이 프로젝트를 통해서 해답을 찾기는 어렵다고 볼수있었다.
# 끝마치며
뭔가 부족하고 내용이 알찬 회고록은 아니지만 블로그에 이렇게 긴글을 남기는 것은 처음인것같다. 무슨 의미가 있을까 생각해보면 나중에 내가했던 고민을 좀더 논리적으로 볼 수 있다는점이 있는것같다. 따라서 그 장점을 좀더 느끼고 혹여나 다른 장점도 찾을수있게 항해를 진행하며 이런 방식으로 쭉 진행할 생각이다.
'항해 부트캠프 > 항해99' 카테고리의 다른 글
항해99 ) 4주차 회고록 (0) | 2022.07.17 |
---|---|
항해99 ) 3주차 회고록 (0) | 2022.07.09 |
항해99 ) 2주차 회고록 (0) | 2022.07.03 |