-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
예측 가능한 코드를 만들기 위해 메소드 파라미터에 final 키워드를 붙입니다
- Loading branch information
Showing
17 changed files
with
367 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
plugins { | ||
id 'java' | ||
id 'org.springframework.boot' version '3.1.1' | ||
id 'io.spring.dependency-management' version '1.1.0' | ||
} | ||
|
||
group = 'com' | ||
version = '0.0.1-SNAPSHOT' | ||
|
||
java { | ||
sourceCompatibility = '17' | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
implementation 'org.springframework.boot:spring-boot-starter-actuator' | ||
implementation 'org.springframework.boot:spring-boot-starter-jdbc' | ||
implementation 'org.springframework.boot:spring-boot-starter-web' | ||
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.1' | ||
implementation 'org.springframework.boot:spring-boot-starter-validation' | ||
implementation 'org.mindrot:jbcrypt:0.4' | ||
compileOnly 'org.projectlombok:lombok' | ||
annotationProcessor 'org.projectlombok:lombok' | ||
runtimeOnly 'com.mysql:mysql-connector-j' | ||
testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.1' | ||
} | ||
|
||
tasks.named('test') { | ||
useJUnitPlatform() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.bookbook.controller; | ||
|
||
|
||
import com.bookbook.dto.SignUpRequest; | ||
import com.bookbook.service.UserService; | ||
import com.bookbook.util.response.CommonResponse; | ||
import com.bookbook.util.response.ResponseUtil; | ||
import jakarta.validation.Valid; | ||
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; | ||
|
||
@RequestMapping("/api/v1/users") | ||
@RestController | ||
public class UserController { | ||
|
||
private final UserService userService; | ||
|
||
public UserController(final UserService userService) { | ||
this.userService = userService; | ||
} | ||
|
||
@PostMapping | ||
public CommonResponse<Long> signUp(@RequestBody @Valid final SignUpRequest signUpRequest) { | ||
userService.signUp(signUpRequest); | ||
return ResponseUtil.success(200, signUpRequest.getId()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.bookbook.dto; | ||
|
||
import jakarta.validation.constraints.NotEmpty; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
public class SignUpRequest { | ||
private Long id; | ||
@NotEmpty | ||
private String userId; | ||
@NotEmpty | ||
private String password; | ||
|
||
private String introduce = ""; | ||
|
||
private SignUpRequest() {} | ||
|
||
public SignUpRequest(final String userId, final String password, final String introduce) { | ||
this.userId = userId; | ||
this.password = password; | ||
this.introduce = introduce; | ||
} | ||
|
||
public SignUpRequest(final String userId, final String password) { | ||
this.userId = userId; | ||
this.password = password; | ||
} | ||
|
||
public void updateHashedPassword(final String hashedPassword) { | ||
this.password = hashedPassword; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.bookbook.dto; | ||
|
||
public enum UserRole { | ||
ADMIN, | ||
USER | ||
} |
11 changes: 11 additions & 0 deletions
11
src/main/java/com/bookbook/exception/UserIdExistsException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.bookbook.exception; | ||
|
||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
|
||
@ResponseStatus(value = HttpStatus.CONFLICT) | ||
public class UserIdExistsException extends RuntimeException { | ||
public UserIdExistsException(final String message) { | ||
super(message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.bookbook.mapper; | ||
|
||
import com.bookbook.dto.SignUpRequest; | ||
import org.apache.ibatis.annotations.Mapper; | ||
|
||
/* | ||
* @Mapper 정리 | ||
* - MyBatis의 mappers를 위한 marker interface로 사용한다. -> 특정 매퍼 등록을 위한 어노테이션 | ||
* - 여러 개의 매퍼를 사용하기 위해선 @MapperScan 어노테이션을 사용하고, @Mapper 는 생략할 수도 있다. | ||
* - 매퍼 스캔은 basePackages 속성에 지정된 패키지 아래의 모든 인터페이스가 매퍼로 추가된다. | ||
* */ | ||
@Mapper | ||
public interface UserMapper { | ||
void insertUser(SignUpRequest user); | ||
boolean selectUserId(String userId); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.bookbook.service; | ||
|
||
import com.bookbook.dto.SignUpRequest; | ||
import com.bookbook.exception.UserIdExistsException; | ||
import com.bookbook.mapper.UserMapper; | ||
import com.bookbook.util.password.Encoder; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
public class UserService { | ||
private final UserMapper userMapper; | ||
private final Encoder passwordEncoder; | ||
|
||
public UserService(final UserMapper userMapper, final Encoder passwordEncoder) { | ||
this.userMapper = userMapper; | ||
this.passwordEncoder = passwordEncoder; | ||
} | ||
|
||
public void signUp(final SignUpRequest signUpRequest) { | ||
if (checkUniqueUserId(signUpRequest.getUserId())) { | ||
throw new UserIdExistsException("이미 가입된 아이디입니다. " + signUpRequest.getUserId()); | ||
} | ||
signUpRequest.updateHashedPassword(encryptPassword(signUpRequest.getPassword())); | ||
userMapper.insertUser(signUpRequest); | ||
} | ||
|
||
public String encryptPassword(final String password) { | ||
return passwordEncoder.hashPassword(password); | ||
} | ||
|
||
private boolean checkUniqueUserId(final String userId) { | ||
return userMapper.selectUserId(userId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.bookbook.util; | ||
|
||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
|
||
public class ResponseEntityConstants { | ||
public static final ResponseEntity<Void> RESPONSE_OK = new ResponseEntity(HttpStatus.OK); | ||
} |
17 changes: 17 additions & 0 deletions
17
src/main/java/com/bookbook/util/password/BcryptEncoder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.bookbook.util.password; | ||
|
||
import org.mindrot.jbcrypt.BCrypt; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class BcryptEncoder implements Encoder { | ||
@Override | ||
public String hashPassword(final String rawPassword) { | ||
return BCrypt.hashpw(rawPassword, BCrypt.gensalt()); | ||
} | ||
|
||
@Override | ||
public boolean isMatched(final String rawPassword, final String hashedPassword) { | ||
return BCrypt.checkpw(rawPassword, hashedPassword); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.bookbook.util.password; | ||
|
||
public interface Encoder { | ||
String hashPassword(final String rawPassword); | ||
|
||
boolean isMatched(final String rawPassword, final String hashedPassword); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.bookbook.util.response; | ||
|
||
import com.fasterxml.jackson.annotation.JsonInclude; | ||
|
||
public record CommonResponse<T>(int code, boolean success, @JsonInclude(JsonInclude.Include.NON_NULL) T result) {} |
10 changes: 10 additions & 0 deletions
10
src/main/java/com/bookbook/util/response/ResponseUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.bookbook.util.response; | ||
|
||
public class ResponseUtil { | ||
|
||
private ResponseUtil() {} | ||
|
||
public static <T> CommonResponse<T> success(final int code, final T result) { | ||
return new CommonResponse<>(code, true, result); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
mybatis: | ||
mapper-locations: classpath:mapper/*.xml | ||
type-aliases-package: com.bookbook.dto | ||
|
||
debug: false | ||
management.endpoints.web.exposure.include: "*" | ||
|
||
logging: | ||
level: | ||
com.com.bookbook: debug | ||
org.springframework.web.servlet: debug | ||
|
||
spring: | ||
datasource: | ||
driver-class-name: com.mysql.cj.jdbc.Driver | ||
url: jdbc:mysql:https://localhost:3306/bookbook | ||
username: wisdom | ||
password: asdfasdf33 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE mapper | ||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" | ||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||
|
||
<mapper namespace="com.bookbook.mapper.UserMapper"> | ||
<insert id="insertUser" parameterType="SignUpRequest" useGeneratedKeys="true" keyProperty="id" keyColumn="id"> | ||
INSERT INTO user (user_id, hashed_password, introduce) | ||
VALUES (#{userId}, #{password}, #{introduce}) | ||
</insert> | ||
|
||
<select id="selectUserId" parameterType="java.lang.String" resultType="boolean"> | ||
SELECT EXISTS (SELECT 1 FROM user WHERE user_id = #{userId}); | ||
</select> | ||
</mapper> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package com.bookbook.service; | ||
|
||
import com.bookbook.dto.SignUpRequest; | ||
import com.bookbook.exception.UserIdExistsException; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.test.context.jdbc.Sql; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
||
/* | ||
* @SpringBootTest 정리 | ||
* - 애플리케이션을 테스트하기 위한 많은 기능을 제공한다. | ||
* - Spring Boot Test, JUnit, AssertJ, Hamcrest, Mockito ... | ||
* - ApplicationContext를 쉽게 생성하고 조작할 수 있다. | ||
* - classes 속성을 통해 빈을 생성할 클래스들을 지정할 수 있고, 지정하지 않으면 애플리케이션 상에 정의된 모든 빈을 생성한다. | ||
* - @Import 어노테이션을 사용해서 별도로 TestConfiguration을 명시하고 가져다 쓸 수 있다. | ||
* - @Transactional 어노테이션과 같이 사용하면 rollback 된다. spring-boot-test는 그저 spring-test를 확장한 것이기 때문 | ||
* - 다만 RANDOM_PORT나 DEFINED_PORT로 테스트를 설정하면 실제 테스트 서버는 별도의 스레드에서 수행되기 때문에 rollback되지 않는다. | ||
* | ||
* 언제 사용하나? | ||
* - 일반적으로 통합테스트 시 사용한다. | ||
* - business~data layer 와 같이 계층에 걸쳐 테스트할 때 사용하기 좋다. | ||
* - 참고로 유닛테스트의 경우에는 @WebMvcTest, @DataJpaTest, @MyBatisTest 등을 사용한다 | ||
* | ||
* 장점 | ||
* - 애플리케이션의 컨텍스트를 전부 셋업하기에 편리하다 | ||
* | ||
* 단점 | ||
* - 기본적으로 모든 빈을 탐색하고 등록하기 때문에 특정 계층만 테스트할 목적으로 사용하기에는 무겁고 시간이 오래 걸린다. | ||
* */ | ||
@Sql("classpath:init.sql") | ||
@SpringBootTest | ||
class UserServiceTest { | ||
|
||
@Autowired | ||
private UserService userService; | ||
|
||
SignUpRequest user; | ||
SignUpRequest userWithoutIntroduce; | ||
|
||
@BeforeEach | ||
void setUpUser() { | ||
user = new SignUpRequest( | ||
"wisdom", | ||
"pwpwpwpw123", | ||
"introduce" | ||
); | ||
|
||
userWithoutIntroduce = new SignUpRequest( | ||
"jihye", | ||
"pasdlfkjaslkdf123" | ||
); | ||
} | ||
|
||
@DisplayName("선택 입력값인 introduce 값이 없어도 유저가 정상적으로 회원가입에 성공합니다") | ||
@Test | ||
void signUpWithoutIntroduce() { | ||
userService.signUp(userWithoutIntroduce); | ||
assertThat(user.getUserId()).isNotNull(); | ||
} | ||
|
||
@DisplayName("선택 입력값인 introduce 값이 있는 상황에서 유저가 정상적으로 회원가입에 성공합니다") | ||
@Test | ||
void signUpWithIntroduce() { | ||
userService.signUp(user); | ||
assertThat(user.getUserId()).isNotNull(); | ||
} | ||
|
||
@DisplayName("이미 가입된 아이디 입력으로 회원가입에 실패합니다") | ||
@Test | ||
void signUpWithDuplicatedId() { | ||
userService.signUp(user); | ||
assertThatThrownBy(() -> userService.signUp(user)) | ||
.isInstanceOf(UserIdExistsException.class) | ||
.hasMessageContaining("이미 가입된 아이디입니다.") | ||
.hasMessageContaining(user.getUserId()) | ||
; | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
src/test/java/com/bookbook/util/password/BcryptEncoderTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.bookbook.util.password; | ||
|
||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.mindrot.jbcrypt.BCrypt; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
class BcryptEncoderTest { | ||
|
||
private final BcryptEncoder encoder = new BcryptEncoder(); | ||
|
||
@DisplayName("BCrypt 에 의해 비밀번호가 정상적으로 해싱 처리된다.") | ||
@Test | ||
void passwordIsHashedByBCrypt() { | ||
String password = "test-password"; | ||
String hashed = encoder.hashPassword(password); | ||
|
||
assertThat(hashed).hasSize(60); | ||
assertTrue(hashed.startsWith("$2a$10$")); | ||
} | ||
|
||
@DisplayName("패스워드 입력값이 같으면 해싱 결과값이 동일하다.") | ||
@Test | ||
void isMatched() { | ||
String password = "test-password"; | ||
assertTrue(encoder.isMatched(password, BCrypt.hashpw(password, BCrypt.gensalt()))); | ||
} | ||
|
||
@DisplayName("패스워드 입력값이 다르면 해싱 결과값이 동일하지 않다.") | ||
@Test | ||
void isNotMatchedWithDifferentValue() { | ||
String one = "one-password"; | ||
String another = "another-password"; | ||
assertFalse(encoder.isMatched(another, BCrypt.hashpw(one, BCrypt.gensalt()))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
DELETE FROM bookbook.user; | ||
ALTER TABLE bookbook.user AUTO_INCREMENT = 1; |