본문 바로가기
SpringBoot

Spring-Boot Blog Project 3. 회원가입 화면 구현하기

by 김마리님 2020. 7. 20.

Spring-Boot Blog Project 2. nav, footer 제작

 

Spring-Boot Blog Project 2. nav, footer 제작

Spring-Boot Blog Project 1. 기본 세팅 Spring-Boot Blog Project 1. 기본 세팅 사용할 기본 Dependency STS에서 제공하지 않는 dependency를 추가한다. → dependency 추가 pom.xml 에 추가(jstl, jasper) java..

itstudy-mary.tistory.com

jsp로 블로그를 구현해본 사람은 알 것이다. 로그인을 하기 위해서는

 

1. 회원가입 화면으로 넘어가는 로직

2. 회원가입을 완료하기 위해 데이터를 넘기는 로직

 

이 두 개는 권한을 요구하지 않는다. 따라서, 앞으로 권한을 요구하는 컨텍스트는 /user을, 요구하지 않으면 /auth를 타고 가기로 한다.

 

먼저, 회원가입 화면을 구현한다.

https://itstudy-mary.tistory.com/119?category=919831

 

Servlet 과 JSP를 이용한(모델2 형식) 블로그 만들기(3) - 부트스트랩을 이용한 웹 폼 만들기

웹페이지를 만들다보면 CSS 덕분에 시간이 다 가는 경우가 있다. 우리는.. 일단 프론트엔드가 위주가 아니므로 편리한 부트스트랩을 이용하고자 한다. 부트스트랩은 트위터가 만든 프론트엔드 ��

itstudy-mary.tistory.com

 

※소스 코드 :

더보기

- joinForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%@ include file="../layout/header.jsp" %> 

<div class="container">
   <form class="was-validated" >
     
     <div class="form-group">
       <label for="username">Username:</label>
       <button type="button" id ="btn-username-check" class="btn btn-warning float-right">중복확인</button>
       <input type="text" id="username" class="form-control" placeholder="Enter username" required>
       <div class="valid-feedback">Valid.</div>
       <div class="invalid-feedback">Please fill out this field.</div>
     </div>
     
     <div class="form-group">
       <label for="pwd">Password:</label>
       <input type="password" id="password" class="form-control" placeholder="Enter password" required>
       <div class="valid-feedback">Valid.</div>
       <div class="invalid-feedback">Please fill out this field.</div>
     </div>
     
     <div class="form-group">
       <label for="email">Email:</label>
       <input type="email" id="email" class="form-control" placeholder="Enter Email" required>
       <div class="valid-feedback">Valid.</div>
       <div class="invalid-feedback">Please fill out this field.</div>
     </div>

     <button id="btn-save" type="button" class="btn btn-primary">회원가입완료</button>
   </form>
</div>

<script src="/js/user.js"></script>
<%@ include file="../layout/footer.jsp" %>

 

 

화면을 전환하는 로직은 간단하다. 그냥 GetMapping을 이용해 주소를 설정하고, 주소에 따른 페이지만 불러오면 된다.

 

IndexController.java

package com.mary.blog.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

	@GetMapping({"","/"})
	public String index() {
		return "index";
	}
	
	@GetMapping("auth/joinForm")
	public String joinForm() {
		return "user/joinForm";
	}
	
	@GetMapping("auth/loginForm")
	public String loginForm() {
		return "user/loginForm";
	}
}

 

기존 jsp에서는 post 방식을 이용해서 key-value 형태로 데이터를 전송했다. 그러나 이 방식은 리액트나 안드로이드에서 사용할 수 없다.

기존 jsp는,

다음과 같이 viewResolver를 이용하는데, 그래서 결과물을 html을 출력한다.

그러나, 안드로이드는 액티비티를 변경하면서 페이지를 바꾸고,

리액트는 데이터베이스에 요청할 일이 없으면 Node.js 서버에서 해결해버리기 때문에 데이터를 key-value 형태로 전송하는 것은 호환성이 좋지 않다. 따라서 json 형태로 데이터를 전송한다.

 

따라서 javascript로 json 형태로, ajax 통신을 한다.

단, javascript를 통해 데이터를 전송하려면 id를 통해 데이터를 전송해야하기 때문에 name을 삭제하고 id만 남겨두고도 된다.

물론 form action도 삭제해도 되고.

 

이제 javascript를 보자.

js는 static폴더에 만든다.

 

user.js

let index={
		//리스너
		init : function(){
			$("#btn-save").on("click",()=>{ //this 바인딩 할 필요 없이 바로 부모의 this를 찾음.
				//콜백
				this.save();
			});
		}, //이벤트 리스닝 바인딩 함수
		
		save : function(){
			let data={
					username:$("#username").val(),
					password:$("#password").val(),
					email:$("#email").val()		
			};
			
			$.ajax({
				type: "POST",
				url: "/auth/joinProc",
				data: JSON.stringify(data),
				contentType : "application/json; charset=utf-8", //스프링의 데이터 형식 인식 -> 오브젝트 변환
				dataType : "json"	
			}).done(function(resp){
				console.log(resp);
				alert("회원가입이 완료되었습니다.");
				location.href="/";
			}).fail(function(error){
				console.log(error);
				alert("회원가입 실패");
			});
		}, //이벤트 리스닝 실제 실행 함수
}

