프로젝트

Node.js - 로그인 기능 구현

GoF9490 2022. 8. 21. 14:27

진행 기간 : 22-08-15 ~ 22-08-19

소요 시간 : 하루 평균 2시간씩, 약 10시간 + @

깃허브 : https://github.com/GoF9490/Login-newAcc

 

사족

더보기

생활코딩 WEB2 Node.js-MySQL 강의를 듣고나서 Express 프레임 워크 강의로 들어가기전에 뭐라도 하나 만들어보자 싶어서 진행해보았다. 로그인 기능 구현 강의는 인터넷에 널리고 널려있지만, 그런걸 찾아보아서 따라하지 말고 최대한 아는 범위 내에서 해보고자 했다. 무식한 방식일 수도 있고, 결과물인 코드 또한 무식할지도 모르지만, 오히려 좋다.

 보시는 사람이 있을지는 모르지만, 밑의 무식한 코드를 보고 훈수를 던지고 싶은 마음이 가득하시다면 부디 그렇게 해주시길 부탁드립니다.

 

 

// 코드 리뷰

main.js

const http = require('http');
const url = require('url');
const account = require('./lib/account.js');

let app = http.createServer((request, response) =>{
    let _url = request.url;
    let queryData = url.parse(_url, true).query;
    let pathname = url.parse(_url, true).pathname;
    if (pathname === '/'){
        account.login(request, response);
    } else if (pathname === '/login_process'){
        account.login_process(request, response);
    } else if (pathname === '/newAcc'){
        account.newAcc(request, response);
    } else if (pathname === '/newAcc_process'){
        account.newAcc_process(request, response);
    } else if (pathname === '/findId'){

    } else if (pathname === '/findPW'){
        account.findPW(request, response);
    } else if (pathname === '/findPW_process'){
        account.findPW_process(request, response);
    } else {
        response.writeHead(404);
        response.end("Not Found");
    }
});
app.listen(3000);

 

변수타입은 var에서 let으로, 모듈은 const로 바꿔주었다. 그 이외에는 생활코딩 강의에서 크게 벋어난 것이 없다.

아이디찾기는 회원가입 단계에서 개인정보를 받아오는게 없으므로, 일단 미구현으로 놔두었다.

패스워드찾기는 아이디값과 질문, 답변을 통해 정보를 가져올 수 있다. 

 

 

template.js

let css = require('./template.css.js');

exports.login = ()=>{
    return  `
        <!DOCTYPE html>
        <html lang="ko">
        <head>
            <title>LoginPage</title>
            <meta charset="UTF-8">
            <style>
                ${css.login()}
            </style>
        </head>
        <body>
            <h1>Login</h1>
            <form action="/login_process" method="post">
                <p>ID</p>
                <input type="text" name="id" class="loginText">
                <p>PASSWORD</p>
                <input type="password" name="pw" class="loginText">
                <input type="submit" value="Login" id="loginButton">
            </form>
            <ul>
                <li class="findText"><a href="/newAcc">회원가입</a></li>
                <li class="findText"><a>아이디 찾기</a></li> 
                <li class="findText"><a href="/findPW">비밀번호 찾기</a></li>
            </ul>
        </body>
        </html>
        `;//네이버에 ::before 들어가있던데 뭐지?
}

exports.newAcc = ()=>{
    return `
    <!DOCTYPE html>
    <html lang="ko">
    <head>
        <meta charset="UTF-8">
        <title>newAcc</title>
        <style>
            ${css.newAcc()}
        </style>
    </head>
    <body>
        <div class="contents">
            <form action="/newAcc_process" method="post">
                <p class="textContent">ID : <input type="text" class="textBox" name="id" maxlength="20" placeholder="6자 이상"></p>
                <p class="textContent">Password : <input type="password" class="textBox" name="pw" maxlength="30" placeholder="8자 이상"></p>
                <p class="textContent">Nick Name : <input type="text" class="textBox" name="nickname" maxlength="20" placeholder="3글자 이상"></p>
                <p class="textContent">Age : <input type="number" class="textBox" name="age" maxlength="3"></p>
                <p class="textContent">
                    Gender : 
                    <input type="radio" id="man" name="gender" value="man" class="radio_gender" checked="checked"><label for="man">Man</label>
                    <input type="radio" id="woman" name="gender" value="woman"><label for="woman">Woman</label>
                </p>
                <p class="textContent">
                    Question : 
                    <select name="question" class="comboBox">
                        <option value="1">질문1</option>
                        <option value="2">질문2</option>
                        <option value="3">질문3</option>
                    </select>
                </p>
                <p class="textContent">Answer : <input type="text" class="textBox" name="answer" maxlength="30" placeholder="최대 30글자"></p>
                <div class="button">
                    <input type="submit" value="ok">
                    <input type="button" onclick="history.back()" value="back" style="float: right;">
                </div>
            </form>
        </div>
    </body>
    </html>
    `
}

