๊ฐ„๋‹จํ•œ Spring Boot Restful API Sample Project ๋งŒ๋“ค์–ด๋ณด๊ธฐ #2. ํšŒ์›๊ฐ€์ž… ๊ตฌํ˜„ํ•˜๊ธฐ

2024. 3. 11. 15:20ใ†Spring/[2024] Spring Boot

728x90

 

 

https://hyejin.tistory.com/1295

 

๊ฐ„๋‹จํ•œ Spring Boot Restful API Sample Project ๋งŒ๋“ค์–ด๋ณด๊ธฐ #1. Spring Security ์„ค์ • + CustomFilter ๋งŒ๋“ค๊ธฐ

โœ” ์ƒ˜ํ”Œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ๋กœ ๊ฒฐ์ •ํ•œ ์ด์œ  -> ์‹œ๊ฐ„์ด ์กฐ๊ธˆ ์—ฌ์œ ๋กœ์šด ์š”์ฆ˜, ๊ณง ๊ฐœ๋ฐœ ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— ๊ฐ„๋‹จํ•˜๊ฒŒ ์ƒ˜ํ”Œ ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๋ฉด์„œ ๊ฐ ์žก๊ณ ? ์žˆ๊ณ  ์‹ถ์–ด์„œ! ๊ทธ๋ฆฌ๊ณ  ๋งŒ๋“  ๊น€์— ์˜ค๋žœ๋งŒ์— ํ‹ฐ์Šคํ† ๋ฆฌ

hyejin.tistory.com

-> ์ด๋ฒˆ ๊ธ€์€ ํšŒ์› ๊ฐ€์ž… ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ธ€๋กœ, ์‹œํ๋ฆฌํ‹ฐ ์„ค์ • ๊ด€๋ จ ๋ถ€๋ถ„์€ ๋ฐ‘์— ๋งํฌ ํด๋ฆญํ•ด์„œ ํ™•์ธ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค :) 

 

 

๐Ÿ˜€ : ์ƒ๊ฐํ•ด๋ณด๋ฉด ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์„ค์ • ๋ฐ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „์— ํšŒ์›๊ฐ€์ž… ๋กœ์ง์„ ๋งŒ๋“ค์—ˆ์–ด์•ผ ํ•œ๋‹ค.. ์•„๋†” 

ํšŒ์› ๊ฐ€์ž…์„ ํ•ด์•ผ ํšŒ์› ์ •๋ณด ์ €์žฅํ•˜๊ณ , ๋กœ๊ทธ์ธํ•˜๋‹ˆ๊นŒ..! ์–ด์ฐŒ๋ณด๋ฉด ๋‹น์—ฐํ•œ๊ฑด๋ฐ ๋‚˜๋Š” ์Šคํ”„๋ง ์„ค์ •ํ•  ์ƒ๊ฐ๋ฟ์ด์—ˆ๋‹ค ใ…‹ใ…‹ใ…‹ใ…‹ ํ•˜๋‚˜์— ๊ฝ‚ํžˆ๋ฉด ์ด๋ ‡๊ฒŒ ๋œ๋‹ค.;;; ์•”ํŠผ ๊ทธ๋ž˜์„œ ์ด์ œ์„œ์•ผ ํšŒ์› ๊ฐ€์ž… ๊ด€๋ จ ๊ธ€์„ ์ž‘์„ฑํ•œ๋‹ค.. 

 

 

Member 

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseEntity
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String email; // email์„ id๋กœ ์‚ฌ์šฉํ•  ๊ฒƒ์ž„!
    private String password;
    private String name;
    
    @Enumerated(EnumType.STRING)
    private MemberLevel memberLevel;
    
    @Enumerated(EnumType.STRING)
    private MemberStatus memberStatus;
    
    @Builder
    public Member(String email, String password, String name, MemberLevel memberLevel, MemberStatus memberStatus)
    {
       this.email = email;
       this.password = password;
       this.name = name;
       this.memberLevel = memberLevel;
       this.memberStatus = memberStatus;
    }
}

-> Member ๋Š” ์—”ํ‹ฐํ‹ฐ๋กœ id, email, password, name ๊ธฐ๋ณธ ์ •๋ณด์™€ memberLevel, memberStatus ๊ฐ’์„ enum ์œผ๋กœ ๊ฐ–๋Š”๋‹ค. 

