AWS Back Day 76. "Spring Boot 비동기 도서관리 시스템에서 회원가입과 이메일 중복확인 처리

이강용·2023년 4월 19일
0

Spring Boot

목록 보기
11/20

CORS (Cross-origin-resource-sharing)

콜백함수(CallBack Function)

파라미터로 함수를 전달하는 함수

import React, { useState } from 'react';

const Callback = () => {

    const [ cnt, setCnt] = useState(0); 
    let count1 = 0;

    const a =(fx, fx2) => {

        console.log("A함수 실행");
        setCnt(() => fx(fx2));
    }


    const b = (fx2) => {
        console.log("B함수 실행");
        count1 = cnt + 100;
        fx2();
        return count1;
    }


    const c = () => {
        console.log("C함수 호출");
        console.log(count1);
    }

    const clickHandler = () => {
        a(b, c);
    }

    return (
        <div>
            <button onClick={clickHandler}>버튼</button>
        </div>
    );
};

export default Callback;
  • Callback 컴포넌트를 불러올 때, useState를 사용하여 cnt라는 상태 변수를 초기화하고, 상태를 변경하기 위한 setCnt함수를 생성, count1이라는 변수도 초기화
  • 사용자가 UI에 표시된 버튼을 클릭하면, clickHandler함수가 호출
  • clickHandler함수 내에서 a 함수가 호출되며, b함수와C함수를 인자로 전달
  • a 함수 내에서 console A함수 실행 출력 -> setCnt를 호출하여 상태 변경, 이때, b함수가 인자로 c 함수를 전달받아 호출
  • b 함수 내에서 console B함수 실행 출력 -> count1변수에 cnt 상태 값에 100을 더한 값을 할당, c 함수 호출
  • c 함수 내에서 console C함수 호출과 함께 count1 값 출력
  • b 함수는 count1값을 반환하고, setCnt함수는 이 값을 사용해 상태를 변경, 상태가 변경되면 컴포넌트가 다시 렌더링 됨

결과
버튼

Promise

비동기 작업의 결과를 나타내는 객체로, 작업이 성공적으로 완료되거나 실패한 후에 실행될 콜백 함수를 관리

import React from 'react';

const PromiseStudy = () => {


    const a = new Promise((resolve, reject) =>{
        console.log("프로미스 호출");
        resolve("A함수 resolve 결과 리턴")

    });

    const clickHandler = () => {
        a.then(b);
    }

    const b = (str) => {
        console.log(str);
    }


    return (
        <div>
            <button onClick={clickHandler}>버튼</button>
        </div>
    );
};

export default PromiseStudy;

import React from 'react';

const PromiseStudy = () => {


    const a = new Promise((resolve, reject) =>{
        console.log("프로미스 호출");

        if(1 !== 1){
            resolve();

        }else {
            reject(new Error("오류입니다."));
        }

    });

    const clickHandler = () => {
        a.then(() => {
            console.log("1번 then 호출");
            return new Promise((resolve, reject) =>{
                resolve("리턴!!!");
            });
        })
        .catch((error) =>{
            console.log(error);
        })
        .then(b);
    }

    const b = (str) => {
        console.log(str);
    }


    return (
        <div>
            <button onClick={clickHandler}>버튼</button>
        </div>
    );
};

export default PromiseStudy;

도서 관리 시스템 에러 처리 (비동기)

Register.js (수정)

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
import React, { useState } from 'react';

import LoginInput from '../../components/UI/Login/LoginInput/LoginInput';
import { FiUser, FiLock } from 'react-icons/fi';
import { Link } from 'react-router-dom';
import {BiRename} from 'react-icons/bi';
import axios from 'axios';

;


const container = css`
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 70px 30px;
`;

const logo = css`
    margin: 50px 0px;
    font-size: 34px;
    font-weight: 600;

`;


const mainContainer = css`
    display: flex;
    flex-direction: column;
    align-items: center;
    border: 1px solid #dbdbdb;
    border-radius: 10px;
    padding: 40px 20px;
    width: 400px;
`;

const authForm = css`
  width: 100%;

`;

const inputLabel = css`
    margin-left: 5px;
    font-size: 12px;
    font-weight: 600;
`;



const loginButton = css`
    margin: 10px 0px ;
    border: 1px solid #dbdbdb;
    border-radius: 7px;
    width: 100%;
    height: 50px;
    background-color: white;
    font-weight: 900;
    cursor: pointer;
    &:hover {
        background-color: #fafafa;
    }
    &:active {
        background-color: #eee;
    }
`;





