TIL

플러스 주차 개인과제 TIL 2.

phonebee 2025. 3. 11. 20:26

▶ LEVEL1

▷ 2. 코드 추가 퀴즈 - JWT의 이해

● 문제의 요구 사항

1. User의 정보에 nickname을 추가

2. JWT에서 유저의 닉네임을 꺼내 화면에 보여주기를 원한다.

 

해결할 방법을 글로 작성해 본다면

1. User의 정보에 nickname을 추가

이는 User  엔티티에 nickname컬럼을 추가하면되고

 

2. JWT에서 유저의 닉네임을 꺼내 화면에 보여주기를 원한다.

이는 JwtUtil에서 토큰에 nickname값을 추가하도록 설정하면된다.

 

해당 문제를 수행하기 위해서 우선 User 엔티티를 봐야한다.

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

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true)
    private String email;
    private String password;
    @Enumerated(EnumType.STRING)
    private UserRole userRole;

    public User(String email, String password, UserRole userRole) {
        this.email = email;
        this.password = password;
        this.userRole = userRole;
    }

    private User(Long id, String email, UserRole userRole) {
        this.id = id;
        this.email = email;
        this.userRole = userRole;
    }

    public static User fromAuthUser(AuthUser authUser) {
        return new User(authUser.getId(), authUser.getEmail(), authUser.getUserRole());
    }

    public void changePassword(String password) {
        this.password = password;
    }

    public void updateRole(UserRole userRole) {
        this.userRole = userRole;
    }
}

우선 User 엔티티 코드에 nickname 컬럼을 추가해보자

 

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

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true)
    private String email;
    private String password;
    @Enumerated(EnumType.STRING)
    private UserRole userRole;

    private String nickname;

    public User(String email, String password, UserRole userRole, String nickname) {
        this.email = email;
        this.password = password;
        this.userRole = userRole;
        this.nickname = nickname;
    }

    private User(Long id, String email, UserRole userRole) {
        this.id = id;
        this.email = email;
        this.userRole = userRole;
    }

    public static User fromAuthUser(AuthUser authUser) {
        return new User(authUser.getId(), authUser.getEmail(), authUser.getUserRole());
    }

    public void changePassword(String password) {
        this.password = password;
    }

    public void updateRole(UserRole userRole) {
        this.userRole = userRole;
    }
}

이때 nickname의 경우 중복이 되어도 문제가 없다고 했기 때문에 따로 Column 어노테이션을 작성하지 않았다.

 

이렇게 하여 첫번째 요구사항을 해결했다.

 

이제 두번째 요구사항을 해결하기 위해서 JwtUtil코드를 살펴보도록하자

@Slf4j(topic = "JwtUtil")
@Component
public class JwtUtil {

    private static final String BEARER_PREFIX = "Bearer ";
    private static final long TOKEN_TIME = 60 * 60 * 1000L; // 60분

    @Value("${jwt.secret.key}")
    private String secretKey;
    private Key key;
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    @PostConstruct
    public void init() {
        byte[] bytes = Base64.getDecoder().decode(secretKey);
        key = Keys.hmacShaKeyFor(bytes);
    }

    public String createToken(Long userId, String email, UserRole userRole) {
        Date date = new Date();

        return BEARER_PREFIX +
                Jwts.builder()
                        .setSubject(String.valueOf(userId))
                        .claim("email", email)
                        .claim("userRole", userRole)
                        .setExpiration(new Date(date.getTime() + TOKEN_TIME))
                        .setIssuedAt(date) // 발급일
                        .signWith(key, signatureAlgorithm) // 암호화 알고리즘
                        .compact();
    }

    public String substringToken(String tokenValue) {
        if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
            return tokenValue.substring(7);
        }
        throw new ServerException("Not Found Token");
    }

    public Claims extractClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
}

우선 요구 사항대로 JWT에서 유저의 닉네임을 꺼내 화면에 보여주고 싶다면 JWT토큰 안에 nickname 값이 추가 되어야한다.

return BEARER_PREFIX +
        Jwts.builder()
                .setSubject(String.valueOf(userId))
                .claim("email", email)
                .claim("userRole", userRole)
                .claim("nickname", nickname)
                .setExpiration(new Date(date.getTime() + TOKEN_TIME))
                .setIssuedAt(date) // 발급일
                .signWith(key, signatureAlgorithm) // 암호화 알고리즘
                .compact();