memberLevel ์€ ์šฐ์„  ์ƒ๊ฐ์œผ๋ก  ๊ด€๋ฆฌ์ž๋ƒ, ์‚ฌ์šฉ์ž๋ƒ ๋ฅผ ๊ตฌ๋ถ„ํ•  ๊ฒƒ์ด๊ณ , 

memberStatus ์—๋Š” ์‚ฌ์šฉ์ค‘์ธ์ง€, ์‚ฌ์šฉ ์ค‘์ง€๋œ ์‚ฌ์šฉ์ž์ธ์ง€ ๊ตฌ๋ถ„ํ•  ๊ฒƒ์ด๋‹ค. 

 

 

MemberLevel 

@Getter
@RequiredArgsConstructor
public enum MemberLevel
{
    USER("์ผ๋ฐ˜ ์‚ฌ์šฉ์ž"),
    ADMIN("๊ด€๋ฆฌ์ž");
    
    private final String text;

}

 

 

MemberStatus 

@Getter
@RequiredArgsConstructor
public enum MemberStatus
{
    USE("์‚ฌ์šฉ ์ค‘"),
    HOLD("์ค‘์ง€");
    
    private final String text;
}

 

 

BaseEntity 

@Getter
@MappedSuperclass // ๊ณตํ†ต ๋งตํ•‘ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉ, ๋ถ€๋ชจ ํด๋ž˜์Šค์—์„œ ์„ ์–ธํ•˜๊ณ  ์†์„ฑ๋งŒ ์ƒ์†๋ฐ›๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉ
@EntityListeners(AutoCloseable.class) // JPA Entity์— ์ด๋ฒคํŠธ ๋ฐœ์ƒํ•  ๋•Œ ๊ด€๋ จ ์ฝ”๋“œ ์‹คํ–‰
public abstract class BaseEntity
{
    @CreatedDate // ์ƒ์„ฑ ์ผ์ž๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ํ•„๋“œ, ํ˜„์žฌ ๋‚ ์งœ ์ฃผ์ž…
    @Column(updatable = false) // ์ƒ์„ฑ์ผ์ž์— ๋Œ€ํ•œ ํ•„๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ˆ˜์ • ๋ถˆ๊ฐ€ํ•˜๋„๋ก ์„ค์ •
    private LocalDateTime registerDate;
    
    @LastModifiedDate // ์กฐํšŒํ•œ Entity ๊ฐ’ ๋ณ€๊ฒฝํ•  ๋•Œ ์‹œ๊ฐ„ ์ž๋™ ์ €์žฅ
    private LocalDateTime modifiedDate;
}

-> ๊ทธ๋ฆฌ๊ณ  BaseEntity์ด๋‹ค. BaseEntity๋Š” ์ถ”์ƒํด๋ž˜์Šค๋กœ ๋งŒ๋“ค๊ณ , ์ด๋ฅผ ์ƒ์†๋ฐ›์€ ์—”ํ‹ฐํ‹ฐ๋“ค์— ๋Œ€ํ•œ ๋“ฑ๋ก์ผ๊ณผ ์ˆ˜์ •์ผ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. Member๋Š” ๊ฐ€์ž…ํ•˜๋ฉด ๊ฐ€์ž…์ผ, ์ˆ˜์ •ํ•˜๋ฉด ์ˆ˜์ •์ผ์„ ๊ฐ™์ด ์ €์žฅํ•ด์ฃผ๋Š” ๊ฒŒ ์ข‹์„ ๊ฒƒ ๊ฐ™์•„์„œ ์ถ”๊ฐ€ํ–ˆ๋‹ค. 

์ด๋•Œ ๋“ฑ๋ก์ผ์€ ์ƒ์„ฑ์ผ์ž์— ๋Œ€ํ•œ ํ•„๋“œ๋‹ˆ๊นŒ ์ˆ˜์ • ๋ถˆ๊ฐ€ํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค. 

 

 

AuthController

@Slf4j
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController
{
    private final AuthService authService;
    
    @PostMapping("/signUp")
    public ApiResponse<SignResponse> signUp(@RequestBody SignUpCreateRequest request)
    {
       return ApiResponse.ok(authService.signUp(request));
    }
}

