가장 먼저 회원가입을 구현하였다.
먼저 Spring Boot의 폴더 구조는 다음과 같다.
java.com.woo.outstagram
-- config
-- controller
-- dto
-- entity
-- repository
-- service
-- util
1. config : Web관련 설정이나, Spring Security 설정 등을 정의할 패키지이다.
2. controller : 요청 URL에 해당하는 처리를 담당하는 Controller 들을 정의할 패키지이다.
3. dto : 클래스들 사이에서 데이터 전달을 담당하는 DTO 객체들을 정의할 패키지이다.
4. entity : 데이터베이스에 저장될 필드와 여러 연관관계를 정의할 패키지이다.
5. repository : JPA를 사용하기 위한 Repository 객체들을 정의할 패키지이다.
6. service : 비즈니스 로직을 정의할 패키지이다.
7. util : 프로젝트에서 쓰일 여러 기능의 객체들을 정의할 패키지이다.
1. User Entity, UserRepository 작성
- User 테이블에 해당하는 Entity 객체를 작성한다.
@Entity
@Data
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Long id;
@Column(unique = true)
private String email;
private String password;
private String name;
private String nickname;
private String phone;
@Column(name = "profile_img_url")
private String profileImgUrl;
private String introduce;
private String gender;
@Enumerated(EnumType.STRING)
private Role role;
@Builder
public User(String email, String password, String name, String nickname, String phone, String profileImgUrl, String introduce, String gender, Role role) {
this.email = email;
this.password = password;
this.name = name;
this.nickname = nickname;
this.phone = phone;
this.profileImgUrl = profileImgUrl;
this.introduce = introduce;
this.gender = gender;
this.role = role;
}
}
public enum Role {
ROLE_USER("ROLE_USER"),
ROLE_ADMIN("ROLE_ADMIN");
private final String role;
Role(String role) {
this.role = role;
}
public String getRole() {
return role;
}
}
- User 테이블의 고유 ID는 Long 타입으로 설정하였으며, 생성 규칙은 GenerationType.IDENTITY 옵션을 통해 MYSQL DB에서 처리하도록 하였다.
- 권한을 저장하기 위해 Enum 타입의 Role 객체를 가져와 String 형태로 저장하도록 하였다.
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Boolean existsByEmail(String email);
}
- Jpa 기능을 사용하기 위하여 JpaRepository 객체를 상속받았으며, 기본적으로 save, delete 등의 메서드는 구현하지 않아도 사용할 수 있지만, 앞으로 필요에 따라 사용하게 될 findByEmail, existByEmail 메서드는 미리 작성해두었다.
2. SignUpReauestDto 작성
- DTO는 데이터 전달이 목적인 객체이다.
- Front에서 회원가입 요청이 데이터와 함께 들어오게 되는데, 그 데이터를 비즈니스로직까지 전달해줄 객체이다.
@Data
public class SignUpRequestDto {
@Email(message = "이메일 양식이 올바르지 않습니다.")
@NotBlank(message = "빈 항목이 존재합니다.")
private String email;
@NotBlank(message = "빈 항목이 존재합니다.")
private String password;
@Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "핸드폰 번호의 양식이 올바르지 않습니다. 01x-xxx(x)-xxxx")
@NotBlank(message = "빈 항목이 존재합니다.")
private String phone;
@NotBlank(message = "빈 항목이 존재합니다.")
private String name;
@NotBlank(message = "빈 항목이 존재합니다.")
private String nickname;
@Builder
public SignUpRequestDto(String email, String password, String name, String nickname, String phone) {
this.email = email;
this.password = password;
this.name = name;
this.nickname = nickname;
this.phone = phone;
}
public User toEntity(String password) {
return User.builder()
.email(this.email)
.password(password)
.name(this.name)
.nickname(this.nickname)
.phone(this.phone)
.profileImgUrl("/static/profileImage/default_profile.png")
.role(Role.ROLE_USER)
.gender("")
.introduce("")
.build();
}
}
- JSR-303 기능을 통하여 들어오는 데이터 형식을 검증한다. @Email 은 이메일 형식인지, @NotBlank는 null 및 공백인지, @Pattern은 사용자 지정 형식인지를 검증할 수 있게 해준다.
- toEntity 메서드는 DTO 객체를 User 객체로 변환하게 해주는 메서드이다. User 객체 필드 중 DTO객체 필드에 있는 것들은 this 메서드를 사용하여 주입하였고, 나머지는 생성 시 일괄적으로 적용될 초기화 값들을 넣어주었다.
- toEntity 메서드에서 password 파라미터를 받는 이유는, Spring Security에서 제공하는 BCryptPasswordEncoder를 사용하여 비밀번호를 암호화 한 뒤 User 객체에 주입하기 위해서이다.
3. Spring Security 설정
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().disable()
.csrf().disable()
.authorizeRequests()
.antMatchers("/users/**").permitAll();
}
}
- BCryptPasswordEncoder를 사용하기 위해 passwordEncoder 이름으로 Bean을 등록해준다.
- cors, csrf 설정을 꺼준 뒤, 회원 가입/로그인 등의 요청은 /users 로 시작되는 URL로 들어올 예정이므로 그것에 대하여 권한을 permitAll로 설정해주었다.
4. UserService 회원 가입 로직 구현
@Service
@Slf4j
@RequiredArgsConstructor
public class UserService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
@Transactional
public void join(SignUpRequestDto requestDto) throws Exception {
if(!userRepository.existsByEmail(requestDto.getEmail())) {
userRepository.save(requestDto.toEntity(passwordEncoder.encode(requestDto.getPassword())));
} else {
throw new Exception("이미 가입된 이메일 입니다.");
}
}
}
- @RequiredArgsConstructor 를 통하여 사용할 PasswordEncoder, UserRepository 객체를 주입받는다.
- 만약 테이블에 requestDto의 이메일과 같은 데이터가 없다면 UserRepository의 save 메서드를 사용하여 DB에 저장하고, 중복된 이메일이 존재한다면 예외를 발생시킨다.
- 앞서 작성한 SignUpRequestDto의 toEntity 메서드의 파라미터에 PasswordEncoder의 encode 메서드를 사용하여 password를 변환한 뒤 전달해 주었다.
5. UserController 회원가입 작성
@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {
private final UserService userService;
@PostMapping("/join")
public ResponseEntity join(@Valid @RequestBody SignUpRequestDto requestDto, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
String errorMsg = bindingResult.getFieldError().getDefaultMessage();
return ResponseEntity.badRequest().body(errorMsg);
}
try {
userService.join(requestDto);
return ResponseEntity.ok().body("회원가입이 정상적으로 진행되었습니다.");
} catch (Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}
- Post 메서드의 "/api/users/join" URL 요청은 join이라는 메서든에 지정된 형식으로 처리된다.
- @Valid 어노테이션을 통해 JSR-303검증을, @RequestBody 메서드를 통해 POST의 Body에 넘어온 데이터를 SignUpRequestDto 객체에 매핑시켜준다.
- 만약 JSR-303 검증에서 에러가 발생할 경우, DTO객체에서 지정한 메서드와 함께 400 BadRequest를 반환한다.
- userService의 join 메서드를 실행 후 성공적으로 로직이 수행되었다면 200 OK를, 예외가 발생했다면 400 BadRequest를 반환한다.
'Web > 인스타 클론 코딩' 카테고리의 다른 글
[인스타그램 클론코딩] 04. JWT 필터 구현(Back-End) (0) | 2023.02.01 |
---|---|
[인스타그램 클론코딩] 03. 로그인 구현(Front-End) (0) | 2023.02.01 |
[인스타그램 클론코딩] 03. 로그인 구현(Back-End) (0) | 2023.02.01 |
[인스타그램 클론코딩] 02. 회원 가입 구현(Front-End) (0) | 2023.01.31 |
[인스타그램 클론코딩] 01. 기술 스택 정하기 (0) | 2023.01.31 |