2024-06-19 오늘의 TIL - User(1)

이재성·2024년 6월 20일
post-thumbnail

User관련 CRUD 만들기

  • 조건
    • username: 최소 4자 이상, 10자 이하이며 알파벳 소문자(a~z), 숫자(0~9)
    • password: 최소 8자 이상, 15자 이하이며 알파벳 대소문자(a~z, A~Z), 숫자(0~9), 특수문자
  • 회원가입 기능
    • 성공
      • DB에 중복된 username이 없다면 회원을 저장한다.
      • 클라이언트에 성공 메시지와 상태코드를 반환한다.
      • 응답은 content-type application/json 형식입니다.
      • 회원 권한 부여
        • ADMIN
          • 모든 게시글, 댓글 수정과 삭제 가능
        • USER
          • 본인이 작성한 게시글, 댓글에 한하여 수정과 삭제 가능
    • ⚠️ 필수 예외처리
      • DB에 중복된 username이 이미 존재하는 경우
      • username,password 조건에 맞지 않는 경우
  • 비밀번호 수정 조건
    • 비밀번호 수정 시, 본인 확인을 위해 현재 비밀번호를 입력하여 올바른 경우에만 수정할 수 있습니다.
    • 비밀번호는 현재 비밀번호 및 최근 사용한 세 개의 비밀번호와 다르게 설정해야 합니다.

Controller

@RequiredArgsConstructor
@RequestMapping("/api/user")
@RestController
public class UserController {
    private final UserService userService;

    @PostMapping("/sign-up")
    public ResponseEntity<String> signUp(@RequestBody UserDto userDto , UserRoleEnum userRoleEnum) {
        return userService.signUp(userDto, userRoleEnum);
    }
    @GetMapping("/{userId}")
    public ResponseEntity<ProfileDto> getProfile(@PathVariable Long userId) {
        return userService.getProfile(userId);
    }
    @PatchMapping("/{userId}")
    public ResponseEntity<String> updateProfile(@PathVariable("userId") Long userId,
            CustomUserDetails user ,ProfileDto profileDto) {
        return userService.updateProfile(userId,user.getUser(),profileDto);
    }
    @PostMapping("/{userId}/sign-out")
    public ResponseEntity<String> signOut(@PathVariable("userId") Long userId, CustomUserDetails user) {
        return userService.signOut(userId, user.getUser());
    }
}

Entity

User

@Getter
@Entity
@NoArgsConstructor
@Table(name = "users")
public class User extends Timestamped {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String username;

    @Column(unique = true, nullable = false)
    private String password;

    @Column(nullable = false)
    private String nickname;

    private String userinfo;

    @Column(nullable = false)
    private UserStatusEnum status = UserStatusEnum.ACTIVE;

    @Column(nullable = false)
    private UserRoleEnum role;

    private String refreshToken;
    @ElementCollection(fetch = FetchType.LAZY)
    private List<String> deniedPassword = new ArrayList<>(3);
    @Column
    private boolean expired = false;

    public User(String username, String password, String nickname, String userinfo, UserRoleEnum role) {
        this.username = username;
        this.password = password;
        this.nickname = nickname;
        this.userinfo = userinfo;
        this.role = role;
    }

    // 프로필 저장
    public void updateProfile(ProfileDto profileDto) {
        this.password = profileDto.getPassword();
        this.nickname = profileDto.getNickname();
        this.userinfo = profileDto.getUserinfo();
        setDeniedPassword(profileDto.getPassword());
    }

    //최근 변경한 비밀번호 저장
    private void setDeniedPassword(String password){
        if(deniedPassword.size() > 2){
            deniedPassword.remove(0);
        }
        deniedPassword.add(password);
    }

    //삭제처리 soft delete
    public void deleteUser() {
        this.status = UserStatusEnum.DENIED;
    }
}

Timestamped

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)

public abstract class Timestamped {

    // 상속받는 클래스에 모두 creatAt, modifiedAt
    @CreatedDate
    // updatable = 생성 후 변경 불가 여부
    @Column(updatable = false, nullable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime createdAt;

    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime deletedAt;

    // 탈퇴를 할 때 값 저장.
    protected void setDeletedAt(LocalDateTime now) {
        this.deletedAt = now;
    }
}

Enum

Role

public enum UserRoleEnum {
    USER("user"),
    ADMIN("admin");

    private final String userRole;

    UserRoleEnum(String value) {
        this.userRole = value;
    }
}

Status

public enum UserStatusEnum {
    ACTIVE("Active"),
    DENIED("Denied");

    private final String userStatus;
    UserStatusEnum(String userStatus) {
        this.userStatus = userStatus;
    }
}

Repository

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findById(Long userId);
}

DTO

ProfileDTO

@Getter
public class ProfileDto {
    @NotBlank(message = "필수로 입력해야됩니다.")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-z0-9](?=.*[!@#$%^&*()]).{8,15}$" , message = "비밀번호는 특수문자포함 최소 8자, 최대 15자입니다.")
    private String password;
    @NotBlank(message = "필수로 입력해야됩니다.")
    private String nickname;
    private String userinfo;

    public ProfileDto(String nickname, String userinfo) {
        this.nickname = nickname;
        this.userinfo = userinfo;
    }
    public ProfileDto(String nickname, String userinfo, String password) {
        this.nickname = nickname;
        this.userinfo = userinfo;
        this.password = password;
    }
}

UserDTO

@Getter
public class UserDto {
    @NotBlank(message = "필수로 입력해야됩니다.")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[0-9])[a-z0-9]{4,10}$" , message = "아이디는 최소 4자, 최대 10자입니다.")
    private String username;
    @NotBlank(message = "필수로 입력해야됩니다.")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-z0-9](?=.*[!@#$%^&*()]).{8,15}$" , message = "비밀번호는 특수문자포함 최소 8자, 최대 15자입니다.")
    private String password;
    @NotBlank(message = "필수로 입력해야됩니다.")
    private String nickname;
    private String userinfo;
    private UserRoleEnum role;

}
profile
하이요

0개의 댓글