TIL(46일차)

김규현·2022년 11월 9일
0

📝 오늘 배운 것

지난 번 포스팅한 글에서 완성한 회원가입 로직에서 문제가 발생했다.
로컬 환경에서 개발할 때는 크게 문제가 없었는데, 다른 팀원들과 코드를 합치면서 문제가 발생했다.

원래 회원가입 진행 중 정규표현식 검사에서 걸리면 사용자가 통과할 때 까지 다음으로 넘어갈 수 없는데
사용자가 아이디 공백 및 유효성 검사를 마친 후 비밀번호를 잘못 입력했음에도 불구하고 입력한 아이디, 비밀번호 값으로 서버에 요청을 하여 status code가 201이 나와 db에 유저가 생성 되어버리는 문제가 발생했다.

원인을 찾아보니 javascript의 비동기 처리 방식 때문에 발생한 것으로 보여졌고, 현재 버튼을 눌렀을 때 api.js 파일에서 handleSignup 함수가 실행되고 사용자의 입력 값을 받아 서버로 fetch 요청을 보내어 status code가 201이 나오면 db에 유저가 생성되는 로직이다.

그리고 signup.js 파일에서 해당 버튼에 addEventListner로 정규표현식을 사용하여 사용자의 입력 값의 유효성을 검사하는 로직을 추가해서 사용자가 입력한 값을 서버로 보내기 전 입력하는 단계에서 유효성 검사를 먼저 하게 되는데 비동기 처리 방식 때문에 signup.js에서 유효성 검사를 진행하는 동안 api.js의 로직이 끝까지 실행되어 이러한 버그가 발생해버리는 것이었다.

현재 회원가입 로직은 아래 코드와 같다.

html

<h1>회원가입 페이지</h1>
    <form>
        <div class="mb-3">
            <label for="exampleInputEmail1" class="form-label">아이디</label>
            <input type="text" class="form-control" id="username" aria-describedby="emailHelp">
        </div>
        <div class="mb-3">
            <label for="exampleInputPassword1" class="form-label">비밀번호</label>
            <input type="password" class="form-control" id="password">
        </div>
        <div class="mb-3">
            <label for="exampleInputPassword1" class="form-label">비밀번호 재확인</label>
            <input type="password" class="form-control" id="password2">
        </div>
        <div class="file mb-3">
            <label class="img" for="inputGroupFile01" style="margin-bottom: 5px;">프로필 사진</label>
            <input type="file" class="form-control" id="profile_img">
        </div>
        <div class="mb-3">
            <label for="exampleInputEmail1" class="form-label">프로필 소개</label>
            <input type="text" class="form-control" id="introduce" aria-describedby="emailHelp">
        </div>
        <div class="mb-3">
            <label for="exampleInputEmail1" class="form-label">관심있는 장르</label>
            <input type="text" class="form-control" id="favorite" aria-describedby="emailHelp">
        </div>
        <button type="button" class="btn btn-primary" id='signupbtn' onclick="handleSignup()" style="display: block; width: 150px; margin: auto; margin-top: 50px;">가입하기</button>
    </form>

api.js

// 전역 변수 //
const backend_base_url = 'http://127.0.0.1:8000/'
const frontend_base_url = 'http://127.0.0.1:5500/'

async function handleSignup() {
    const username = document.getElementById('username').value
    const password = document.getElementById('password').value
    const profile_img = document.getElementById('profile_img').files[0]
    const introduce = document.getElementById('introduce').value
    const favorite = document.getElementById('favorite').value
    
    const formData = new FormData()
    formData.append("username", username)
    formData.append("password", password)
    formData.append("profile_img", profile_img)
    formData.append("introduce", introduce)
    formData.append("favorite", favorite)
    
    const response = await fetch(`${backend_base_url}users/sign-up/`, {
        headers: {
        },
        method: 'POST',
        body: formData
    })
    console.log(response)

    if (response.status == 201) {
        alert('가입 완료!')
        window.location.replace(`${frontend_base_url}login.html`)
    } else {
        alert("잘못된 접근입니다.", response.status)
    }
}

signup.js

let username = document.querySelector('#username')
let password = document.querySelector('#password')
let passwordCheck = document.querySelector('#password2')
let introduce = document.querySelector('#introduce')
let favorite = document.querySelector('#favorite')
let btn = document.querySelector('#signupbtn')

