주노 님의 블로그

20240825 주말에도 나와버린 나의 과제 작성기 본문

TIL

20240825 주말에도 나와버린 나의 과제 작성기

juno0432 2024. 8. 26. 01:40

최선을 다하면되는거야~

 


숙련주차 개인 과제 구현

 

3단계

페이지네이션을 위해 Pageable과 Sort인터페이스를 사용하였다

public List<TaskResponseDto> getTasks(int page, int size)
{
    Sort sort = Sort.by(Sort.Direction.DESC, "modifiedAt");
    Pageable pageable = PageRequest.of(page-1 ,size, sort);
    Page<Task> tasks = taskRepository.findAll(pageable);

    List<TaskResponseDto> taskResponseDtos = new ArrayList<>();
    for(Task task : tasks.getContent())
    {
        taskResponseDtos.add(new TaskResponseDto(task));
    }
    if(taskResponseDtos.isEmpty())
    {
        new IllegalArgumentException("일정 목록이 없습니다.");
    }
    return taskResponseDtos;
}

 

 page와 size를 리퀘스트파람으로 받아서 페이지와 사이즈를 정한다

그리고 페이지가 0부터 시작한다고 며칠전 튜터님께 들었던 기억이났다

그래서 바로.. 줍줍 하였다.

jdbcTemplate와 비교하여 엄청 편해졌다

물론 jdbcTemplate도 어려운건 아니었지만 귀찮은 코드긴 했었다.

하지만 jpa를 사용함으로써 엄청 간편해지고 가독성이 좋은 코드로 바뀐것같다고 생각한다.

 

참고자료
https://zumsim.tistory.com/59

 

[Spring] Pageable 인터페이스 사용해보기

Spring Data JPA에서 페이징 처리와 정렬은 findAll()이라는 메서드를 이용한다. findAll()은 JpaRepository 인터페이스의 상위인 PagingAndSortRepository의 메서드로 파라미터로 전달되는 'Pageable'이란 타입의 객체

zumsim.tistory.com

 

4단계

일정 전체 삭제.

나는 진짜 삭제가아닌 삭제된것처럼.. 보이게하는 boolean 변수를 이용하여 일괄 수정을 진행한다

@OneToMany(mappedBy = "task", cascade = CascadeType.PERSIST)
private List<Reply> replyList = new ArrayList<>();

 

cascade 타입을 PERSIST로 정한다.

사실 REMOVE가 맞는데 진짜 REMOVE는 사용을 못하니

근디 cascade 타입을 persist로 지정해도 delete를 수행하는건 수동인데...

softDelete는 이걸 구현할 수 있나?

 

힘들거같아서 branch를 따로 파서 작업하기로 했다.

 

진짜 삭제를 원할경우 delete로 하면된다

 

옛날에 jpa프로젝트할때 그걸로 교수님한테 피드백 받은적 있걸랑

 

@Transactional
public void deleteTask(Long id)
{
    Task task = findTask(id);
    if(task.isDeleteStatus())
    {
        throw new IllegalArgumentException("이미 삭제된 일정은 다시 삭제할 수 없습니다");
    }
    task.delete();


    for(Reply reply : task.getReplyList())
    {
        reply.delete();
    }
}

 

그리고 위처럼 구현해준다

트랜잭션을 걸어줘야 상태 변경이 이루어진다.

+실패시 롤백이 될수도 있으니..!

 

5단계

엄청난 코드 변경과 수정을 요하는 5단계

차라리 처음부터 다시짜는게 나을정도지만..

 

이리저리 수정해줘야한다

 

유저 엔티티를 추가해주면서

중간 테이블인

매니저 테이블을 추가해주고

유저와 매니저 일정간의 관계를 수정

일정 엔티티를 수정해주고

dto를 각각의 연관관계에 맞게 수정해주고.(일정을 추가하면 그에 맞게 회원 id랑 이어줘야함)

service는 삭제 로직만 바꿔주면된다

 

 

 

 

 

 

베베꼬인 관계

살짝 월요일날 물어볼것..

 

만들긴 했는데.. 정상적으로 돌아가긴하는데..

맞는지 코드를 짜면서도 이 관계가 맞는지 확신이 안든다 ㅠㅠ

 

단계6

일정 단건 조회시 담당 유저 정보가 떠야한다면

 

private List<MemberResponseDto> managers = new ArrayList<>();

