프로젝트/게시판 프로젝트

[SpringBoot/게시판] 게시판 API(3) -JSON 로그인을 위한 회원 엔티티 설계

과로사한 공돌이 2024. 2. 15. 16:03
728x90
반응형
  1. 시큐리티를 이용한 JSON 데이터로 로그인(진행중...)
  2. JWT를 이용한 인증
  3. 도메인, 테이블 설계, 엔티티 생성
  4. 댓글 삭제 로직 구현
  5. 회원가입 + 정보수정 등 회원 서비스 구현
  6. 게시판 서비스 구현
  7. 댓글 서비스 구현 (1댓글 -> *(무한) 대댓글 구조)
  8. 예외 처리
  9. 예외 메세지 국제화
  10. 카테고리별 게시판 분류
  11. 게시글 페이징
  12. 동적인 검색 조건을 사용한 검색
  13. 사용자 간 쪽지 기능
  1. 무한 쪽지 스크롤
  2. 게시물 & 댓글에 대한 알람
  3. 쪽지에 대한 알람
  4. 접속한 사용자 간 실시간 채팅
  5. Swagger를 사용한 API 문서 만들기
  6. 신고 & 블랙리스트 기능
  7. AOP를 통한 로그
  8. 어드민 페이지
  9. 캐시
  10. 배포 (+ 무중단 배포)
  11. 배포 자동화
  12. Vue.js로 프론트

회원가입 & 로그인 요구사항 분석 

  • 아이디와 비밀번호, 이름, 닉네임(별명), 나이를 입력받습니다. 
  • 아이디는 중복될 수 없습니다. 
  • 비밀번호와 이름, 닉네임과 나이는 변경할 수 있습니다. 
  • 비밀번호는 암호화되어 데이터베이스에 저장됩니다. 
  • 회원을 포함한 모든 엔티티는 데이터베이스에 저장될 때, 등록 시간과 업데이트 시간을 등록하도록 하겠습니다. 
  • 로그인에 성공하면 JWT를 발급해주며, 이를 통해 사용자는 우리의 게시판 서비스 API를 이용할 수 있습니다. 
  • 회원가입으로는 USER 권한의 회원만 가입할 수 있습니다. ADMIN 권한은 직접 데이터베이스에 입력하여 지정해주도록 하겠습니다. 

회원 엔티티 작성

@Table(name = "MEMBER")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@AllArgsConstructor
@Builder
public class Member extends BaseTimeEntity {

    // pk
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;

    // 아이디
    @Column(nullable = false, length = 30, unique = true)
    private String username;

    // 비밀번호
    private String password;

    // 실명
    @Column(nullable = false, length = 30)
    private String name;

    // 별명
    @Column(nullable = false, length = 30)
    private String nickName;

    // 나이
    @Column(nullable = false)
    private Integer age;

    // 권한 -> USER, ADMIN
    @Enumerated(EnumType.STRING)
    private Role role;

    public void updatePassword(PasswordEncoder passwordEncoder, String password){
        this.password = passwordEncoder.encode(password);
    }

    public void updateName(String name){
        this.name = name;
    }

    public void updateNickName(String nickName){
        this.nickName = nickName;
    }

    public void updateAge(int age){
        this.age = age;
    }

    public void encodePassword(PasswordEncoder passwordEncoder){
        this.password = passwordEncoder.encode(password);
    }
}
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public abstract class BaseTimeEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    @Column(updatable = true)
    private LocalDateTime lastModifiedDate;
}
public enum Role {
    USER, ADMIN
}

 

EnableJpaAuditing을 추가해줍니다.

엔티티 객체가 생성이 되거나 변경이 되었을 때 @EnableJpaAuditing 어노테이션을 활용해서 자동으로 값을 등록할 수 있다.

@EnableJpaAuditing
@SpringBootApplication
public class BoardApplication {

   public static void main(String[] args) {
      SpringApplication.run(BoardApplication.class, args);
   }

}

위를 통해 다음 요구사항을 만족시켰습니다.

  • 아이디와 비밀번호, 이름, 닉네임(별명), 나이를 입력받습니다.
  • 아이디는 중복될 수 없습니다.
  • 비밀번호와 이름, 닉네임과 나이는 변경할 수 있습니다.
  • 비밀번호는 암호화되어 데이터베이스에 저장됩니다.
  • 회원을 포함한 모든 엔티티는 데이터베이스에 저장될 때, 등록 시간과 업데이트 시간을 등록하도록 하겠습니다.
  • 로그인에 성공하면 JWT를 발급해주며, 이를 통해 사용자는 우리의 게시판 서비스 API를 이용할 수 있습니다.
  • 회원가입으로는 USER 권한의 회원만 가입할 수 있습니다. ADMIN 권한은 직접 데이터베이스에 입력하여 지정해주도록 하겠습니다.