exports.findPW = ()=>{
    return `
        <!DOCTYPE html>
        <html lang="ko">
        <head>
            <meta charset="UTF-8">
            <title>newAcc</title>
            <style>
                ${css.newAcc()}
            </style>
        </head>
        <body>
            <div class="contents">
                <form action="/findPW_process" method="post">
                    <p class="textContent">ID : <input type="text" class="textBox" name="id"></p>
                    <p class="textContent">
                        Question : 
                        <select name="question" class="comboBox">
                            <option value="1">질문1</option>
                            <option value="2">질문2</option>
                            <option value="3">질문3</option>
                        </select>
                    </p>
                    <p class="textContent">Answer : <input type="text" class="textBox" name="answer"></p>
                    <div class="button">
                        <input type="submit" value="ok">
                        <input type="button" onclick="history.back()" value="back" style="float: right;">
                    </div>
                </form>
            </div>
        </body>
        </html>
    `
}

로그인 기능에 쓰인 모든 html을 때려박은 무식한 템플릿 모듈이다. 3개의 화면이 전부이기에 굳이 나눌 필요가 있나 싶었다. 실제 로그인 화면은 어떻게 만들어지나 싶어 네이버 로그인화면을 검사 해봤는데 ::before 이란 요소를 사용하더라. 찾아보니 CSS쪽 테크닉 같은데, 나중에 프론트를 깊게 배울일이 생기면 한번 배워서 써먹어볼까 한다.

 

template.css

exports.login = ()=>{
    return `
    h1{
        margin-top: 200px;
        margin-bottom: 30px;
        text-align: center;
    }
    
    .loginText {
        margin: 0 auto;
        margin-bottom: 20px;
        display: block;
    }
    
    p {
        margin: 5px;
        text-align: center;
    }

    ul {
        padding: 0px;
        text-align: center;
    }
    
    #loginButton {
        margin: 0 auto;
        margin-top: 30px;
        display: block;
    }
    
    .findText {
        list-style: none;
        padding-left: 0px;
        display: inline;
        text-decoration: underline;
        padding: 5px;
    }
    `
}

exports.newAcc = ()=>{
    return `
    .contents {
        display: block;
        width: 500px;
        margin: 0 auto;
        margin-top: 300px;
    }

    .textContent {
        margin-left: 100px;
    }

    .textBox {
        float: right;
        margin-right: 100px;
    }

    .radio_gender {
        margin-left: 60px;
    }

    .comboBox {
        float: right;
        margin-right: 100px;
        width: 177px;
        height: 21px;
    }

    .button {
        width: 100px;
        margin: 0 auto;
    }
    `
}

생활코딩 강의에서는 템플릿에 css까지 이어서 쓰던데, 나는 한번 나누어보았다. CSS를 깊게 배운것도 아니고, 그러한 목적의 프로젝트도 아니기에 간단히 보기좋게만 만들었다.

 

맨 처음 보일 로그인화면

 

회원가입 화면

 

비밀번호찾기 화면

 

 

 

account.js

const template = require('./template');
const fs = require('fs');
const qs = require('querystring');
const alert_tem = require('./alert');
const db = require('./db.js');

exports.login = (request, response)=>{
    let html = template.login();

    response.writeHead(200);
    response.end(html);
}

exports.login_process = (request, response)=>{
    let body = '';
    request.on('data', (data)=>{
        body = body + data;
    });
    request.on('end', ()=>{
        let post = qs.parse(body);

        db.query(`SELECT ifnull(max(password), 0) AS password FROM account WHERE acc_id=?`,
        [post.id], (error, acc)=>{
            if (error) throw error;
            if (acc[0].password === post.pw){
                response.writeHead(200);
                response.end('success');
            } else {
                response.writeHead(200);
                response.end('fail');
            }
        });
    });
}

exports.newAcc = (request, response)=>{
    let html = template.newAcc();

    response.writeHead(200);
    response.end(html);
}