public TaskResponseDto(Task task)
{
    this.id = task.getId();
    this.title = task.getTitle();
    this.contents = task.getContents();
    this.registerAt = task.getRegisterAt();
    this.modifiedAt = task.getModifiedAt();
    this.delete_status = task.isDeleteStatus();
    this.memberId=task.getMember().getId();

    this.managers = task.getManagerList().stream()
            .map(manager -> new MemberResponseDto(manager.getMember()))
            .collect(Collectors.toList());
}

 

taskResponseDto를 수정한다

managers 정보를 위해 list에서 member정보를 가져와야하고

멤버 정보를 불러온다.

 

그러면 매니저들의 정보를 불러 올 수 있게된다

잠깐. 

dto를 따로 빼야할것같다.

 
@Getter
@NoArgsConstructor
public class MemberManagerResponseDto
{
    private Long id;
    private String name;
    private String email;

    public MemberManagerResponseDto(Member member)
    {
        this.id =member.getId();
        this.name =member.getName();
        this.email =member.getEmail();
    }
}

 

필요한 정보만을 담은 dto로 변경했다 간단하게 id name email만 담은 정보면 된다

 

 

일정 전체 조회시 지연로딩을 이용하라는디

 

package com.sparta.springadvancedpersonalproject.entity;

import com.sparta.springadvancedpersonalproject.dto.request.TaskCreateRequestDto;
import com.sparta.springadvancedpersonalproject.dto.request.TaskUpdateRequestDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@NoArgsConstructor
@Table(name = "task")
public class Task extends Timestamped
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 100)
    private String title;

    @Column(nullable = false, length = 200)
    private String contents;

    @Column
    private boolean deleteStatus = false;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    @OneToMany(mappedBy = "task")
    private List<Reply> replyList = new ArrayList<>();

    @OneToMany(mappedBy = "task")
    private List<Manager> managerList = new ArrayList<>();

    public Task(TaskCreateRequestDto taskCreateRequestDto)
    {
        this.title = taskCreateRequestDto.getTitle();
        this.contents = taskCreateRequestDto.getContents();
    }

    public Task(String title, String contents, Member member) {
        this.title = title;
        this.contents = contents;
        this.member = member;
    }

    public void update(TaskUpdateRequestDto taskUpdateRequestDto)
    {
        this.title = taskUpdateRequestDto.getTitle();
        this.contents = taskUpdateRequestDto.getContents();
    }

    public void delete()
    {
        this.deleteStatus = true;
    }
}

 

 

단계 7

유저에 비밀번호를 추가하였다

그에 맞는 dto를 수정하였다

 

어떻게 비밀번호 암호화를 해야할까?

 

@Component
public class PasswordEncoder {

    public String encode(String rawPassword) {
        return BCrypt.withDefaults().hashToString(BCrypt.MIN_COST, rawPassword.toCharArray());
    }

    public boolean matches(String rawPassword, String encodedPassword) {
        BCrypt.Result result = BCrypt.verifyer().verify(rawPassword.toCharArray(), encodedPassword);
        return result.verified;
    }
}

 

일단 password encoder가 주어졌다

로우 패스워드를 encode하면 암호화가되어서 나타남

 

/**
 * 회원을 등록합
 * @param memberCreateRequestDto 회원 이름과 이메일을 받습니다
 * @return 회원 아이디와 회원이름, 회원이메일, 삭제여부, 등록일자, 수정일자를 반환합니다.
 */
@PostMapping("/member")
public ResponseEntity<MemberResponseDto> signupMember(@RequestBody MemberCreateRequestDto memberCreateRequestDto, HttpServletResponse res) {
    MemberResponseDto responseDto = memberService.signupMember(memberCreateRequestDto, res);
    return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
}

 

원래는 createMember이었지만 상황에 맞게 signup으로 변환하였다

 

또 쿠키에 사용될 수 있게 HttpServletResponse을 받는다

아참 튜터님이 단축어 쓰지말랫는데

 

public MemberResponseDto signupMember(MemberCreateRequestDto memberCreateRequestDto, HttpServletResponse response)
{
    String password = passwordEncoder.encode(memberCreateRequestDto.getPassword());

    Member member = new Member(memberCreateRequestDto.getName(), password, memberCreateRequestDto.getEmail());
    memberRepository.save(member);

    String token = jwtUtil.createToken(member.getId());

    jwtUtil.addJwtToCookie(token, response);

    MemberResponseDto responseDto = new MemberResponseDto(member);
    return responseDto;
}

 

급하게 response로 바꾸고

위 코드에서 password를 받은 비밀번호를 넣어 인코딩한다

 

당연 저건 매치로 로그인할때 쓰면될듯

 

그리고 멤버를 만들때는 암호화된 암호를 넣어주고

 

