[23.10.15] 웹미니 프로젝트 WIL

yy·2023년 10월 12일

개발일지

목록 보기
1/122

#1. 개발 공부가 처음이신가요? 처음이 아니라면, 어느 정도 기간을 가지고 어떻게 학습을 하셨나요?

1년 전부터 독학으로 눈도장 찍은 정도로 프로그래밍 기초언어를 기초적인 내용만 가볍게 공부했다. 어떤 식으로 공부에 깊이감을 더해야할지몰라 기초언어만 기웃거리면서 공부해왔다. 개발 공부라고 하기엔 좀 그렇지만 기본적인 지식이라도 알아두자라는 느낌으로 정보처리기사 자격증을 취득하기도 했다.

#2. 내가 항해99에 참여한 계기는 무엇인가요?

  • 어떤 역량을 기르고자, 혹은 어떻게 성장하고자 참여하셨는지 구체적으로 작성해주세요.

비전공자 개발자로서 기본개발능력을 기르고 협력하는 방법을 알고싶었다. 팀프로젝트를 하면서 개발 플로우도 익히며 개발업무에 익숙해지고 싶었다.


#3. 개발자의 역할을 수행하는 데에 있어 나의 강점과 연관된 부분은 무엇이라고 생각하나요?
혹은 보완, 개선하고 싶은 개인 역량이 있나요 ?

  • 과거 혹은 현재의 업무와 연관 지어도 좋습니다. ‘능력’을 기준으로 고민해주세요.

문제가 생겼을때 혼자 먼저 찾고, 해결하려고하는 자립성이 나의 강점이다. 이는 앞으로 개발자의 업무에 있어 해결하려는 데에 중요하다고 생각한다.
또다른 나의 강점은 꾸준함이다. 1년 전 부터 개발자라는 커리어를 생각했을때부터 맥이 끊길듯 안끊기는 느낌으로 은은하고 잔잔하게 공부를 이어나갔다.

이와 관련하여 나한테 보완해야할 점은 주변인들한테 질문하기다. 내가 얼마나 부족한지 느끼기때문에 괜히 질문했다가 이런것도 몰라? 라는 반응이 돌아올까 물어보기도 겁이 난다.
아직은 개발영역에서 문제가 생기고 이를 처리하는 과정이 익숙치 않고, 내가 알고있는게 뭔지도 불확실한 상황에서 더욱 그렇게 느끼는 듯 하다.

#4. 항해99 수료 후, 어떤 모습으로 성장하고 싶나요?

  • 개발자라는 커리어를 통해 무엇을 이루고 싶나요?
    무슨무슨 기능 넣어주세요~ 라고 말만 하면 뚝딱뚝딱 잘 만들어내고싶고, 1인분은 하는 개발자가 되고싶다. 나중에 시니어 개발자가 되면 장소나 시간에 구애받지 않고 내가 하고싶은걸 하면서 살아가고 싶다.



회고




난생처음 웹미니 프로젝트를 했다. 내가 맡은 바는 로그인 기능 구현. 쓰흡 제대로 할 수 있을까. 못할게 뭐야 라는 생각으로 접근했다. 그리고 예시라고 항해에서 예시코드도 제시해줬다.(맨 아래 첨부 예정) 암만 봐도 모르겠는거다. 주석과 코드들의 흐름으로 이해하려고 했는데 난 여태 뭘 배웠는가에 대한 회의감이 밀려오기 시작하여 일단 로그인이랑 회원가입을 하는 척이라도 하는걸로 만들어보자. 라고 다짐했다.


notion => API문서 작성 방법
github => 사용 방법 미흡/ 파일 합치기를 원시방법으로 해결함.
로그인 보안/DB => flask, DB, JWT 등의 공부 필요
flask, DB => 진짜 난 아무것도 모른다.
와이어프레임, ERD => 작성방법을 알아야 논리적으로 표현할 수 있다.


1조부터 8조까지 발표를 하는데 정말이지 대단하다 싶은 사람들이 있었다. 아니 대체 사람들 저런건 어디서 보고 배워서 하는거야?(심지어 ppt도 준비한 사람들이 있었다)싶은 생각이 들었다. 예를 들면 와이어프레임과 ERD 표현, 코드 구현력들 등등..역시 여기서 비전공자 나만 진심인거지? 다들 진짜 비전공자 아니고 다들 어디서 배워오는거지? 싶었다. 거짓말쟁이들. 이럼 나만 스트레스받고 심장 벌벌 손 벌벌 떨면서 공부하게된다고ㅠㅠ

