본문 바로가기

개발/프로젝트

[스프링부트 게시판] 15. 회원가입 기능 구현

이번 게시글에서는 회원가입 페이지를 이용한 회원가입 기능 구현에 대해 설명 드리고자 합니다.

 

그 전에 먼저 전에 13. 페이지 공통 부분 분리에 대해서 몇 가지 수정을 설명하고 이어 말씀하도록 하겠습니다.


이전에 페이지 공통 부분으로 Header와 Footer를 공통으로 잡아 해결했지만 Header와 Footer에 대한 공통 부분은 계속 반복적으로 코드를 작성하기 때문에 순수하게 안에 내용만을 바꾸고 싶은 레이아웃을 만들고 싶었습니다. 

 

[계속 반복해야하는 코드]

<header th:replace="layouts/header::header"></header>

[내부 내용]

<footer th:replace="layouts/footer::footer"></footer>

 

먼저 thymeleaf에서 제공하는 태그인 thymeleaf-layout-dialect를 사용하겠습니다. 먼저 pom.xmldependency로 이 코드를 추가시킵니다.

 

 

Thymeleaf Page Layouts - Thymeleaf

Summary In this article, we described many ways of achieving the same: layouts. You can build layouts using Thymeleaf Standard Layout System that is based on include-style approach. You also have powerful Layout Dialect, that uses decorator pattern for wor

www.thymeleaf.org

 

[pom.xml]

<dependency>
    <groupId>nz.net.ultraq.thymeleaf</groupId>
    <artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>

jar파일 생성
layout.html

이 기능은 이전에 Header나 Footer를 계속 반복으로 생성하지 않도록 하는 레이아웃 기능 중에 하나입니다. 추가된 것을 확인이 되었다면 이전에 layouts의 폴더 안에 layout.html을 생성합니다.

 

 

[layout.html]

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">
</head>
<body>
<header th:replace="layouts/header::header"></header>
<th:block layout:fragment="content"></th:block>
<br>
<footer th:replace="layouts/footer::footer"></footer>
</body>
<script th:each="script : ${addScript}" th:src="@{/js/{js}.js(js=${script})}"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.1/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
</html>

 

html 태그xmlns:layout 에 대한 URL을 작성합니다. 여기에서 공통부분을 제외한 다른 부분을 바꾸는 것을 th:block 태그를 이용하여 만듭니다. 여기서 공통 CSS나 JS로 이용한 것을 모아서 할 수 있으며 다르게 제작하는 부분은 이와 같이 작성을 하시면 됩니다. 가변되는 내용에 해당하는 contentlayout:fragment 태그를 추가하여 작성을 합니다.

 

[joinForm.html]

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
		xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{layouts/layout}">
		
<th:block layout:fragment="content">
<div class="container" style="margin-top:30px">
  	<form action="/action_page.php">
	  <div class="form-group">
	    <label for="username">Username:</label>
	    <input type="text" class="form-control" placeholder="Enter email" id="username">
	  </div>
	  <div class="form-group">
	    <label for="password">Password:</label>
	    <input type="password" class="form-control" placeholder="Enter password" id="password">
	  </div>
	  <div class="form-group">
	    <label for="email">Email address:</label>
	    <input type="email" class="form-control" placeholder="Enter email" id="email">
	  </div>
	  <button type="submit" class="btn btn-primary">Submit</button>
	</form>
</div>
</th:block>

</html>

 

가변되는 내용에 해당하는 중요한 점은 html 태그에 layout:decorate를 집어넣어야 작동이 됩니다. 이전에 Header나 Footer가 th:replace로 해당하는 파일의 경로를 갖고와 사용하는 것 처럼 이 또한 해당하는 경로를 표시하는 것입니다. 이 후 layout:fragment에 설정된 이름을 불러와 '이 위치에 해당하는 페이지를 구성할 것이다!' 로 표현되는 것입니다.

 

Result

 

수정 후 정상적으로 layout을 통한 페이지가 구성 된 것을 확인 하실 수 있습니다.