토큰을 받아주기위해 jwtUtil에 있는 createToken으로 멤버아이디를 넣었다

util은 아직은 유저 권한이 필요없어서 권한부분은 자름

 

 

그러니까 담아서 주긴했다

 

어차피 bearer이후로 잘릴거니까 받아올수 있긴함!

 

 

 

패스워드도 암호처럼 들어간것을 볼 수 있다

 

추가 공부한 자료

https://brunch.co.kr/@jinyoungchoi95/1

 

JWT(Json Web Token) 알아가기

jwt가 생겨난 이유부터 jwt의 실제 구조까지 | 사실 꾸준히 작성하고 싶었던 글이지만 JWT를 제대로 개념을 정리하고 구현을 진행해본 적이 없었는데 리얼월드 프로젝트를 진행하면서 JWT에 대한

brunch.co.kr

 

단계 8

아까 받은 암호화된 비밀번호와 입력된 비밀번호를 매치시켜서

맞으면 로그인하는 기능을 구현해보자.

 

@Column(name = "email", unique = true, nullable = false, length = 100)
private String email;

 

일단 이메일로 로그인하니 unique를 ture로 체크해서 중복이 안되게 하자

 

 

테이블을 삭제하고 하이버네이트가 다시 만들게 하자

 

/**
 * 회원 로그인을 진행합니다.
 * @param memberLoginRequestDto 로그인정보는 이메일과
 * @param response
 * @return 반환값은 jwt값입니다.
 */
@PostMapping("/member/login")
public String loginMember(@RequestBody MemberLoginRequestDto memberLoginRequestDto, HttpServletResponse response)
{
    String token = memberService.loginMember(memberLoginRequestDto, response);
    return token;
}

 

controller에 로그인을 만들어주자

로그인 dto는 아이디와 이메일만을 받으니 따로 dto를 만들어주자

 

public String loginMember(MemberLoginRequestDto memberLoginRequestDto, HttpServletResponse response)
{
    String email = memberLoginRequestDto.getEmail();
    String password = memberLoginRequestDto.getPassword();

    Member member = memberRepository.findByEmail(email);
    if(member == null)
    {
        throw new IllegalArgumentException("회원이 존재하지 않습니다");
    }
    if(!passwordEncoder.matches(password, member.getPassword()))
    {
        throw new IllegalArgumentException("비밀번호가 일치하지 않습니다");
    }
    String token = jwtUtil.createToken(member.getId());
    jwtUtil.addJwtToCookie(token, response);

    return token;
}

 

회원이 없을때와 비밀번호가 맞을때를 확인하는 로직을 추가하고

로그인이 성공되었을때 토큰을 만들고 쿠키에 넣어준다.

 

필터를 적용했는데도 잘 되는 마법이?

 

알고보니 포스트맨이 쿠키를 자동으로 등록해주는 거시었다

.

 

 

 

 

 

이게 로그인시, 회원가입시 반환되는 토큰을 자동으로 저장하고있었다

땡큐지만 일단 필터 테스트를 위해 빼놓자

 

 

필터가 토큰이 없음을 확인하고 잡아줬다

이제 모든 단계에서

 

 

위 링크에서는 토큰이 없어도 검증이 되지않지만

다른곳에서는 토큰이없으면 안된다

 

그리고 이메일과 비밀번호가 다를때 responseEntity를 사용하여

상태코드를 같이 반환해보자

 

그리고 토큰이 없을때의 로직은 모든 서비스에 구현하기 어렵다

아니 귀찮다

그래서 우리가 아까 사용했던 authFilter에 사용해보자

 

if (!jwtUtil.validateToken(token)) {
    throw new IllegalArgumentException("Token Error");
}

위 코드긴 한데.. 문제는 void이다.

갑자기 생각난 내용은

status를 반환한 후에

return으로 강종시키는 방법이..

 

인텔리제이가 편해진걸 느낀 나

 

참고자료

https://velog.io/@2jjong/Spring-Boot-s6xmqo77

 

[Spring Boot] ResponseEntity 사용하기

ResponseEntity에 대한 개념과 Status Code, Spring Boot로 구현하기

velog.io

 

 

'TIL' 카테고리의 다른 글

20240827 본캠프 32일차 TIL  (0) 2024.08.27
20240826 본캠프 31일차 TIL  (0) 2024.08.26
20240823 본캠프 30일차 TIL  (0) 2024.08.23
20240822 본캠프 29일차 TIL  (0) 2024.08.22
20240821 본캠프 28일차 TIL  (0) 2024.08.21