본문 바로가기
💻 뚝딱뚝딱/북북클럽

[개발일지 #003] 회원(User) 도메인 회원가입 API 구현 및 테스트

by 뚜루리 2025. 4. 22.
728x90
320x100

🎯 오늘의 목표

  • 회원(User) 도메인 회원가입 API 구현

⚙️ 진행한 작업

  • 회원(User) 도메인 회원가입 API 구현
    • repository, service, controller 수정 및 구현
  • 기타 등등 수정

🛠️ 개발내용

📌  회원 레파지토리 추가 (UserRepository.java)

package ddururi.bookbookclub.domain.user.repository;

import ddururi.bookbookclub.domain.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {

    //생략//

    //닉네임 중복 확인 (API 용도)
    boolean existsByNickname(String nickname);
}
  • API 용도로 사용할 닉네임 중복확인 메서드를 추가하였음.

 


 

📌  회원 서비스 수정  및 추가 (UserService.java)

package ddururi.bookbookclub.domain.user.service;


import ddururi.bookbookclub.global.exception.DuplicateNicknameException;
import ddururi.bookbookclub.domain.user.dto.UserLoginRequest;
import ddururi.bookbookclub.domain.user.dto.UserResponse;
import ddururi.bookbookclub.domain.user.dto.UserSignupRequest;
import ddururi.bookbookclub.domain.user.dto.UserUpdateRequest;
import ddururi.bookbookclub.domain.user.entity.User;
import ddururi.bookbookclub.domain.user.repository.UserRepository;
import ddururi.bookbookclub.global.exception.DuplicateEmailException;
import ddururi.bookbookclub.global.exception.UserNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder passwordEncoder;

    //회원가입
    public UserResponse signup(UserSignupRequest request){
        validateDuplicateEmail(request.getEmail());
        validateDuplicateNickname(request.getNickname());

        User user = User.create(
                request.getEmail(),
                passwordEncoder.encode(request.getPassword()),
                request.getNickname()
        );

        userRepository.save(user);

        return UserResponse.from(user);
    }

    //이메일 중복 확인
    private void validateDuplicateEmail(String email) {
        if (userRepository.existsByEmail(email)) {
            throw new DuplicateEmailException();
        }
    }

    //닉네임 중복 확인
    private void validateDuplicateNickname(String nickname) {
        if (userRepository.existsByNickname(nickname)) {
            throw new DuplicateNicknameException();
        }
    }

    // 이메일 중복 확인 (API 용도)
    public boolean isEmailDuplicate(String email) {
        return userRepository.existsByEmail(email);
    }

    //닉네임 중복 확인 (API 용도)
    public boolean isNicknameDuplicate(String nickname) {
        return userRepository.existsByNickname(nickname);
    }


	//생략//
  • 이메일과 닉네임 중복하는 메소드를 따로 빼고, API용도로 사용할 이메일/닉네임 중복 메서드로 생성하였음.

 


📌  회원 컨트롤러 구현 (UserController.java)

package ddururi.bookbookclub.domain.user.controller;

import ddururi.bookbookclub.domain.user.dto.LoginResponse;
import ddururi.bookbookclub.domain.user.dto.UserLoginRequest;
import ddururi.bookbookclub.domain.user.dto.UserResponse;
import ddururi.bookbookclub.domain.user.dto.UserSignupRequest;
import ddururi.bookbookclub.domain.user.service.UserService;
import ddururi.bookbookclub.global.common.ApiResponse;
import ddururi.bookbookclub.global.jwt.JwtUtil;
import ddururi.bookbookclub.global.security.CustomUserDetails;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;
    private final JwtUtil jwtUtil;

    //회원가입
    @PostMapping
    public ResponseEntity<ApiResponse<UserResponse>> signup(@Valid @RequestBody UserSignupRequest request) {
        UserResponse response = userService.signup(request);
        return ResponseEntity.ok(ApiResponse.success(response));
    }

    //이메일 중복 확인
    @GetMapping("/check-email")
    public ResponseEntity<ApiResponse<Boolean>> checkEmailDuplicate(@RequestParam String email) {
        boolean isDuplicate = userService.isEmailDuplicate(email);
        return ResponseEntity.ok(ApiResponse.success(isDuplicate));
    }

    //닉네임 중복 확인
    @GetMapping("/check-nickname")
    public ResponseEntity<ApiResponse<Boolean>> checkNicknameDuplicate(@RequestParam String nickname) {
        boolean isDuplicate = userService.isNicknameDuplicate(nickname);
        return ResponseEntity.ok(ApiResponse.success(isDuplicate));
    }

}

 

[Postman을 통한 테스트]


POST http://localhost:8080/api/users 
Content-Type: application/json
{
  "email": "test@example.com",
  "password": "12345678",
  "nickname": "북덕이"
}

 

 

📌  UserSignupRequest 수정

package ddururi.bookbookclub.domain.user.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserSignupRequest {

    @Email(message = "이메일 형식이 올바르지 않습니다.")
    @NotBlank(message = "이메일은 필수입니다.")
    private String email;

    @NotBlank(message = "비밀번호는 필수입니다.")
    @Size(min = 6, max = 20, message = "비밀번호는 6자 이상 20자 이하로 입력해주세요.")
    private String password;

    @NotBlank(message = "닉네임은 필수입니다.")
    @Size(max = 30, message = "닉네임은 최대 30자까지 입력할 수 있습니다.")
    private String nickname;
}
  • @vaild 어노테이션 활용을 위해 dto에 어노테이션 추가

 


 

[기타 추가 수정]

 

📌  회원(User) 엔티티 수정 

package ddururi.bookbookclub.domain.user.entity;

import ddururi.bookbookclub.domain.user.enums.Role;
import ddururi.bookbookclub.domain.user.enums.UserStatus;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class User {

	//생략//
    
    @Column(unique = true, nullable = false)
    private String email;

	//생략//
}
  • 이메일 필드에 unique 조건을 추가하였음.

 

728x90
320x100