const signupMessage = css`
    margin-top: 20px;
    font-size: 14px;
    font-weight: 600;
    color: #777;
`;

const register = css`
    margin-top: 10px;
    font-weight: 600;
`;

const errorMsg = css`
    margin-left: 5px;
    margin-bottom: 20px;
    font-size: 12px;
    color:red;
`;


const Register = () => {

    const [registerUser, setRegisterUser]  = useState( { email:"",password:"",name:"" } );
    const [errorMessages, setErrorMessages]  = useState( { email: "", password: "" , name: "" } );
   
    const onChangeHandle = (e) => {
        const { name, value } = e.target;
        setRegisterUser( { ...registerUser, [name]:value } );
    }

    const registeSubmit = async() => {
        const data = {
            ...registerUser
        }

        const option = {
            headers: {
                "Content-Type" : "application/json"
            }
        }

        try{
            const response = await axios.post("http://localhost:8080/auth/signup", JSON.stringify(data), option);
            setErrorMessages({email: "", password: "" , name: ""}); //빈값 ( 로그인 성공 시, error 메시지 뜨지않음 )
            
        }catch(error){
            setErrorMessages({email: "", password: "" , name: "",...error.response.data.errorData}); //객체 (error.response.data.errorData)
        }
        
        // .then( response => {
        //     setErrorMessages({email: "", password: "" , name: ""}); //빈값 ( 로그인 성공 시, error 메시지 뜨지않음 )
        //     console.log(response);
        // })
        // .catch(error => {
        //     setErrorMessages({email: "", password: "" , name: "",...error.response.data.errorData}); //객체 (error.response.data.errorData)
        // });
    }


    return (
        <div css= {container}>
            <header>
                <h1 css= { logo } >SIGN UP</h1>
            </header>
            <main css={ mainContainer }>
                <div css={authForm}>
                    <label css={ inputLabel }>Email</label>
                    <LoginInput type="email" placeholder="Type your email" onChange={onChangeHandle} name="email">
                        <FiUser />
                    </LoginInput>
                    <div css={errorMsg}>{errorMessages.email}</div>

                    <label css={ inputLabel }>Password</label>
                    <LoginInput type="password" placeholder="Type your password" onChange={onChangeHandle} name="password">
                        <FiLock />
                    </LoginInput>
                    <div css={errorMsg}>{errorMessages.password}</div>

                    <label css={ inputLabel }>Name</label>
                    <LoginInput type="text" placeholder="Type your name" onChange={onChangeHandle} name ="name">
                        <BiRename />
                    </LoginInput>
                    <div css={errorMsg}>{errorMessages.name}</div>

                    <button css={ loginButton } onClick={registeSubmit}>REGISTER</button>
                </div>

                
                <div></div>
                
            </main>

            <div css= { signupMessage }>Already a user?</div>

            <footer>
                <div css = { register }><Link to="/login">LOGIN</Link></div>
            </footer>
        </div>
    );
};

export default Register;

등록을 정상 입력 했을 때

Email, Password, Name 중 잘못 입력 했을 때

async, await

  • 비동기 작업을 간편하게 처리하도록 도와주는 JavaScript의 문법
    async는 함수를 비동기로 선언하며, await는 Promise를 기다리는 동안 코드 실행을 일시 중단함
const registeSubmit = async() => {
        const data = {
            ...registerUser
        }

        const option = {
            headers: {
                "Content-Type" : "application/json"
            }
        }

        try{
            const response = await axios.post("http://localhost:8080/auth/signup", JSON.stringify(data), option);
            setErrorMessages({email: "", password: "" , name: ""}); //빈값 ( 로그인 성공 시, error 메시지 뜨지않음 )
            
        }catch(error){
            setErrorMessages({email: "", password: "" , name: "",...error.response.data.errorData}); //객체 (error.response.data.errorData)
        }
        
        // .then( response => {
        //     setErrorMessages({email: "", password: "" , name: ""}); //빈값 ( 로그인 성공 시, error 메시지 뜨지않음 )
        //     console.log(response);
        // })
        // .catch(error => {
        //     setErrorMessages({email: "", password: "" , name: "",...error.response.data.errorData}); //객체 (error.response.data.errorData)
        // });
    }

Backend 단

SpringBoot

Email 중복확인

service > AuthenicationService

package com.toyproject.bookmanagement.service;

import org.springframework.stereotype.Service;

import com.toyproject.bookmanagement.entity.User;
import com.toyproject.bookmanagement.exception.CustomException;
import com.toyproject.bookmanagement.exception.ErrorMap;
import com.toyproject.bookmanagement.repository.UserRepository;