MemberRepository 생성

public interface MemberRepository extends JpaRepository<Member, Long> {
    Optional<Member> findByUsername(String userName);

    boolean existsByUsername(String userName);
}

Spring Data Jpa로 만들었습니다.

반응형

테스트 코드 작성

테스트 해야할 것들은 다음과 같습니다.

  • 회원 저장
  • 회원 저장시 아이디가 없으면 오류
  • 회원 저장시 이름이 없으면 오류
  • 회원 저장시 닉네임이 없으면 오류
  • 회원 저장시 나이가 없으면 오류
  • 회원 저장시 중복된 아이디가 있으면 오류
  • 회원 수정
  • 회원 삭제
  • existsByUsername이 잘 작동하는지
  • username으로 회원 찾기 기능이 잘 작동하는지
  • 가입 시 생성시간이 잘 등록되었는지
@SpringBootTest
@Transactional
public class MemberRepositoryTest {

    @Autowired MemberRepository memberRepository;
    @PersistenceContext EntityManager em;

    private void clear(){
        em.flush();
        em.clear();
    }

    @AfterEach
    private void afterEach(){
        em.clear();
    }

    // 회원 저장
    @Test
    void 회원저장_성공() throws Exception {
        // given
        Member member = Member.builder()
                .username("username")
                .password("1234567890")
                .name("Member1")
                .nickName("NickName1")
                .role(Role.USER)
                .age(22).build();

        // when
        Member saveMember = memberRepository.save(member);

        // then
        Member findMember = memberRepository.findById(saveMember.getId()).orElseThrow(() -> new RuntimeException("저장된 회원이 없습니다"));

        Assertions.assertThat(findMember).isSameAs(member);
        Assertions.assertThat(findMember).isSameAs(saveMember);
    }

    // 회원 저장시 아이디가 없으면 오류
    @Test
    void 회원저장_실패_회원가입시_아이디가_없음() throws Exception {
        // given
        Member member = Member.builder()
                .password("1234567890")
                .name("Member1")
                .nickName("NickName1")
                .role(Role.USER)
                .age(22).build();

        // when

        // then
        org.junit.jupiter.api.Assertions.assertThrows(Exception.class, () -> memberRepository.save(member));
    }

    // 회원 저장시 이름이 없으면 오류
    @Test
    void 회원저장_실패_회원가입시_이름이_없음() throws Exception {
        // given
        Member member = Member.builder()
                .username("username")
                .password("1234567890")
                .nickName("NickName1")
                .role(Role.USER)
                .age(22).build();

        // when

        // then
        org.junit.jupiter.api.Assertions.assertThrows(Exception.class, () -> memberRepository.save(member));
    }

    // 회원 저장시 닉네임이 없으면 오류
    @Test
    void 회원저장_실패_회원가입시_닉네임이_없음() throws Exception {
        // given
        Member member = Member.builder()
                .username("username")
                .password("1234567890")
                .name("Member1")
                .role(Role.USER)
                .age(22).build();

        // when

        // then
        org.junit.jupiter.api.Assertions.assertThrows(Exception.class, () -> memberRepository.save(member));
    }

    // 회원 저장시 나이가 없으면 오류
    @Test
    void 회원저장_실패_회원가입시_나이가_없음() throws Exception {
        // given
        Member member = Member.builder()
                .username("username")
                .password("1234567890")
                .name("Member1")
                .nickName("NickName1")
                .role(Role.USER).build();

        // when

        // then
        org.junit.jupiter.api.Assertions.assertThrows(Exception.class, () -> memberRepository.save(member));
    }

    // 회원 저장시 중복된 아이디가 있으면 오류
    @Test
    void 회원저장_실패_회원가입시_아이디가_중복() throws Exception {
        // given
        Member member1 = Member.builder()
                .username("username")
                .password("1234567890")
                .name("Member1")
                .nickName("NickName1")
                .role(Role.USER)
                .age(22).build();

        Member member2 = Member.builder()
                .username("username")
                .password("1234567890")
                .name("Member2")
                .nickName("NickName2")
                .role(Role.USER)
                .age(22).build();

        // when
        memberRepository.save(member1);
        clear();

        // then
        org.junit.jupiter.api.Assertions.assertThrows(Exception.class, () -> memberRepository.save(member2));
    }