index.init();

이 스크립트를 걸어두면 아래의 index.init가 자동 실행된다.

init 함수는 리스너로서 다음 아이디가 가진 버튼이 클릭 되면 내부 함수를 실행되도록 한다.

이 때, 클릭하면 실행되는 함수는 익명 함수이기 때문에 화살표 함수를 사용해도 된다. 무엇보다도, 화살표 함수를 사용하면 this 함수가 함수 내부의 this를 가리키는 것이 아닌 함수 밖의, 부모 클래스의 this를 바인딩 하기 때문에 화살표 함수를 사용한다.

 

이제, 화살표 함수가 실행되면 진행될 실행함수를 제작하자.

data를 통해 받아온 값을 json 형태로 만든다. 이 때 내부의 값은 jQuery 문법으로, $(#아이디 이름).val(form 태그 내부 값을 찾는 함수)로 값을 받아온 후 ajax 통신으로 데이터를 넘긴다. 단, 데이터를 넘길 때 주의할 점이 있다.

data : JSON.stringify(data)

구문이 있다. 이 구문은 data가 아직 형태는 json의 형태여도 전송될때는 string 의 형태를 가지기 때문에 jsonString형태로 파싱해야한다.

또, 스프링은 데이터의 형태를 받아 파싱하기 때문에 넘겨줄 컨텐츠 타입을 지정하지 않으면 값을 오브젝트로 변환할 수 없다. 따라서, jsp와 다르게 반드시 데이터 형태를 지정해주어야 한다.

그리고 마지막으로 내가 받아올 형태도, 우리는 json형태로 통신할 것이기 때문에 json 형태로 받아온다.

 

데이터를 전송해줄 컨트롤러를 만들자.

 

- UserController.java

package com.mary.blog.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import com.mary.blog.controller.dto.CommonRespDto;
import com.mary.blog.model.User;
import com.mary.blog.service.UserService;

@Controller
public class UserController {

	@Autowired
	private UserService userService;
	
	@PostMapping("auth/joinProc")
	public @ResponseBody CommonRespDto<?> joinProc(@RequestBody User user) { //key-value 데이터가 아님
		userService.회원가입(user);
		return new CommonRespDto<String>(1,"회원 가입 성공");
	}
	
}

일단 리턴 형태는 보지 말고, 데이터가 어디로 전송되는지 확인해보자.

 

먼저 정보는 서비스 트랜젝션으로 이동한다.

package com.mary.blog.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mary.blog.model.User;
import com.mary.blog.repository.UserRepository;


//Controller, Repository(MyBatis를 쓰는 경우 MapperScan하기 때문에 선택),Configuration,Service(트랜젝션 호출),Component
//RestController,Bean

@Service //IoC
public class UserService {
	
	@Autowired
	private UserRepository userRepository; //DI
	
	@Transactional 
	public int 회원가입(User user) {
		try {
			userRepository.save(user);
			return 1;
		} catch (Exception e) {
			e.getMessage();
			return -1;
		}	
	}
	
}

try-catch를 통해 다음의 함수가 제대로 실행되면 1을 리턴, 제대로 실행되지 않으면 오류를 출력하며 -1을 리턴한다. 저 함수는 무엇일까? 저장소에 저장된, MyBatis가 가진 SQL의 아이디를 저장해둔 저장소의 함수들이다.

 

UserRepository.java

package com.mary.blog.repository;

import com.mary.blog.model.User;

public interface UserRepository {
	public void save(User user);
}

저장은 딱히 데이터베이스에서 무언가를 받아올 필요가 없어 리턴할 필요가 없으므로 void를 붙이고 save 함수를 xml에서 참조한다.

 

user.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.mary.blog.repository.UserRepository">
	<insert id="save">
		INSERT INTO user(username,password,email,createDate)
		VALUES (#{username}, #{password}, #{email}, now()) 
	</insert>
</mapper>

MyBatis는 참조한 오브젝트 값을 이름에 따라 변수에 차례대로 넣어준다. 따라서, 변수와 이름이 같아야 올바르게 파싱되어 들어간다. 아까 말했다시피 값을 리턴받아올 필요가 없으므로 resultType를 따로 지정할 필요는 없다.

 

따라서, 올바르게 동작했다면, try-catch 구문을 통해 repository-xml(MyBatis)로 접근해 데이터를 삽입하고, 성공하면 1을 리턴한다.

이 값은 컨트롤러로 돌아올 것이다.

 

이제 협업할 때를 생각해보자.

ajax를 resp할 값이 중구난방으로 있다면 이 코드는 좋은 코드가 아닐 것이다. 게다가 resp에 돌려받을때 json 타입으로 돌려받아야 하기 때문에 string의 형태가 아닌 object의 형태로 리턴받아야 한다. 

따라서, 리턴받을 오브젝트를 하나 더 생성한다.

 

CommonRespDto.java

package com.mary.blog.controller.dto;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CommonRespDto<T> {
	private int statusCode; // 1: 정상, -1 : 실패, 0 : 변경 안됨
	private T data;
}

이제 정상적으로 코드가 동작했다면 done, 코드가 동작 하지 않았다면 error 로직으로 이동한다.

반응형