import lombok.RequiredArgsConstructor;



@Service
@RequiredArgsConstructor
public class AuthenticationService {
	
	private final UserRepository userRepository;
	
	public void checkDuplicatedEmail(String email) {
		
		User userEntity = userRepository.findUserByEmail(email);
		if(userEntity != null) {
			
			throw new CustomException("Duplicated Email",
					ErrorMap.builder().put("email", "사용중인 이메일입니다.").build());
			
		}
	}
}

repository > UserRepository

package com.toyproject.bookmanagement.repository;

import org.apache.ibatis.annotations.Mapper;

import com.toyproject.bookmanagement.entity.User;

@Mapper
public interface UserRepository {
	public User findUserByEmail(String email);
}

entitiy > Authority, Role, User

Authority

package com.toyproject.bookmanagement.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Builder
@NoArgsConstructor
@Data
public class Authority {
	private int authorityId;
	private int userId;
	private int roleId;
	
	
	private Role role;
	
}

Role

package com.toyproject.bookmanagement.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor


public class Role {
	private int roleId;
	private String roleName;
}

User

package com.toyproject.bookmanagement.entity;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
	private int userId;
	private String email;
	private String password;
	private String name;
	
	private List<Authority> authorities;
}

resources > mappers

mappers > 오른쪽 마우스 클릭 > New > Other >

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.toyproject.bookmanagement.repository.UserRepository">



	<resultMap type="com.toyproject.bookmanagement.entity.User" id="userMap">
		<id property="userId" column="user_id"/>
		<result property="email" column="email"/>
		<result property="password" column="password"/>
		<result property="name" column="name"/>
		<result property="provider" column="provider"/>
		<collection property="authorities" javaType="list" resultMap="authorityMap" />
	</resultMap>
	<resultMap type="com.toyproject.bookmanagement.entity.Authority" id="authorityMap">
		<id property="authorityId" column="authority_id"/>
		<result property="userId" column="user_id"/>
		<result property="roleId" column="role_id"/>
		<association property="role" resultMap="RoleMap"></association>
	
	</resultMap>
	<resultMap type="com.toyproject.bookmanagement.entity.Role" id="RoleMap">
		<id property="roleId" column="role_id"/>
		<result property="roleName" column="role_name"/>
	</resultMap>

	<select id="findUserByEmail" resultMap="userMap">
	
		select
			ut.user_id,
			ut.email,
			ut.password,
			ut.name,
			ut.provider,
		
			at.authority_id,
			at.user_id,
			at.role_id,
			
			rt.role_id,
			rt.role_name
		from
			user_tb ut
			left outer join authority_tb at on(at.user_id = ut.user_id)
			left outer join role_tb rt on(rt.role_id = at.role_id)
		where
			ut.email = #{email}

	</select>


</mapper>
  • repository > UserRepositoty 경로 복사

exception > ErrorMap(클래스 생성)

package com.toyproject.bookmanagement.exception;

import java.util.HashMap;
import java.util.Map;

public class ErrorMap {
	
	
	private Map<String, String> errorMap;
	
	private ErrorMap() {
		errorMap = new HashMap<>();
		
	}
	
	public static ErrorMap builder() {
		return new ErrorMap();
	}
	
	public ErrorMap put(String key, String value) {
		errorMap.put(key, value);
		return this;
	}
	
	public Map<String, String> build(){
		return errorMap;
	}

}

controller > AuthenticationController (수정)

package com.toyproject.bookmanagement.controller;

import javax.validation.Valid;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.toyproject.bookmanagement.aop.annotation.ValidAspect;
import com.toyproject.bookmanagement.dto.auth.SignupReqDto;
import com.toyproject.bookmanagement.service.AuthenticationService;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthenticationController {
	
	private final AuthenticationService authenticationService;
	
	
	
	@PostMapping("/login")
	public ResponseEntity<?> login() {
		return ResponseEntity.ok(null);
	}
	
	@CrossOrigin
	@ValidAspect
	@PostMapping("/signup")
	public ResponseEntity<?> signup(@Valid @RequestBody SignupReqDto signupReqDto, BindingResult bindingResult) {
		authenticationService.checkDuplicatedEmail(signupReqDto.getEmail());
		return ResponseEntity.ok(null);
	}
}

MySQL에서 Select문 확인해보기

결과

회원가입

entity > User

User

package com.toyproject.bookmanagement.entity;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
	private int userId;
	private String email;
	private String password;
	private String name;
	private String provider;
	
	private List<Authority> authorities;
}