-> /auth/signUp post ์š”์ฒญ์œผ๋กœ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ request body์— ๋‹ด์•„ ํ˜ธ์ถœํ•˜๋ฉด, ํšŒ์› ๊ฐ€์ž… ํ›„ ์ •ํ•ด์ง„ ApiResponse์— ๋งž๊ฒŒ ๋ฆฌํ„ดํ•ด์ค€๋‹ค. 

 

 

SignUpCreateRequest

@Getter
@Setter
@NoArgsConstructor
public class SignUpCreateRequest
{
    private String email;
    private String password;
    private String name;
    
    private MemberLevel memberLevel;
    
    private MemberStatus memberStatus;
    
    @Builder
    public SignUpCreateRequest(String email, String password, String name)
    {
       this.email = email;
       this.password = password;
       this.name = name;
       this.memberLevel = USER;
       this.memberStatus = USE;
    }
    
    public static Member toEntity(SignUpCreateRequest request)
    {
       return Member.builder()
             .name(request.getName())
             .email(request.getEmail())
             .password(request.getPassword())
             .memberLevel(USER)
             .memberStatus(USE)
             .build();
    }
}

-> ํšŒ์› ๊ฐ€์ž…ํ•  ๋•, email, password, name ๋“ฑ์„ ๋ฐ›๋Š”๋‹ค๊ณ  ์ƒ๊ฐํ•˜๊ณ , 
MemberLevel, MemberStatus๋Š” ์ƒ์„ฑํ•  ๋•Œ ์ž๋™์œผ๋กœ USER, USE๋กœ ์ง€์ •๋œ๋‹ค. 

 

 

ApiResponse

@Getter
public class ApiResponse<T>
{
    private int code;
    private HttpStatus status;
    private String message;
    private T data;
    
    public ApiResponse(HttpStatus status, String message, T data)
    {
       this.code = status.value();
       this.status = status;
       this.message = message;
       this.data = data;
    }
    
    public static <T> ApiResponse<T> of(HttpStatus status, String message, T data)
    {
       return new ApiResponse<>(status, message, data);
    }
    
    public static <T> ApiResponse<T> of(HttpStatus status, T data)
    {
       return of(status, status.name(), data);
    }
    
    public static <T> ApiResponse<T> ok(T data)
    {
       return of(HttpStatus.OK, data);
    }
    
}

-> ๋‚ด๊ฐ€ ๋งŒ๋“  Restful api response ํ˜•์‹์„ ๋งŒ๋“ค์—ˆ๋‹ค. 

code์—๋Š” ์ƒํƒœ๊ฐ’, status์—๋Š” ์‘๋‹ต ์ƒํƒœ, message์—๋Š” ์‘๋‹ต ๋ฉ”์‹œ์ง€, data์— ์ด์ œ ๋ฆฌํ„ด๋œ ๊ฐ’์„ ๋ฐ›์„ ๊ฒƒ์ด๋‹ค. 

 

 

AuthService

@Slf4j
@Service
@RequiredArgsConstructor
public class AuthService
{
    private final MemberRepository memberRepository;
    
    private final PasswordEncoder passwordEncoder;
    
    public SignResponse signUp(SignUpCreateRequest request)
    {
       Optional<Member> byEmail = memberRepository.findByEmail(request.getEmail());
       
       if (byEmail.isPresent()) {
          throw new AlreadyExistsException();
       }
       
       String rawPassword = request.getPassword();
       String encryptedPassword = passwordEncoder.encode(rawPassword);
       request.setPassword(encryptedPassword);
       
       Member member = SignUpCreateRequest.toEntity(request);
       
       Member saveMember = memberRepository.save(member);
       return new SignResponse(saveMember.getEmail());
    }
}

-> ํšŒ์› ๊ฐ€์ž… ์„œ๋น„์Šค ๋กœ์ง์ด๋‹ค. 
๋จผ์ € request์—์„œ ๋ฐ›์€ email ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  MemberRepository์—์„œ ํ•ด๋‹น email์„ ๊ฐ€์ง„ ํšŒ์›์ด ์žˆ๋Š”์ง€ ์กฐํšŒํ•˜๊ณ , ๋งŒ์•ฝ ์žˆ๋‹ค๋ฉด ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค. (์ค‘๋ณต ์ด๋ฉ”์ผ ์•ˆ๋จ!) 

 

์ด์ œ ์ด๋ฉ”์ผ์ด ์ค‘๋ณต๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๋ฐ›์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”ํ•ด์„œ db์— ์ €์žฅํ•œ๋‹ค. 