ERD

영상


협업인 만큼 깃허브와 sourcetree를 이용해서 커밋관리를 해봤다. 버전과 이력관리가 되어 편하긴 한데...다른 분들이 올린 파일에 겹쳐져써지면 어쩌지...?(이름이 같으면 당연함. 그래야 협업이 됨) 이라는 이상한 생각이 들어 쉽사리 푸쉬를 하지 못했다. 그러라고 만들어둔 깃허브인데 왜 안쓰고 뭐했는지 다음부터는 깃허브와 sourcetree를 활용해봐야겠다. 애초에 그냥 온갖 프로젝트를 깃헙에 올리는게 좋겠는걸..?


로그인의 보안/DB문제는 수없이도 말했지만 내가 flask, DB, JWT를 공부를 안한게 아니고 그냥 응용할지도 모르는것 같다. 그렇다고 보안관련해서 아는것도 아니지만 그래도 flask랑 db는 여태 계속 강의를 들었는데 들었던것도 기억을 못해서 쩔쩔매고 누구한테도 물어보지 못하는게 내가 너무 고립되어있다고 생각이 들었다. 조금 바뀌어야지. 근데 사람이 변할까? 암튼.
flask, db를 좀 따로 공부해야겠다고 생각이 들었다. 차주부터 프로그램언어 기초 강의 듣기 시작할텐데 그때 시간을 정해서 나머지 학습을 해야겠다.


와이어프레임,ERD는 정처기 시험때도 나왔던 부분인데 그냥 이런게 있나보다 하고 지나갔었다. 근데 이제는 따로 작성방법에 대해서 알아봐야겠다.


부트스트랩을 이용해서 대충 틀은 짰는데 자꾸 CSS가 안먹는거다. 그럴때 찾은 적용순위 링크. CSS적용에도 우선순위가 있다니. class 속성에 넣는건 style태그에 넣는 css보다 우선 적용되는건 알았지만, 그 외로는 몰랐다.


특히 이번 프로젝트를 하면서 개발자도구를 많이 사용했는데 CSS때문에 많이 봤다. 보통 display: flex;를 먹여놓고 정렬하려고 할 때 바로바로 확인하려고 자주 본다. 가끔 취소선이 그어져있어서 CSS가 안먹는게 존재한다. 그때는 부모로부터 상속된 CSS를 따르기 때문에 그렇게 표시된다고한다.

CSS적용순위
CSS에 왜 취소선이?!


pythonanywhere를 이용해 배포 중 발생한 서버문제로 서비스를 하고 있는 API에서 받아온 리스트를 보여주는 화면이었는데 서버 에러가 떴다. 코드는 문제가 없었고, 지난번 내가 멜론사이트를 크롤링해서 보여주는 사이트를 배포했다가 작동이 안됐던 적이 있다고 포스팅을 올린적이 있다. 초반에 팀원 중 한 분이 사이트에서 바로 데이터를 웹에 보여줘서 그런건 아닌지 의문을 제기하셨고, DB에 저장 후 보여주는 코드작성하면 되지 않을까 하는 아이디어를 제시해주셨다. 혹시 안되면 그렇게하자고...근데 프로젝트 마지막날 진짜 배포가 안될줄은 몰랐고, db에 저장해서 나타나는 코드를 짜기엔 다들 지쳐있었다. 그래서 매니저님 한테 가서 문의했고, flask와 크롤링하는 프로그램(requests, bs4)의 호환성 문제일 수 있다고 하셨다. 팀원 분이 알아본 경과 pythonanywhere에서 관리하는 크롤링 프로그램이 따로 있기에 그걸 따라야한다고 한다. 오마이갓.
크롤링 프로그램을 바꾸던가, 배포하는 사이트를 바꾸던가.

크롤링하는-Flask앱을-pythonanywhere에-호스팅하는-방법




웹미니프로젝트

SA