repository > UserRepository

UserRepository (interface)

package com.toyproject.bookmanagement.repository;

import org.apache.ibatis.annotations.Mapper;

import com.toyproject.bookmanagement.entity.User;

@Mapper
public interface UserRepository {
	// 이메일 중복확인
	public User findUserByEmail(String email);
	
	// 유저 등록
	public int saveUser (User user);
}
  • @Mapper는 MyBatis에서 사용되는 어노테이션으로, 해당 인터페이스를 데이터베이스 매퍼로 표시하며 SQL 쿼리와 자바 메서드를 매핑하기 위해 사용

dto > auth > SignupReqDto

package com.toyproject.bookmanagement.dto.auth;

import javax.validation.constraints.Email;
import javax.validation.constraints.Pattern;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import com.toyproject.bookmanagement.entity.User;

import lombok.Data;

@Data
public class SignupReqDto {
	@Email
	private String email;
	@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,16}$",
			message = "비밀번호는 영문자, 숫자, 특수문자를 포함하여 8 ~ 16 글자로 작성하세요.")
	private String password;
	
	@Pattern(regexp = "^[가-힣]{2,7}$",
			message = "한글이름만 작성 가능합니다.")
	private String name;
	
	
	public User toEntity() {
		
		 return User.builder()
				.email(email)
				.password(new BCryptPasswordEncoder().encode(password))
				.name(name)
				.build();
	}
}

service

AuthenticationService

package com.toyproject.bookmanagement.service;

import org.springframework.stereotype.Service;

import com.toyproject.bookmanagement.dto.auth.SignupReqDto;
import com.toyproject.bookmanagement.entity.User;
import com.toyproject.bookmanagement.exception.CustomException;
import com.toyproject.bookmanagement.exception.ErrorMap;
import com.toyproject.bookmanagement.repository.UserRepository;

import lombok.RequiredArgsConstructor;



@Service
@RequiredArgsConstructor
public class AuthenticationService {
	
	private final UserRepository userRepository;
	
	public void checkDuplicatedEmail(String email) {
		
		User userEntity = userRepository.findUserByEmail(email);
		if(userEntity != null) {
			
			throw new CustomException("Duplicated Email",
					ErrorMap.builder().put("email", "사용중인 이메일입니다.").build());
			
		}
	}
	
	public void signup(SignupReqDto signupReqDto) {
		
		User userEntity = signupReqDto.toEntity();
		userRepository.saveUser(userEntity);
		
	}
}

resources > mappers

UserMapper.xml (추가)

<insert id="saveUser" parameterType="com.toyproject.bookmanagement.entity.User">
		insert into user_tb
		values (0, #{email},#{password},#{name},#{provider})
</insert>
  • MyBats를 사용하여 데이터베이스에 새 사용자를 추가하는 sql 쿼리를 정의하는 코드
  • parameterTyp은 쿼리에 전달되는 파라미터의 타입을 지정
  • insert into user_tbuser_tb 테이블에 새 레코드를 추가하는 sql 쿼리
  • values (0, #{email},#{password},#{name},#{provider})는 추가할 레코드의 값을 지정, 0은 자동 증가되는 primary key를 의미하며 #{email},#{password},#{name},#{provider} 는 각각 전달된 User의 객체의 email, password, name, provider 속성을 나타냄
  • 즉, entity,User의 객체를 전달받아 user_tb테이블에 새 사용자를 레코드를 추가하는 sql 쿼리를 정의

controller

AuthenticationController (추가)

package com.toyproject.bookmanagement.controller;

import javax.validation.Valid;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.toyproject.bookmanagement.aop.annotation.ValidAspect;
import com.toyproject.bookmanagement.dto.auth.SignupReqDto;
import com.toyproject.bookmanagement.service.AuthenticationService;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/auth") 
@RequiredArgsConstructor
public class AuthenticationController {
	
	private final AuthenticationService authenticationService;
	
	
	@PostMapping("/login")
	public ResponseEntity<?> login() {
		return ResponseEntity.ok(null);
	}
	
	@CrossOrigin
	@ValidAspect
	@PostMapping("/signup")
	public ResponseEntity<?> signup(@Valid @RequestBody SignupReqDto signupReqDto, BindingResult bindingResult) {
		authenticationService.checkDuplicatedEmail(signupReqDto.getEmail());
		authenticationService.signup(signupReqDto);
		return ResponseEntity.ok(null);
	}
}
profile
HW + SW = 1

0개의 댓글