본격적으로 회원가입 기능 구현에 대해 설명 드리겠습니다.

 

먼저 UserController 클래스 파일에 자바스크립트를 위한 script를 생성합니다.

 

UserController

 

자바스크립트(JS) 파일을 불러오기 위해 생성하였습니다. 그런데 어떻게 하면 저렇게 경로를 잡아도 되는지에 대해서 먼저 설명 드려야 할 것이 있습니다.

 

[MvcConfiguration.java]

@Configuration
public class MvcConfiguration implements WebMvcConfigurer {

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/**")
					.addResourceLocations("classpath:/templates/", "classpath:/static/");
	}
	
}

 

이전 application.properties를 만들면서 생성했던 MvcConfiguration 클래스 파일입니다. 이 파일은 스프링 MVC에 대한 설정을 하는 WebMvcConfigurer를 불러와 해당 웹 페이지 설정을 구성하는 것입니다.

 

 

[Spring Boot] @configuration과 WebMvcConfigurer의 addCorsMapping

TIL 42일차 cross origin 설정을 해줄 때, 전역적으로 config클래스를 만들어 설정해줄 수 있다. 그리고 앞으로 spring security를 사용하게 될 때도 config파일을 작성했던 기억이 있는데 예전에 할땐 너무

w97ww.tistory.com

 

이중에 addResourceHandlers가 작성되어 있는데 이는 JS나 CSS, HTML을 지원하는 정적 리소스(Static Resource) 경로를 설정하는 것입니다. 여기에 파일을 다운로드/업로드 하는 위치도 조정할 수 있으나 이는 나중에 설명드리겠습니다.

 

이로써 script 태그로 설정한 경로를 통해 자바스크립트(JS)를 불러올 수 있습니다. 

 

나중에 CSS와 JS를 더 추가하기 위해 static 폴더 안에 css, js 폴더join.js 파일을 추가하였습니다.

 

form action 삭제
type="submit"에서 "button"으로 변경

[join.js]

window.addEventListener("DOMContentLoaded", function() {
	const submitBtn = document.getElementById("submitBtn");
	
	if(submitBtn) {
		submitBtn.addEventListener("click", () => {
			
			let data = {
				username : document.getElementById("username").value,
				password : document.getElementById("password").value,
				email : document.getElementById("email").value
			}
			
			console.log(data);
		});
	}
});

 

먼저 버튼을 눌렀을 때 JS 콘솔을 통해 값들이 나오는지 확인을 하였습니다.

 

Result

 

각 값에 맞게 데이터가 출력하는 것을 확인하실 수 있습니다.

 


이제 이 데이터를 AJAX 통신을 사용하여 JSON으로 변경 후 데이터 베이스에 Insert 할 수 있도록  처리를 할 것입니다.

AJAX란 비동기 자바스크립트와 XML (Asynchronous JavaScript And XML)을 말합니다. 간단히 말하면, 서버와 통신하기 위해 XMLHttpRequest 객체를 사용하는 것을 말합니다. JSON, XML, HTML 그리고 일반 텍스트 형식 등을 포함한 다양한 포맷을 주고 받을 수 있습니다. AJAX의 강력한 특징은 페이지 전체를 리프레쉬 하지 않고서도 수행 되는 "비동기성"입니다. 이러한 비동기성을 통해 사용자의 Event가 있으면 전체 페이지가 아닌 일부분만을 업데이트 할 수 있게 해줍니다.

 

 

Ajax 시작하기 - 웹 개발자 안내서 | MDN

본 문서는 AJAX의 기본을 익힐수 있도록 해주며, 두 가지 간단한 훈련용 예제를 제공합니다.

developer.mozilla.org

 

우리가 회원가입시 AJAX를 사용하는 첫 번째 이유는 요청에 대한 응답을 HTML이 아닌 Data(JSON)로 받기 위해서 입니다.

고객이 사용하는 클라이언트는 크게 2가지로 웹과 앱이 있으며, 일반적으로 서버로부터 응답받을때 웹은 HTML파일을 받고 앱은 데이터(JSON)를 받습니다. 이것은 서버를 이원화하여 구축한 것을 의미하며 이때 '서버를 통합하여 각각의 클라이언트에게 응답해줄순 없을까?'라는 의문점에서 고안된 방법이 AJAX통신입니다. AJAX통신을 사용하면 웹은 서버로부터 데이터(JSON)를 리턴받을 수 있으며 그렇게 되면 서버를 분리할 필요없이 하나의 서버로 각각의 클라이언트 요청을 받아 응답해줄 수 있게 됩니다. 대신 웹 클라이언트는 추가적인 요청을 통해 HTML파일을 받아야합니다.

 

AJAX를 사용하는 두 번째 이유는 비동기(Asynchronous) 통신을 하기 위해서 입니다.

비동기란 말그대로 '동시에 일어나지 않는다'를 의미합니다. 즉, 요청(Request)과 응답(Response)이 동시에 일어나지 않을거라는 약속이라고 볼 수 있으며 요청과 응답이 다른 시간대에 존재하기 때문에, 요청내용에 대해 지금 바로 혹은 당장 응답받지 않아도 상관없다는 것을 말합니다. 비동기 방식은 동기보다 복잡하지만 결과가 주어지는데 시간이 걸리더라도 그 시간 동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있다는 장점을 갖고 있습니다.

 

먼저 join.js 파일에 다음과 같은 코드를 추가합니다.

 

// ajax를 위한 XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('POST', "/toy/api/join"); // 요청방식과 URL
xhr.responseType='json'; // 응답한 데이터를 Json으로 받도록 설정
xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8'); // 보내는 데이터가 어떤 방식인지 설정
xhr.send(JSON.stringify(data)); // 데이터를 보내기(여기서 JSON으로 변환)

// 응답하여 갖고온 데이터 확인
xhr.onload = () => {
    const data = xhr.response;
    if(data.success == true) {
        alert(data.message);
        location.href = "/main";
    } else {
        alert(data.message);
    }
}

 

다른 블로그 게시판 만들기와는 다르게 필자는 Jquery를 사용하지 않고 순수한 JS, Vanilla JS로 작성하고 있습니다. AJAX 통신으로 성공/실패 분기점을 만들어놓았고 성공하면 메인페이지로 이동하도록 설정했습니다.

 

다음은 JSON 파일로 되보내기 위한 JsonResult 클래스 파일을 만들도록 하겠습니다.

 

JsonResult

[JsonResult.java]

@Getter @Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class JsonResult<T> {
	private boolean success;
	private String message;
	private T data;
}

 

먼저 공통적으로 사용한다는 의미로 common 패키지를 생성하고 그 안에 JsonResult 클래스 파일을 생성하였습니다. 파일에는 성공여부(success), 보내야할 메세지(message), JSON 데이터로 보내야할 제너릭 형태의 데이터(data) 로 구성되어있습니다.

 

 

Json 데이터로 보내기 위한 RestController 어노테이션을 이용한 UserApiController를 user 패키지 안에 생성시키고 JsonResult로 반환값을 출력하도록 설정했습니다.

 

 

여기에서 데이터가 저장하도록 실행하는 Service 패키지를 추가 후 UserService 클래스 파일을 추가하도록 하겠습니다.

 

UserService

 

항상 데이터에 대해 작업을 할 때에는 Service 클래스 파일을 통해서 작업을 해야 좋습니다. 그리고 만약 저장이 실패했다고 한다면 예외처리를 위한 ExceptionController를 수정합니다.

 

ExceptionController

 

반환값을 JsonResult로 변경 후 현재 회원가입에 대한 실패를 위해 메세지를 위와 같이 고정시켰습니다.

 

이제 해당 페이지를 이용하여 회원가입을 진행하도록 하겠습니다.

 

페이지

 

Console
DBeaver

 

정상적으로 추가되었습니다. 현재 테이블의 기본적인 id에 해당하는 username이 unique로 설정되어 있지 않아 중복된 값이 들어갈 수 있습니다. 이는 나중에 따로 게시글을 추가하여 설명드리겠습니다.