exports.newAcc_process = (request, response)=>{
    let body = '';
    request.on('data', (data)=>{
        body = body + data;
    });
    request.on('end', ()=>{
        let post = qs.parse(body);

        if (post.id.length < 6){
            response.write(alert_tem.text('아이디는 6자 이상이여야 합니다.'));
            response.end();
        } else if (post.pw.length < 8){
            response.write(alert_tem.text('패스워드는 8자 이상이여야 합니다.'));
            response.end();
        } else if (post.nickname.length < 3){
            response.write(alert_tem.text('닉네임은 3글자 이상이여야 합니다.'));
            response.end();
        } else if (post.age === ''){
            response.write(alert_tem.text('나이를 입력해주세요.'));
            response.end();
        } else if (post.answer.length < 1){
            response.write(alert_tem.text('선택한 질문에 대한 답변을 입력해주세요. 추후 비밀번호 분실시에 이용됩니다.'));
            response.end();
        } else {
            db.query(`INSERT INTO account 
            (acc_id, password, nickname, age, gender, date, question, answer) 
            VALUES(?, ?, ?, ?, ?, NOW(), ?, ?)`,
            [post.id, post.pw, post.nickname, post.age, post.gender, post.question, post.answer],
            (error, result)=>{
                if (error) throw error;
                response.writeHead(200);
                response.end('success');
            });
        }
    });
}

exports.findID = (request, response)=>{

}

exports.findPW = (request, response)=>{
    let html = template.findPW();

    response.writeHead(200);
    response.end(html);
}

exports.findPW_process = (request, response)=>{
    let body = '';
    request.on('data', (data)=>{
        body = body + data;
    });
    request.on('end', ()=>{
        let post = qs.parse(body);

        db.query(`SELECT ifnull(max(question), 0) AS question, answer, password FROM account WHERE acc_id=?`,
        [post.id], (error, result)=>{
            if (error) throw error;
            console.log(post);
            console.log(result[0]);
            if (result[0].question == post.question && result[0].answer === post.answer){
                response.writeHead(200);
                response.end(result[0].password);
            } else if (result[0].question === 0){
                response.write(alert_tem.text('존재하지 않는 아이디 입니다.'));
                response.end();
            } else {
                response.write(alert_tem.text('질문과 답이 올바르지 않습니다. 다시 확인해주세요.'));
                response.end();
            }
        });
    });
}

대망의 주요한 기능을 전부 때려박은 코드. 해당 코드를 기능별로 묶어서 모듈로 나눠볼까 했지만, 갯수도 적고 나누는 기준도 감이 안잡혔기에 그냥 때려박았다. 나눈다면 아이디찾기랑 패스워드찾기를 묶어서 빼는게 맞는거같은데, 아이디찾기는 구현을 안했기에 따로 빼기도 뭐해서 놔두었다. 이에대해 피드백을 준다면 나중에 리팩토링할때 참고하겠다.

 

이 코드를 구현하면서 db의 IFNULL 문을 처음 배웠고, 아이디가 존재하지 않아 조회되지 않을 때 사용되도록 해보았다. 실제 로그인 기능도 이런 방식인지는 모르겠지만, 나의 해결과정은 'where 조회된 데이터가 없을때' 같은 키워드로 검색을 했고, 거기서 해당 문법을 배워서 적용, 사용했을때 문제없이 원하는 결과값을 얻을 수 있었다.

 

처리 후 결과값으로 리다이렉트 할만한 마땅한 창들이 없어서 단순히 success나 fail, 변수출력 같은 화면으로 마무리되는 식이다. 더 이쁘게 만들수도 있겠지만, 백엔드라면 어짜피 json파일로 마무리 되는것으로 알고있어서, 아니라면 훈수 환영한다.

 

사실 위의 코드는 db대신 fs를 사용하는 방식으로 이미 완성된 코드였지만, 이대로 올리기에는 좀 허접한것 같아서 db를 사용하는 방식으로 변경했다. 소요시간에서 +@ 부분이 바로 이것. 맨위의 깃허브 링크에는 fs방식으로 db대신 json파일을 사용하는 코드로 작성되어있다. 해당 글에 올려진 코드 말고도 html의 css를 만들때 미리보기로 사용된 더미코드라던가, alert_tem 모듈이라던가, 개인 사족 txt등도 있으니, 보고서 훈수 두실 분들은 환영.

 

앞으로 배울것 / 일정

더보기

사족에서 언급했지만, 일단 express를 배우려고 한다. express를 훓고, 프레임워크를 맛 본 후 스프링 프레임워크를 배울 예정이다. 이러한 빌드업 과정에는 생활코딩 강의가 큰 지분을 차지하고있지 않을까. 

 

티스토리도 한번 꾸며보고싶지만, 당분간은 기초 다지는것도 바쁠 것 같다. 우선순위가 낮으니 미루고 미루다 결국 안꾸밀지도. 어짜피 튜닝의 끝은 순정이다.

 

글은 매주 주말에, 안된다면 다음주 월요일에 올릴 예정이긴 한데, 토이 프로젝트를 매 주마다 진행할 것은 아니라 어떤 글을 올릴지 고민된다. 알고리즘 문제 풀이라도 올릴까 생각중이다. 확실한건, 만약 프로젝트가 진행중이라면, 무조건 글을 올릴 생각이다. 훈수 환영한다.