즉, 토큰에 닉네임 값도 포함하도록 제작하면 된다.

이러면 회원가입을 진행할 때 닉네임 값을 입력받으면 해당 토큰에 작성한 닉네임이 들어가게 된다.

 

▷ 3. 코드 개선 퀴즈 - JPA의 이해

● 문제의 요구사항

1. 할 일 검색 시 wather 조건으로도 검색할 수 있어야한다.(단, weather 조건은 있을 수도, 없을 수도 있다.)

2. 할 일 검색 시 수정일 기준으로 기간 섬색이 가능해야한다.(단, 기간의 시작과 끝 조건은 있을 수도, 없을 수도 있다.)

3. JPQL을 사용하고, 쿼리 메소드명은 자유롭게 지정하되 너무 길지 않게 해야한다.

 

우선 JPQL을 통해서 해당 조건을 구현해야 하므로 Todo리포지토리 코드에서 수행해야한다.

먼저, 해당 컨트롤이 실행하는 서비스 코드를 살펴보도록하자

@Transactional(readOnly = true)
public Page<TodoResponse> getTodos(int page, int size) {
    Pageable pageable = PageRequest.of(page - 1, size);

    Page<Todo> todos = todoRepository.findAllByOrderByModifiedAtDesc(pageable);

    return todos.map(todo -> new TodoResponse(
            todo.getId(),
            todo.getTitle(),
            todo.getContents(),
            todo.getWeather(),
            new UserResponse(todo.getUser().getId(), todo.getUser().getEmail()),
            todo.getCreatedAt(),
            todo.getModifiedAt()
    ));
}

코드에서는 Todo리포지토리의 findAllByOrderByModifiedAtDesc를 사용하여 조회하고 있다.

 

@Query("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ORDER BY t.modifiedAt DESC")
Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);

위의 조건을 만족하는 쿼리문을 추가하도록 한다.

@Query("SELECT t FROM Todo t LEFT JOIN FETCH t.user u " +
        "WHERE (:weather IS NULL OR t.weather = :weather) " +
        "AND (:startDate IS NULL OR t.modifiedAt >= :startDate) " +
        "AND (:endDate IS NULL OR t.modifiedAt <= :endDate) " +
        "ORDER BY t.modifiedAt DESC")
Page<Todo> findAllByOrderByModifiedAtDesc(
        @Param("weather") String weather,
        @Param("startDate") LocalDateTime startDate,
        @Param("endDate") LocalDateTime endDate,
        Pageable pageable);

그 후 controller와 service에서 날씨, 시작, 종료일을 받을 수 있도록 수정한다.

<Service>

@Transactional(readOnly = true)
public Page<TodoResponse> getTodos(int page, int size, String weather, LocalDateTime startDate, LocalDateTime endDate) {
    Pageable pageable = PageRequest.of(page - 1, size);

    Page<Todo> todos = todoRepository.findAllByOrderByModifiedAtDesc(weather, startDate, endDate, pageable);

    return todos.map(todo -> new TodoResponse(
            todo.getId(),
            todo.getTitle(),
            todo.getContents(),
            todo.getWeather(),
            new UserResponse(todo.getUser().getId(), todo.getUser().getEmail()),
            todo.getCreatedAt(),
            todo.getModifiedAt()
    ));
}

<Controller>

@GetMapping("/todos")
public ResponseEntity<Page<TodoResponse>> getTodos(
        @RequestParam(defaultValue = "1") int page,
        @RequestParam(defaultValue = "10") int size,
        @RequestParam(required = false) String weather,
        @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)LocalDateTime startDate,
        @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)LocalDateTime endDate
        ) {
    return ResponseEntity.ok(todoService.getTodos(page, size, weather, startDate, endDate));
}

'TIL' 카테고리의 다른 글

플러스 주차 개인과제 TIL 4.  (0) 2025.03.13
플러스 주차 개인과제 TIL 3.  (0) 2025.03.12
플러스 주차 개인과제 TIL 1.  (0) 2025.03.10
Spring 심화주차 개인 과제 TIL  (0) 2025.02.27
내일배움캠프 TIL 25.  (0) 2025.02.26