๊ทธ๋ฆฌ๊ณ  returnํ•  ๋•Œ๋Š” email ์ •๋ณด๋งŒ ๋ฆฌํ„ดํ•ด์ค€๋‹ค. 

 

 

BaseException 

@Getter
public abstract class BaseException extends RuntimeException
{
    public BaseException(String message)
    {
       super(message);
    }
    
    public BaseException(String message, Throwable cause)
    {
       super(message, cause);
    }
    
    public abstract HttpStatus getStatus();
}

-> BaseException๋Š” RuntimeException์„ ์ƒ์†๋ฐ›์€ ์ถ”์ƒ ํด๋ž˜์Šค๋กœ ์ปค์Šคํ…€ํ•œ ์˜ˆ์™ธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํด๋ž˜์Šค๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

AlreadyExistsException 

public class AlreadyExistsException extends BaseException
{
    private static final String MESSAGE = "์ด๋ฏธ ๊ฐ€์ž…๋œ ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค.";
    
    public AlreadyExistsException()
    {
       super(MESSAGE);
    }
    
    @Override
    public HttpStatus getStatus()
    {
       return null;
    }
}

-> ๊ทธ๋ฆฌ๊ณ  BaseException ์„ ์ƒ์†๋ฐ›์•„์„œ ์ด๋ฏธ ๊ฐ€์ž…๋œ ์ด๋ฉ”์ผ์— ๋Œ€ํ•œ ์˜ˆ์™ธ๋ฅผ ์ปค์Šคํ…€ํ•ด์„œ ๋งŒ๋“ค์–ด์คฌ๋‹ค. 

 

 

SignResponse 

@Getter
@Setter
public class SignResponse
{
    private String email;
    
    public SignResponse(String email)
    {
       this.email = email;
    }
}

-> ๊ฐ„๋‹จํ•˜๊ฒŒ ์šฐ์„  ํšŒ์›๊ฐ€์ž… ํ›„ ๋ฆฌํ„ดํ•˜๋Š” vo์—๋Š” email๋งŒ ๋‹ด๊ธด๋‹ค. (์—ฌ๊ธฐ๋Š” ์ˆ˜์ •๋ ์ง€, ์ด๊ฑด ์•„์ง ๋ณด๋ฅ˜) 

 

 

 

MemberRepository

@Repository
public interface MemberRepository extends JpaRepository<Member, Long>
{
    Optional<Member> findByEmail(String email);
}

-> JpaRepository๋ฅผ ์ƒ์†๋ฐ›์•„ ๋งŒ๋“ค๊ณ , email์„ ํ†ตํ•ด ํšŒ์› ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ถ”๊ฐ€ํ–ˆ๋‹ค. 

 

 

 

ํšŒ์› ๊ฐ€์ž…์€ ๊ฐ„๋‹จํ•˜๋‹ค..! 

์ƒ˜ํ”Œ  ํ”„๋กœ์ ํŠธ๋‹ˆ๊นŒ ์šฐ์„  ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์—ˆ๊ณ , ๋งŒ๋“  ํšŒ์› ๊ฐ€์ž…์ด ์ •์ƒ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ณด๋ฉด 

### ํšŒ์› ๊ฐ€์ž…
POST http://localhost:8080/auth/signUp
Content-Type: application/json

{
  "name" : "test",
  "email" : "test@gmail.com",
  "password": "1231"
}

 

 

์ด๋ ‡๊ฒŒ ์šฐ๋ฆฌ๊ฐ€ ์ง€์ •ํ•œ apiResponse์— ๋งž๊ฒŒ ์‘๋‹ต์ด ๋‚ด๋ ค์˜จ๋‹ค. ๐Ÿ˜Š

 

 

 

๐Ÿ‘ฉโ€๐Ÿ’ป : ํšŒ์› ๊ฐ€์ž…์€ ์šฐ์„  ๊ฐ„๋‹จํ•˜๊ฒŒ ์ด๋Ÿฐ์‹์œผ๋กœ ๋งŒ๋“ค์—ˆ๋‹ค ! 

์ด์ œ ์ด ๋‹ค์Œ์—๋Š” jwt ๋ฅผ ์ด์šฉํ•ด์„œ ์ธ์ฆ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ตฌํ˜„ํ•ด๋ณผ ์˜ˆ์ •์ด๋‹ค. 

 

 

 

 

728x90