    // 회원 수정
    @Test
    void 회원수정_성공() throws Exception {
        // given
        Member member = Member.builder()
                .username("username")
                .password("1234567890")
                .name("Member")
                .nickName("NickName")
                .role(Role.USER)
                .age(22).build();
        Member saveMember = memberRepository.save(member);
        clear();

        String updatePassword = "0987654321";
        String updateName = "updateName";
        String updateNickName = "updateNickName";
        int updateAge = 20;

        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        // when
        Member findMember = memberRepository.findById(member.getId()).orElseThrow(() -> new Exception());
        findMember.updatePassword(passwordEncoder, updatePassword);
        findMember.updateName(updateName);
        findMember.updateNickName(updateNickName);
        findMember.updateAge(updateAge);
        em.flush();

        // then
        Member findUpdateMember = memberRepository.findById(member.getId()).orElseThrow(() -> new Exception());
        Assertions.assertThat(findUpdateMember).isSameAs(findMember);
        Assertions.assertThat(passwordEncoder.matches(updatePassword, findUpdateMember.getPassword())).isTrue();
        Assertions.assertThat(findUpdateMember.getName()).isSameAs(updateName);
        Assertions.assertThat(findUpdateMember.getNickName()).isSameAs(updateNickName);
        Assertions.assertThat(findUpdateMember.getAge()).isSameAs(updateAge);
    }

    // 회원 삭제
    @Test
    void 회원삭제_성공() throws Exception {
        // given
        Member member = Member.builder()
                .username("username")
                .password("1234567890")
                .name("Member1")
                .nickName("NickName1")
                .role(Role.USER)
                .age(22).build();

        Member saveMember = memberRepository.save(member);
        clear();

        // when
        memberRepository.delete(member);
        clear();

        // then
        org.junit.jupiter.api.Assertions.assertThrows(
                Exception.class,
                () -> memberRepository.findById(member.getId()).orElseThrow(() -> new Exception())
        );
    }

    // existsByUsername이 잘 작동하는지
    @Test
    void existsByUsername_작동테스트() throws Exception {
        // given
        Member member = Member.builder()
                .username("username")
                .password("1234567890")
                .name("Member1")
                .nickName("NickName1")
                .role(Role.USER)
                .age(22).build();

        // when
        Member saveMember = memberRepository.save(member);
        clear();

        // then
        Assertions.assertThat(memberRepository.existsByUsername(member.getUsername())).isTrue();
        Assertions.assertThat(memberRepository.existsByUsername("username1")).isFalse();
    }

    // username으로 회원 찾기 기능이 잘 작동하는지
    @Test
    void findByUsername_작동테스트() throws Exception {
        // given
        Member member = Member.builder()
                .username("username")
                .password("1234567890")
                .name("Member1")
                .nickName("NickName1")
                .role(Role.USER)
                .age(22).build();

        // when
        Member saveMember = memberRepository.save(member);
        clear();

        // then
        Member findMember = memberRepository.findByUsername(member.getUsername()).orElseThrow(() -> new Exception());
        Assertions.assertThat(memberRepository.findByUsername(member.getUsername()).get().getUsername()).isEqualTo(member.getUsername());
        Assertions.assertThat(memberRepository.findByUsername(member.getUsername()).get().getName()).isEqualTo(member.getName());
        Assertions.assertThat(memberRepository.findByUsername(member.getUsername()).get().getId()).isEqualTo(member.getId());
        org.junit.jupiter.api.Assertions.assertThrows(Exception.class,
                () -> memberRepository.findByUsername(member.getUsername()+"123")
                        .orElseThrow(() -> new Exception()));
    }

    // 가입 시 생성시간이 잘 등록되었는지
    @Test
    void 생성시간_테스트() throws Exception {
        // given
        Member member = Member.builder()
                .username("username")
                .password("1234567890")
                .name("Member1")
                .nickName("NickName1")
                .role(Role.USER)
                .age(22).build();
        memberRepository.save(member);

        // when
        Member findMember = memberRepository.findByUsername(member.getUsername()).orElseThrow(() -> new Exception());

        // then
        Assertions.assertThat(findMember.getCreatedDate()).isNotNull();
        Assertions.assertThat(findMember.getLastModifiedDate()).isNotNull();
    }
}

 

 

전체 소스코드는 깃허브에서 확인하실수 있습니다.

https://github.com/IkJuLim/board

 

GitHub - IkJuLim/board

Contribute to IkJuLim/board development by creating an account on GitHub.

github.com

 

728x90
반응형