회원가입 페이지 만들기
- 그 전에
application.yml
파일에서 코드 추가하겠습니다.
spring:
datasource:
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://localhost:3308/myblog?serverTimezone=Asia/Seoul
username: azure
password: azure1234
jpa:
open-in-view: true
hibernate:
ddl-auto: create
use-new-id-generate-mappings: false
show-sql: true
properties:
hibernate.format_sql: true
thymeleaf:
cache: false
- spring.thymeleaf.cache : Thymeleaf 파일 수정하고 저장한 후에 브라우저에서 변경된 결과를 확인하기 위한 설정입니다. 즉, 개발할 때 false로 두어 편하게 개발하면 되고, 운영시에는 true로 변경하면 됩니다.
- 이제 회원가입을 진행하기 위한 회원가입 페이지를 만들도록 하겠습니다.
- 폴더 경로는 다음과 같이 만들어줍니다.
user-save.html
<!doctype html>
<html lang="en" class="h-100" xmlns:th="http://www.thymeleaf.org">
<head th:replace="layout/header :: head ('로그인')"></head>
<body class="text-center d-flex flex-column h-100">
<header th:replace="layout/header :: header"></header>
<main class="form-signin">
<div class="container border rounded flex-md-row mb-4 shadow-sm h-md-250">
<form>
<h1 class="h3 m-3 fw-normal">회원가입</h1>
<div class="form-floating m-3">
<input type="text" class="form-control" id="username" placeholder="아이디를 입력하세요.">
<label for="username">아이디</label>
</div>
<div class="form-floating m-3">
<input type="password" class="form-control" id="password" placeholder="패스워드를 입력하세요.">
<label for="password">패스워드</label>
</div>
<div class="form-floating m-3">
<input type="email" class="form-control" id="email" placeholder="이메일을 입력하세요.">
<label for="email">이메일</label>
</div>
<div class="form-floating m-3">
<input type="text" class="form-control" id="nickname" placeholder="닉네임을 입력하세요.">
<label for="nickname">닉네임</label>
</div>
<button class="w-100 btn btn-lg btn-primary mb-3" type="submit">회원가입</button>
</form>
</div>
</main>
<footer th:replace="layout/footer :: footer"></footer>
<!-- Option 1: Bootstrap Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>
user-save.css
html,
body {
height: 100%;
}
body {
display: flex;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
margin: auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.btn-primary {
background-color: #15751b;
border-color: #4caf50;
}
.fw-normal {
color: green;
}
header.html
에서css
경로를 추가해주세요.
header.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head (title)">
...
<link th:href="@{../css/sticky-footer-navbar.css}" rel="stylesheet">
<link rel="stylesheet" th:href="@{../css/style.css}">
<link rel="stylesheet" th:href="@{../css/user-save.css}">
<title th:text="${title}">블로그</title>
</head>
- header에 link 태그 경로를 변경해주었습니다!
UserController 클래스
package com.azurealstn.blogproject.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class UserController {
@GetMapping("/auth/user/save")
public String userSave() {
return "layout/user/user-save";
}
}
ajax
user-save.html
에서 버튼을form
바깥에다 두겠습니다. 그리고id
값을 주겠습니다.- 그리고
ajax
사용을 위해<script th:src="@{/js/user.js}"></script>
이 script 태그도 추가하겠습니다. - 폴더 경로는 이렇습니다.
- 또
jquery
도 추가해주겠습니다.
user-save.html
<body class="text-center d-flex flex-column h-100">
<header th:replace="layout/header :: header"></header>
<main class="form-signin">
<div class="container border rounded flex-md-row mb-4 shadow-sm h-md-250">
<form>
...
</form>
<button class="w-100 btn btn-lg btn-primary" id="btn-save">회원가입</button>
</div>
</main>
<footer th:replace="layout/footer :: footer"></footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script th:src="@{/js/user.js}"></script>
</body>
ajax를 사용하는 이유
요청에 대한 응답을
.html
파일이 아닌 JSON Data로 받기 위해.
비동기 통신을 하기 위해.
user.js
'use strict';
let index = {
init: function() {
$("#btn-save").on("click", () => { //this를 바인딩하기 위해 화샬표 함수 사용
this.save();
});
},
save: function() {
let data = { //JavaScript Object
username: $("#username").val(),
password: $("#password").val(),
email: $("#email").val(),
nickname: $("#nickname").val()
}
$.ajax({
type: "POST", //Http method
url: "/api/v1/user", //API 주소
data: JSON.stringify(data), //JSON으로 변환
contentType: "application/json; charset=utf-8", //MIME 타입
dataType: "json" //응답 데이터
}).done(function(res) {
alert("회원가입이 완료되었습니다.");
location.href = "/";
}).fail(function(err) {
alert(JSON.stringify(err));
});
}
}
index.init();
회원가입 테스트
- 회원가입을 테스트 하기 위해
Repository
인터페이스를 생성해야 합니다.
UserRepository 인터페이스
package com.azurealstn.blogproject.domain.user;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
- 흔히
DAO
라 불리는 DB 계층 접근자입니다. JpaRepository 인터페이스를 상속받아야 메소드를 사용할 수 있습니다. - 여기서 @Repository를 추가하지 않아도 IoC 알아서 관리해줍니다. (DI 사용 가능)
- JpaRepository<Entity 타입, PK 타입>
- 테스트 코드 작성하기
UserRepositoryTest 클래스
package com.azurealstn.blogproject.domain.user;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class UserRepositoryTest {
@Autowired
UserRepository userRepository;
@AfterEach
public void cleanup() {
userRepository.deleteAll();
}
@Test
public void 회원가입_테스트() {
//given
String username = "test";
String nickname = "babo";
userRepository.save(User.builder()
.username(username)
.password("1234")
.email("test@naver.com")
.nickname(nickname)
.role(Role.USER)
.build());
//when
List<User> userList = userRepository.findAll();
//then
User user = userList.get(0);
assertThat(user.getUsername()).isEqualTo(username);
assertThat(user.getNickname()).isEqualTo(nickname);
}
}
- JUnit5에서는 @AfterEach로 사용해야 합니다.
- 단위 테스트가 끝날 때마다 수행되는 메소드를 지정합니다.
- 여러 테스트 진행시 데이터가 남아있어서 실패의 원인이 됩니다.
- userRepository.save를 실행하게 되면
insert
혹은update
쿼리가 실행됩니다. - userRepository.findAll() : 모든 데이터를 조회
이제 구현을 해보도록 하겠습니다.
- 회원가입 위한 UserSaveRequestDto 클래스를 만들어 줍니다.
- Entity 클래스는 DB와 매우 밀접한 관계이기 때문에 Request/Response할 때는 따로 Dto 클래스를 만들어주는 것이 좋습니다.
UserSaveRequestDto 클래스
package com.azurealstn.blogproject.dto.user;
import com.azurealstn.blogproject.domain.user.Role;
import com.azurealstn.blogproject.domain.user.User;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Builder
@AllArgsConstructor
@Getter
@NoArgsConstructor
public class UserSaveRequestDto {
private String username;
private String password;
private String email;
private String nickname;
private Role role;
public User toEntity() {
return User.builder()
.username(username)
.password(password)
.email(email)
.nickname(nickname)
.role(Role.USER)
.build();
}
}
UserService 클래스
package com.azurealstn.blogproject.service;
import com.azurealstn.blogproject.domain.user.User;
import com.azurealstn.blogproject.domain.user.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
@Transactional
public Long save(User user) {
return userRepository.save(user).getId();
}
}
- private final UserRepository userRepository : 생성자 주입을 받기 위해 @RequiredArgsConstructor 어노테이션을 썼습니다.
UserApiController 클래스
package com.azurealstn.blogproject.controller.api;
import com.azurealstn.blogproject.dto.user.UserSaveRequestDto;
import com.azurealstn.blogproject.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
@RestController
public class UserApiController {
private final UserService userService;
@PostMapping("/api/v1/user")
public Long save(@RequestBody UserSaveRequestDto userSaveRequestDto) {
return userService.save(userSaveRequestDto.toEntity());
}
}
테스트 코드 작성
UserApiControllerTest 클래스
package com.azurealstn.blogproject.controller.api;
import com.azurealstn.blogproject.domain.user.Role;
import com.azurealstn.blogproject.domain.user.User;
import com.azurealstn.blogproject.domain.user.UserRepository;
import com.azurealstn.blogproject.dto.user.UserSaveRequestDto;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserApiControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private UserRepository userRepository;
@AfterEach
public void cleanup() throws Exception {
userRepository.deleteAll();
}
@Test
public void User_가입완료_테스트() throws Exception {
//given
String username = "test";
String nickname = "babo";
UserSaveRequestDto userSaveRequestDto = UserSaveRequestDto.builder()
.username(username)
.password("1234")
.email("test@naver.com")
.nickname(nickname)
.role(Role.USER)
.build();
String url = "http://localhost:" + port + "/api/v1/user";
//when
ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url, userSaveRequestDto, Long.class);
//then
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<User> userList = userRepository.findAll();
assertThat(userList.get(0).getUsername()).isEqualTo(username);
assertThat(userList.get(0).getNickname()).isEqualTo(nickname);
}
}
- API 테스트시에는 @WebMvcTest를 사용하지 않고 @SpringBootTest와 TestRestTemplate을 이용합니다. 그 이유는 JPA 기능까지 테스트 하기 위해서입니다.
다음으로
- 프로젝트 실행해서 웹 사이트에서 회원가입 정보 입력해서 DB에 제대로 데이터가 들어왔는지 까지 확인하세요!
- 테스트 전체 실행해주시고 체크 표시✔가 떴다면 일단 여기까지 하겠습니다.
- 다음에는 아직 회원가입할 때 패스워드 암호화와 회원 수정해보겠습니다. 이 때는 스프링 시큐리티를 이용하려고 합니다.
References
'공부 기록' 카테고리의 다른 글
[스프링 부트로 게시판 만들기] 07. 스프링 시큐리티를 이용한 로그인 (11) | 2021.07.21 |
---|---|
[스프링 부트로 게시판 만들기] 06. 회원가입 마무리 (2) | 2021.07.20 |
[스프링 부트로 게시판 만들기] 04. Thymeleaf + Bootstrap (0) | 2021.07.19 |
[스프링 부트로 게시판 만들기] 03. 데이터 베이스 설정 및 User 테이블 생성 (19) | 2021.07.18 |
[스프링 부트로 게시판 만들기] 02. Lombok(롬복) 설정 (0) | 2021.07.18 |
댓글