# 들어가며
웹 종합 강의를 듣고, 바로 첫 주차에 팀 단위 미니프로젝트를 시작하였다. 반년만의 협업이라 조금 설레고 걱정되기도 하였다. 현재까지 진행한 프로젝트 결과물, 내용, 배웠던 점, 느낀점등을 간략하게 정리하려고 한다. 사실 여태까지 블로그에는 내가 배웠던 점만 간략하게 정리하였는데 이번엔 글로 풀어내서 적어보려고 한다.
# 주제 선정
처음 게더에서 모여 조원들의 얼굴과 목소리를 확인한 뒤, 주제를 논의하였는데 먼저 저번 기수의 조들이 어떻게 했는지 둘러보던 중, 웹 종합 강의에서 배웠던 스파르타피디아를 응용한 프로젝트가 많았음을 느꼈다. 로그인을 한 뒤영화 추천이나 음악 추천, 반려견 매칭등 상단에 배너와 버튼이나 요소가있고 내용에 다양한 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 |