서비스 화면

  • 메인화면(index.html

    -로그인화면(login.html)

    -회원가입화면(register.html)

    -박스오피스 순위 화면(movie_search.html)

    -게시판 등록화면(boad_list.html)

    -게시판 등록누르면 작성화면(board_write.html)

    -추천영화 화면(Recommened.html)

금회 미니프로젝트 깃허브 사이트


기필코 로그인, 회원가입 화면을 완성하고 마리.


(언젠가)


항해에서 제공 해준 로그인 코드

app.py

from flask import Flask, render_template, jsonify, request, session, redirect, url_for
app = Flask(__name__)
from pymongo import MongoClient
import certifi
ca=certifi.where()
client = MongoClient("mongodb+srv://test:test@cluster0.15fhovx.mongodb.net/test", tlsCAFile=ca)
# client = MongoClient("mongodb+srv://test:<sparta>@cluster0.qfbqcwg.mongodb.net/", tlsCAFile=ca)
db = client.dbsparta_plus_week4
# JWT 토큰을 만들 때 필요한 비밀문자열입니다. 아무거나 입력해도 괜찮습니다.
# 이 문자열은 서버만 알고있기 때문에, 내 서버에서만 토큰을 인코딩(=만들기)/디코딩(=풀기) 할 수 있습니다.
SECRET_KEY = 'asdfasdf'
# JWT 패키지를 사용합니다. (설치해야할 패키지 이름: PyJWT)
import jwt
# 토큰에 만료시간을 줘야하기 때문에, datetime 모듈도 사용합니다.
import datetime
# 회원가입 시엔, 비밀번호를 암호화하여 DB에 저장해두는 게 좋습니다.
# 그렇지 않으면, 개발자(=나)가 회원들의 비밀번호를 볼 수 있으니까요.^^;
import hashlib
#################################
##  HTML을 주는 부분             ##
#################################
@app.route('/')
def home():
    token_receive = request.cookies.get('mytoken')
    try:
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        user_info = db.user.find_one({"id": payload['id']})
        return render_template('index.html', nickname=user_info["nick"])
    except jwt.ExpiredSignatureError:
        return redirect(url_for("login", msg="로그인 시간이 만료되었습니다."))
    except jwt.exceptions.DecodeError:
        return redirect(url_for("login", msg="로그인 정보가 존재하지 않습니다."))
@app.route('/login')
def login():
    msg = request.args.get("msg")
    return render_template('login.html', msg=msg)
@app.route('/register')
def register():
    return render_template('register.html')
#################################
##  로그인을 위한 API            ##
#################################
# [회원가입 API]
# id, pw, nickname을 받아서, mongoDB에 저장합니다.
# 저장하기 전에, pw를 sha256 방법(=단방향 암호화. 풀어볼 수 없음)으로 암호화해서 저장합니다.
@app.route('/api/register', methods=['POST'])
def api_register():
    id_receive = request.form['id_give']
    pw_receive = request.form['pw_give']
    nickname_receive = request.form['nickname_give']
    pw_hash = hashlib.sha256(pw_receive.encode('utf-8')).hexdigest()
    db.user.insert_one({'id': id_receive, 'pw': pw_hash, 'nick': nickname_receive})
    return jsonify({'result': 'success'})
# [로그인 API]
# id, pw를 받아서 맞춰보고, 토큰을 만들어 발급합니다.
@app.route('/api/login', methods=['POST'])
def api_login():
    id_receive = request.form['id_give']
    pw_receive = request.form['pw_give']
    # 회원가입 때와 같은 방법으로 pw를 암호화합니다.
    pw_hash = hashlib.sha256(pw_receive.encode('utf-8')).hexdigest()
    # id, 암호화된pw을 가지고 해당 유저를 찾습니다.
    result = db.user.find_one({'id': id_receive, 'pw': pw_hash})
    # 찾으면 JWT 토큰을 만들어 발급합니다.
    if result is not None:
        # JWT 토큰에는, payload와 시크릿키가 필요합니다.
        # 시크릿키가 있어야 토큰을 디코딩(=풀기) 해서 payload 값을 볼 수 있습니다.
        # 아래에선 id와 exp를 담았습니다. 즉, JWT 토큰을 풀면 유저ID 값을 알 수 있습니다.
        # exp에는 만료시간을 넣어줍니다. 만료시간이 지나면, 시크릿키로 토큰을 풀 때 만료되었다고 에러가 납니다.
        payload = {
            'id': id_receive,
            'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=100)
        }
        token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
        # token을 줍니다.
        return jsonify({'result': 'success', 'token': token})
    # 찾지 못하면
    else:
        return jsonify({'result': 'fail', 'msg': '아이디/비밀번호가 일치하지 않습니다.'})
# [유저 정보 확인 API]
# 로그인된 유저만 call 할 수 있는 API입니다.
# 유효한 토큰을 줘야 올바른 결과를 얻어갈 수 있습니다.
# (그렇지 않으면 남의 장바구니라든가, 정보를 누구나 볼 수 있겠죠?)
@app.route('/api/nick', methods=['GET'])
def api_valid():
    token_receive = request.cookies.get('mytoken')
    # try / catch 문?
    # try 아래를 실행했다가, 에러가 있으면 except 구분으로 가란 얘기입니다.
    try:
        # token을 시크릿키로 디코딩합니다.
        # 보실 수 있도록 payload를 print 해두었습니다. 우리가 로그인 시 넣은 그 payload와 같은 것이 나옵니다.
        payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])
        print(payload)
        # payload 안에 id가 들어있습니다. 이 id로 유저정보를 찾습니다.
        # 여기에선 그 예로 닉네임을 보내주겠습니다.
        userinfo = db.user.find_one({'id': payload['id']}, {'_id': 0})
        return jsonify({'result': 'success', 'nickname': userinfo['nick']})
    except jwt.ExpiredSignatureError:
        # 위를 실행했는데 만료시간이 지났으면 에러가 납니다.
        return jsonify({'result': 'fail', 'msg': '로그인 시간이 만료되었습니다.'})
    except jwt.exceptions.DecodeError:
        return jsonify({'result': 'fail', 'msg': '로그인 정보가 존재하지 않습니다.'})