btn.addEventListener('click', () => {
    // 아이디 정규식 (4~12자의 영문 대소문자와 숫자)
    var userCheck= RegExp(/^[a-zA-Z0-9]{4,12}$/); 
    // 패스워드 정규식 (영문 대문자와 소문자, 숫자, 특수문자를 하나 이상 포함하여 8~16)
    var passwdCheck = RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^*()\-_=+\\\|\[\]{};:\'",.<>\/?]).{8,16}$/);

    // 아이디 유효성 검사
    if (username.value == ''){
        alert('아이디를 입력해주세요!')
        username.focus();
        return false;
    }
    else if(!userCheck.test(username.value)){
        alert("아이디는 4~12자의 영문 대소문자와 숫자로만 입력해주세요!");
        username.focus();
        return false;
    }
    // 비밀번호 공백 검증
    if (password.value == ''){
        alert('비밀번호를 입력해주세요!')
        password.focus();
        return false;
    }

    // 비밀번호 유효성 검사
    else if(!passwdCheck.test(password.value)){
        alert("비밀번호는 영문 대문자와 소문자, 숫자, 특수문자를 하나 이상 포함하여 8~16자로 입력해주세요!");
        password.focus();
    }
    // 비밀번호 재확인 공백 검증
    else if (passwordCheck.value == ''){
        alert('비밀번호 재확인을 해주세요!')
        passwordCheck.focus();
        return false;
    }
    // 비밀번호와 비밀번호 재확인 일치 여부 검증
    else if (password.value != passwordCheck.value){
        alert('비밀번호가 일치하지 않습니다!')
        passwordCheck.focus();
        return false;
    }
    // 아이디 비밀번호 값 중복 검증
    else if (username.value == password.value){
        alert('아이디와 비밀번호는 같을 수 없습니다!')
        password.focus();
        return false;
    }
})

유효성 검사를 통과하기 전 제출이 되어버리는 버그를 해결하기 위해 이것 저것 구글링 해보다가 콜백 함수가 있다는 것을 알게되었고, 콜백 함수를 사용해서 이 문제를 해결 해보려고 했으나 signup.js가 다른 파일이고, addEventListner로 함수가 실행되다 보니 쉽지 않았다.

결국 튜터님께 여쭤보니 굳이 js 파일을 두 개로 나눌 필요가 있냐며 그냥 하나의 js 파일에서 회원가입을 진행하는 로직과 유효성 검사를 하는 로직을 한 번에 작성하는게 좋을 것 같다고 하셨다.

튜터님의 솔루션이 너무 간단해서 당황했지만 하나의 js 파일에서 회원가입 로직과 유효성 검사하는 로직을 하나의 함수에서 처리하니 유효성 검사를 마치기 전 제출은 되지 않았지만 또 다른 문제가 발생했다.

백엔드의 user 모델에서 이미지필드는 blank=True, null=True로 설정해서 필수 값이 아니지만 프론트에서 이미지를 넣지 않고 제출했을 때 아래와 같은 에러가 발생한다.(이미지를 넣었을 때는 정상적으로 가입이 된다)

profile_img의 id로 2개의 변수를 만들어 이미지가 있을때는 document에서 id값을 가져와 files로 form_profile_img 라는 변수를 사용하고, 이미지가 없을 때는 다른 필드와 같이 value로 파일이 아닌 문자열의 값을 받을 수 있도록 profile_img라는 변수를 선언했다.

그리고 formData에서 formData가 undefined가 아닐때는 파일 타입의 변수로 값을 받고, 반대인 경우 문자열로 값을 받음으로서 프로필 이미지를 넣거나 안넣거나 선택할 수 있도록 로직을 구현했다.

	const form_username = document.getElementById('username').value
    const form_password = document.getElementById('password').value
    const form_profile_img = document.getElementById('profile_img').files[0]
    const profile_img = document.getElementById('profile_img').value
    const form_introduce = document.getElementById('introduce').value
    const form_favorite = document.getElementById('favorite').value

    const formData = new FormData()
    formData.append("username", form_username)
    formData.append("password", form_password)
    if (form_profile_img != undefined) {
        formData.append("profile_img", form_profile_img)
    } else {
        formData.append('profile_img', profile_img)
    }
    formData.append("introduce", form_introduce)
    formData.append("favorite", form_favorite)

    const response = await fetch(`${backend_base_url}/users/sign-up/`, {
        headers: {},
        method: "POST",
        body: formData
    })
profile
웹개발 회고록

0개의 댓글