Skip to content

Commit

Permalink
feat: 유저 이메일 찾기, 비밀번호 찾기, 회원 복구 기능
Browse files Browse the repository at this point in the history
- BCryptHashEncrypter -> HashService 변경
  • Loading branch information
memoer committed Aug 11, 2022
1 parent 347b000 commit 3873ea3
Show file tree
Hide file tree
Showing 27 changed files with 498 additions and 89 deletions.
37 changes: 37 additions & 0 deletions src/main/java/com/airjnc/common/annotation/TwoFieldMatch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.airjnc.common.annotation;

import com.airjnc.common.util.beanvalidator.TwoFieldMatchValidator;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

// DTO 바인딩 검사에서 선택된 두 필드에 대하여 해당 두 필드의 값이 동일한 지 체크하는 애노테이션입니다.
// 향후 사용할 가능성이 있을 것 같아, 현재 사용하진 않지만 지우지 않고 남겨둡니다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = TwoFieldMatchValidator.class)
public @interface TwoFieldMatch {

String first();

Class<?>[] groups() default {};

String message() default "{TwoFieldMatch}";

Class<? extends Payload>[] payload() default {};

String second();

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface List {

TwoFieldMatch[] value();
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/airjnc/common/service/HashService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.airjnc.common.service;

import org.mindrot.jbcrypt.BCrypt;
import org.springframework.stereotype.Service;

@Service
public class HashService {

public String encrypt(String plain) {
return BCrypt.hashpw(plain, BCrypt.gensalt());
}

public boolean isMatch(String plain, String hash) {
return BCrypt.checkpw(plain, hash);
}
}
17 changes: 0 additions & 17 deletions src/main/java/com/airjnc/common/util/BCryptHashEncrypter.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.airjnc.common.util.beanvalidator;

import com.airjnc.common.annotation.TwoFieldMatch;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class TwoFieldMatchValidator implements ConstraintValidator<TwoFieldMatch, Object> {

private String firstFieldName;

private String secondFieldName;

private String message;

@Override
public void initialize(TwoFieldMatch constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
message = constraintAnnotation.message();
}

@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
boolean isValid = true;
try {
Method firstMethod = value.getClass().getMethod("get" + replaceFirstToUpperCase(firstFieldName));
Method secondMethod = value.getClass().getMethod("get" + replaceFirstToUpperCase(secondFieldName));

Object firstObj = firstMethod.invoke(value);
Object secondObj = secondMethod.invoke(value);

isValid = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
if (!isValid) {
context.buildConstraintViolationWithTemplate(message)
/*
* `addPropertNode` -> `@TwoFieldMatch` 를 사용할 경우, 필드 접근 방법을 정의한다.
* Ex) `@TwoFieldMatch class Ex { String a; }` 일 경우, `addPropertyNode("a")` 해주면 된다.
* Ex) `@TwoFieldMatch class Ex { Ex2 ex2; } class Ex2{ String a; }` 일 경우, `addPropertyNode("ex2").addPropertyNode("a")` 해주면 된다.
*/
.addPropertyNode(secondFieldName)
/*
* 에러 메시지[message]와 node key[secondFieldName] 값을 넘겨주며, 해당 node는 errors[].field에 바인딩된다.
* `addConstraintViolation` 를 사용할 경우, fieldError에 담긴다.
* `addConstraintViolation` 를 사용하지 않고 `context.buildConstraintViolationWithTemplate(message);` 만 사용할 경우, global error에 담긴다.
*/
.addConstraintViolation()
// `addConstraintViolation` 를 통해 field error을 담았고, `disableDefaultConstraintViolation` 을 통해 global error을 제거한다.
.disableDefaultConstraintViolation();
}
return isValid;
}

private String replaceFirstToUpperCase(String str) {
char c = Character.toUpperCase(str.charAt(0));
StringBuilder sb = new StringBuilder();
int length = str.length();
sb.append(c);
for (int i = 1; i < length; i++) {
sb.append(str.charAt(i));
}
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ public UserResp logIn(@RequestBody @Validated AuthLogInReq authLogInReq) {
@GetMapping("/logOut")
@CheckAuth
public void logOut() {
userStateService.remove();
userStateService.delete();
}
}
45 changes: 41 additions & 4 deletions src/main/java/com/airjnc/user/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@
import com.airjnc.common.annotation.CheckAuth;
import com.airjnc.common.annotation.CurrentUserId;
import com.airjnc.user.dto.request.UserCreateReq;
import com.airjnc.user.dto.request.UserInquiryEmailReq;
import com.airjnc.user.dto.request.UserInquiryPasswordViaEmailReq;
import com.airjnc.user.dto.request.UserResetPwdReq;
import com.airjnc.user.dto.request.inquiryPasswordViaPhoneReq;
import com.airjnc.user.dto.response.UserInquiryEmailResp;
import com.airjnc.user.dto.response.UserResp;
import com.airjnc.user.service.UserService;
import com.airjnc.user.service.UserStateService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
Expand All @@ -27,7 +35,7 @@ public class UserController {

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserResp create(@RequestBody @Validated UserCreateReq userCreateReq) {
public UserResp create(@Validated @RequestBody UserCreateReq userCreateReq) {
UserResp userResp = userService.create(userCreateReq);
userStateService.create(userResp.getId());
return userResp;
Expand All @@ -36,8 +44,37 @@ public UserResp create(@RequestBody @Validated UserCreateReq userCreateReq) {
@DeleteMapping("/me")
@ResponseStatus(HttpStatus.NO_CONTENT)
@CheckAuth
public void remove(@CurrentUserId Long currentUserId) {
userService.remove(currentUserId);
userStateService.remove();
public void delete(@CurrentUserId Long currentUserId) {
userService.delete(currentUserId);
userStateService.delete();
}

@GetMapping("/inquiryEmail")
public UserInquiryEmailResp inquiryEmail(@Validated @ModelAttribute UserInquiryEmailReq userInquiryEmailReq) {
return userService.inquiryEmail(userInquiryEmailReq);
}

@GetMapping(value = "/inquiryPassword", params = "email")
public void inquiryPasswordViaEmail(
@Validated @ModelAttribute UserInquiryPasswordViaEmailReq userinquiryPasswordViaEmailReq) {
userService.inquiryPasswordViaEmail(userinquiryPasswordViaEmailReq);
}

// Naver에서 발신번호 등록이 계속해서 안되고 있어서, 일단 보류함
// @GetMapping(value = "/inquiryPassword", params = "phone")
public void inquiryPasswordViaPhone(
@Validated @ModelAttribute inquiryPasswordViaPhoneReq inquiryPasswordViaPhoneReq) {
userService.inquiryPasswordViaPhone(inquiryPasswordViaPhoneReq);
}

@PutMapping("/resetPassword")
public void resetPassword(@Validated @RequestBody UserResetPwdReq userResetPwdReq) {
userService.resetPassword(userResetPwdReq);
}

@PutMapping(value = "/me", params = "type=restore")
@CheckAuth
public void restore(@CurrentUserId Long userId) {
userService.restore(userId);
}
}
21 changes: 19 additions & 2 deletions src/main/java/com/airjnc/user/dao/UserMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,34 @@

import com.airjnc.user.domain.UserEntity;
import com.airjnc.user.dto.UserSaveDto;
import java.time.LocalDate;
import java.util.Optional;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper {

int delete(Long id);

Optional<UserEntity> findByEmail(String email);

Optional<UserEntity> findById(Long id);

int remove(Long id);
Optional<UserEntity> findByPhoneNumber(String phoneNumber);

Optional<UserEntity> findOnlyDeletedById(Long id);

Optional<UserEntity> findWithDeletedByEmail(String email);

Optional<UserEntity> findWithDeletedByNameAndBirthDate(
@Param("name") String name,
@Param("birthDate") LocalDate birthDate
);

int restore(Long id);

int save(UserSaveDto userSaveDTO);

int save(UserSaveDto userSaveDto);
int updatePasswordByEmail(@Param("email") String email, @Param("password") String password);
}
17 changes: 15 additions & 2 deletions src/main/java/com/airjnc/user/dao/UserRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,27 @@

import com.airjnc.user.domain.UserEntity;
import com.airjnc.user.dto.UserSaveDto;
import java.time.LocalDate;

public interface UserRepository {

void delete(Long id);

UserEntity findByEmail(String email);

UserEntity findById(Long id);

void remove(Long id);
UserEntity findByPhoneNumber(String phoneNumber);

UserEntity findOnlyDeletedById(Long id);

UserEntity findWithDeletedByEmail(String email);

UserEntity findWithDeletedByNameAndBirthDate(String name, LocalDate birthDate);

void restore(Long id);

UserEntity save(UserSaveDto userSaveDTO);

UserEntity save(UserSaveDto userSaveDto);
void updatePasswordByEmail(String email, String password);
}
44 changes: 38 additions & 6 deletions src/main/java/com/airjnc/user/dao/impl/MybatisUserRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import com.airjnc.common.exception.NotFoundException;
import com.airjnc.common.service.CommonCheckService;
import com.airjnc.user.dao.UserRepository;
import com.airjnc.user.dao.UserMapper;
import com.airjnc.user.dao.UserRepository;
import com.airjnc.user.domain.UserEntity;
import com.airjnc.user.dto.UserSaveDto;
import com.airjnc.user.util.UserModelMapper;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

Expand All @@ -20,6 +21,11 @@ public class MybatisUserRepository implements UserRepository {

private final UserModelMapper userModelMapper;

public void delete(Long id) {
int affect = userMapper.delete(id);
commonCheckService.shouldBeMatch(affect, 1);
}

@Override
public UserEntity findByEmail(String email) {
return userMapper.findByEmail(email).orElseThrow(NotFoundException::new);
Expand All @@ -31,15 +37,41 @@ public UserEntity findById(Long id) {
}

@Override
public void remove(Long id) {
int affect = userMapper.remove(id);
public UserEntity findByPhoneNumber(String phoneNumber) {
return userMapper.findByPhoneNumber(phoneNumber).orElseThrow(NotFoundException::new);
}

@Override
public UserEntity findOnlyDeletedById(Long id) {
return userMapper.findOnlyDeletedById(id).orElseThrow(NotFoundException::new);
}

@Override
public UserEntity findWithDeletedByEmail(String email) {
return userMapper.findWithDeletedByEmail(email).orElseThrow(NotFoundException::new);
}

@Override
public UserEntity findWithDeletedByNameAndBirthDate(String name, LocalDate birthDate) {
return userMapper.findWithDeletedByNameAndBirthDate(name, birthDate).orElseThrow(NotFoundException::new);
}

@Override
public void restore(Long id) {
int affect = userMapper.restore(id);
commonCheckService.shouldBeMatch(affect, 1);
}

@Override
public UserEntity save(UserSaveDto userSaveDTO) {
int affect = userMapper.save(userSaveDTO);
commonCheckService.shouldBeMatch(affect, 1);
return userModelMapper.userSaveDtoToUserEntity(userSaveDTO);
}

@Override
public UserEntity save(UserSaveDto userSaveDto) {
int affect = userMapper.save(userSaveDto);
public void updatePasswordByEmail(String email, String password) {
int affect = userMapper.updatePasswordByEmail(email, password);
commonCheckService.shouldBeMatch(affect, 1);
return userModelMapper.userSaveDtoToUserEntity(userSaveDto);
}
}
11 changes: 7 additions & 4 deletions src/main/java/com/airjnc/user/domain/UserEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@ public class UserEntity {
private LocalDateTime deletedAt;

@Builder
public UserEntity(Long id, String email, String password, String name, Gender gender,
String phoneNumber,
String address, boolean isActive, LocalDate birthDate, LocalDateTime createdAt,
LocalDateTime updatedAt, LocalDateTime deletedAt) {
public UserEntity(Long id, String email, String password, String name, Gender gender, String phoneNumber,
String address, boolean isActive, LocalDate birthDate, LocalDateTime createdAt, LocalDateTime updatedAt,
LocalDateTime deletedAt) {
this.id = id;
this.email = email;
this.password = password;
Expand All @@ -53,4 +52,8 @@ public UserEntity(Long id, String email, String password, String name, Gender ge
this.updatedAt = updatedAt;
this.deletedAt = deletedAt;
}

public boolean isDeleted() {
return deletedAt != null;
}
}
Loading

0 comments on commit 3873ea3

Please sign in to comment.