if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)

index.html

<!doctype html>
<html lang="en">
  <head>
    <!-- Webpage Title -->
    <title>여기다가 다른 페이지 넣으면 되겠다.</title>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bulma CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
		<!-- JS -->
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>
    <script>
      // 로그아웃은 내가 가지고 있는 토큰만 쿠키에서 없애면 됩니다.
      function logout(){
        $.removeCookie('mytoken');
        alert('로그아웃!')
        window.location.href='/login'
      }
    </script>
    <style>
      body {
        background-color: black;
        color: white;
      }
    </style>
  </head>
  <body>
    <p>
      <h1 class="title">로그인하고 100초 동안만 볼 수 있는 페이지입니다.</h1>
      <h1 class="subtitle">계속 새로고침 해보세요</h1>
    </p>
    <h5 class="subtitle">나의 닉네임은: {{nickname}}</h5>
    <button class="button is-danger" onclick="logout()">로그아웃하기</button>
  </body>
</html>

login.html

<!doctype html>
<html lang="en">
<head>
    <!-- Webpage Title -->
    <title>Hello, world!</title>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bulma CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
    <!-- JS -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>
    <script>
        {% if msg %}
        alert("{{ msg }}")
        {% endif %}
        // ['쿠키'라는 개념에 대해 알아봅시다]
        // 로그인을 구현하면, 반드시 쿠키라는 개념을 사용합니다.
        // 페이지에 관계없이 브라우저에 임시로 저장되는 정보입니다. 키:밸류 형태(딕셔너리 형태)로 저장됩니다.
        // 쿠키가 있기 때문에, 한번 로그인하면 네이버에서 다시 로그인할 필요가 없는 것입니다.
        // 브라우저를 닫으면 자동 삭제되게 하거나, 일정 시간이 지나면 삭제되게 할 수 있습니다.
        function login() {
            $.ajax({
                type: "POST",
                url: "/api/login",
                data: { id_give: $('#userid').val(), pw_give: $('#userpw').val() },
                success: function (response) {
                    if (response['result'] == 'success') {
                        // 로그인이 정상적으로 되면, 토큰을 받아옵니다.
                        // 이 토큰을 mytoken이라는 키 값으로 쿠키에 저장합니다.
                        $.cookie('mytoken', response['token']);
                        alert('로그인 완료!')
                        window.location.href = '/'
                    } else {
                        // 로그인이 안되면 에러메시지를 띄웁니다.
                        alert(response['msg'])
                    }
                }
            })
        }
    </script>
    <style>
        body {
            background-color: black;
            color: white;
            height: 100vh;
            width: 100vw;
        }
        .logincontainer {
            display: flex;
            flex-direction: column;
            height: 100vh;
            justify-content: center;
            text-align: center;
        }
        .inputForm {
            margin-bottom: 50px;
            display:inline-block;
        }
        .title {
            align-items: center;
            color: white;
        }
        .label {
            color: white;
        }
    </style>
</head>
<body>
    <div class="logincontainer">
        <h1 class="title">LOG IN</h1>
        <div class="section has-text-centered">
            <div class="container" style="width:60%">
                <div class="inputForm">
                    <div class="field is-horizontal">
                        <div class="field-label is-normal">
                            <label class="label" for="userid">ID</label>
                        </div>
                        <div class="field-body">
                            <div class="field">
                                <div class="control">
                                    <input type="text" class="input" id="userid" aria-describedby="emailHelp"
                                        placeholder="My ID">
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="field is-horizontal">
                        <div class="field-label is-normal">
                            <label class="label" for="userpw">PW</label>
                        </div>
                        <div class="field-body">
                            <div class="field">
                                <div class="control">
                                    <input type="password" class="input" id="userpw" placeholder="My Password">
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="buttom">
                    <button class="button is-danger" onclick="login()">로그인</button>
                    <a href="{{ url_for('register')}}">
                        <button class="button is-danger" onclick="">회원가입</button>
                    </a>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

register.html

<!doctype html>
<html lang="en">
<head>
    <!-- Webpage Title -->
    <title>Hello, world!</title>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bulma CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css">
    <!-- JS -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script>
    <script>
        // 간단한 회원가입 함수입니다.
        // 아이디, 비밀번호, 닉네임을 받아 DB에 저장합니다.
        function register() {
            $.ajax({
                type: "POST",
                url: "/api/register",
                data: {
                    id_give: $('#userid').val(),
                    pw_give: $('#userpw').val(),
                    nickname_give: $('#usernick').val()
                },
                success: function (response) {
                    if (response['result'] == 'success') {
                        alert('회원가입이 완료되었습니다.')
                        window.location.href = '/login'
                    } else {
                        alert(response['msg'])
                    }
                }
            })
        }
    </script>
    <style>
        body {
            background-color: black;
            height: 100vh;
            width: 100vw;
        }
        .registerContainer {
            display: flex;
            flex-direction: column;
            height: 100vh;
            justify-content: center;
        }
        .titleContainer {
            display: flex;
            flex-direction: column;
            flex-wrap: wrap;
            align-content: center;
            justify-content: flex-start;
            align-items: flex-start;
            margin-bottom: 30px;
        }
        .title {
            color: white;
        }
        .inputForm {
            margin-bottom: 30px;
            /* display:inline-block; */
            width: 100vw;
            size: 30px 40px;   
        }
        .label {
            margin-bottom: 20px;
            display:inline-block;
            color: white;
        }
        .input {
            display:inline-block;
            width: 600px;
        }
    </style>
</head>
<body>
    <div class="registerContainer">
        <div class="section has-text-centered">
            <div class="titleContainer">
                <h1 class="title">회원가입을 위해</h1>
                <h1 class="title">정보를 입력해주세요.</h1>
            </div>
            <div class="inputForm">
                <div class="container" style="width:60%">
                    <div class="field is-horizontal">
                        <div class="field-label is-normal">
                            <label class="label" for="userid">ID</label>
                        </div>
                        <div class="field-body">
                            <div class="field">
                                <div class="control">
                                    <input type="text" class="input" id="userid" aria-describedby="emailHelp"
                                        placeholder="My ID" dir="ltr">
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="field is-horizontal">
                        <div class="field-label is-normal">
                            <label class="label" for="userpw">PW</label>
                        </div>
                        <div class="field-body">
                            <div class="field">
                                <div class="control">
                                    <input type="password" class="input" id="userpw" placeholder="My Password" dir="ltr">
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="field is-horizontal">
                        <div class="field-label is-normal">
                            <label class="label" for="usernick">NICKNAME</label>
                        </div>
                        <div class="field-body">
                            <div class="field">
                                <div class="control">
                                    <input type="text" class="input" id="usernick" placeholder="My Nickname" dir="ltr">
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <button class="button is-danger is-ligh" onclick="register()">회원가입</button>
        </div>
    </div>
</body>
</html>


# 세션과 토큰 [세션, 토큰, 쿠키에 관한 영상](https://youtu.be/tosLBcAX1vk?si=9gOXeCF8uoH0pGob) [세션, 토큰, JWT 에 관한 영상](https://youtu.be/1QiOXWEbqYQ?si=ZdEjBvxRMNuAIM3g) [로그인 파헤치기-세션,토큰,쿠키 정리 포스트](https://velog.io/@dbsdud143/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0)



profile
시간이 걸릴 뿐 내가 못할 건 없다.

0개의 댓글