저번 프로젝트에 이어서 일정 관리에 관한 기능에 이어서 댓글, 아이디, 페이지네이션, JWT를 활용한 로그인 기능이 추가 되었다.
Github주소
https://github.com/wanduek/mj-schedule
해당 깃허브 read.me에 API명세서와 ERD가 있으니 많관부 바랍니다!
회고
전에는 못했던 3Layer structure 구현에 성공하였으며 각 클래스와 메서드가 명확한 역할을 수행하고 있다. 예를 들면, JwtUtil은 JWT 관련 작업을, UserService는 사용자 관련 비즈니스 로직을, AuthFilter는 인증 필터 역할을 담당하여 코드의 유지보수성과 이해도를 높였다. 그리고 계속 코드를 개선시킬려고 하니 코드 간결성이 더 좋아지고 오류 횟수도 줄어들었다. 하지만 의존성 문제가 항상 있어서 그에 대한 문제를 계속 직면하게 되었지만 공통적으로는 데이터베이스에 이상한 값이 들어있거나, get을 잘못 주입하는 문제가 대부분이라 금방 해결하였다.
잘한점: 적절한 어노테이션 사용, 적절한 @Setter 주입, Enum을 활용해 메시지를 타입화
부족한점/ 개선할점: 예외 처리 및 에러 메시지 관리, 토큰과 비밀번호 UserService의 updateUser 메서드에서 비밀번호 변경이 필요한 경우와 그렇지 않은 경우를 명확히 처리하는 로직이 필요합니다. 현재는 비밀번호가 제공되지 않을 때의 처리 방식이 불분명하기에 관련 로직개선이 필요하다.
그리고 다음 순서는 프로젝트하면서 작성한 코드에 대해 분석하는 글이 되겠다.
순서는 model, controll, servcie, config 순으로 하겠다.
Model
entity
Schedule.class
이 코드는 Schedule이라는 이름의 엔티티 클래스를 정의하고 있다. 이 클래스는 JPA(Java Persistence API)를 사용하여 데이터베이스의 schedule 테이블과 매핑된다.
package com.sparta.scheduleserver.model.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Table(name = "schedule")
@NoArgsConstructor
public class Schedule {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne(fetch = FetchType.LAZY ) // 명시적으로 LAZY 로딩 설정
@JoinColumn(name = "author_id", nullable = false)
private User author;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String content;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
@OneToMany(mappedBy = "schedule", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Comment> comments = new ArrayList<>();
@OneToMany(mappedBy = "schedule", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<UserSchedule> userSchedules = new ArrayList<>();
public Schedule(User author , String title, String content) {
this.author = author;
this.title = title;
this.content = content;
this.createdDate = LocalDateTime.now();
this.updatedDate = LocalDateTime.now();
}
public void update(Long authorId, String title, String content) {
this.title = title;
this.content = content;
this.updatedDate = LocalDateTime.now();
}
}
주요 구성 요소
- 클래스 어노테이션:
- @Entity: 이 클래스가 JPA 엔티티임을 나타낸다. 이 클래스는 데이터베이스 테이블에 매핑되며, schedule 테이블과 연결된다.
- @Table(name = "schedule"): 클래스가 매핑될 테이블의 이름을 schedule로 지정한다.
- @Getter: Lombok 라이브러리를 사용하여, 클래스의 모든 필드에 대해 getter 메서드를 자동으로 생성한다.
- @NoArgsConstructor: Lombok을 사용해 기본 생성자를 자동으로 생성한다.
- 필드:
- id: @Id와 @GeneratedValue 어노테이션이 붙어 있는 이 필드는 기본 키를 나타내며, 자동으로 생성되게 한다.
- author: User 엔티티와 다대일 관계를 가지며, schedule 테이블에서 author_id 열에 매핑된다. @ManyToOne 어노테이션을 통해 다대일 관계를 설정하고, @JoinColumn을 통해 외래 키 컬럼을 지정한다.
- title, content: 일정의 제목과 내용을 저장하며, @Column 어노테이션을 통해 필수 값임을 나타낸다.
- createdDate, updatedDate: 일정이 생성되고 업데이트된 날짜와 시간을 저장하며, LocalDateTime 타입을 사용한다.
- comments, userSchedules: Comment 및 UserSchedule 엔티티와 일대다 관계를 가진다. @OneToMany 어노테이션을 통해 설정하며, cascade 옵션을 통해 연관된 엔티티가 생성되거나 삭제될 때 함께 처리되게 한다.
- 생성자:
- Schedule(User author, String title, String content): 필수 필드를 초기화하는 생성자이다. 객체 생성 시 작성자(author), 제목(title), 내용(content)을 설정하며, 생성일과 수정일을 현재 시간으로 설정한다.
- 메서드:
- update(Long authorId, String title, String content): 일정의 제목과 내용을 업데이트하며, 수정일을 현재 시간으로 갱신한다.
이 클래스는 Schedule 엔티티의 데이터베이스 매핑을 정의하며, 연관된 엔티티들(User, Comment, UserSchedule)과의 관계를 설정한다. 데이터베이스의 schedule 테이블에 일정을 저장하고, 조회하고, 업데이트할 수 있도록 지원한다.
Comment.class
이 코드는 Comment라는 이름의 엔티티 클래스를 정의하고 있다. 이 클래스는 JPA(Java Persistence API)를 사용하여 데이터베이스의 comment 테이블과 매핑된다.
package com.sparta.scheduleserver.model.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Getter
@Table(name = "comment")
@RequiredArgsConstructor
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long commentId;
@Column(nullable = false)
private String username;
@Column(nullable = false)
private String commentContent;
// comment, schedule하고 서로 1:N, N:1로 연관됨, fetch = FetchType.LAZY 부모 엔티티(schedule)가 실제로 필요할때 까지 지연로딩함
@ManyToOne(fetch = FetchType.LAZY)
// 현재 엔티티와 연관된 다른 엔티티 간의 외래 키를 정의함,
@JoinColumn(name = "schedule_id", nullable = false)
// name = "schedule_id" : 현재 엔티티가 참조하는 부모 엔티티의 기본 키를 저장함
// nullable = false : 외래 키 컬럼이 'NULL값을 허용하지 않음 - 무결성 유지
private Schedule schedule;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
// 댓글 username, commentContent와 엔티티 schedule 생성자
public Comment(String username, String commentContent, Schedule schedule){
this.username = username;
this.commentContent = commentContent;
this.schedule = schedule;
this.createdDate = LocalDateTime.now();
this.updatedDate = LocalDateTime.now();
}
// 댓글 내용 수정일 수정
public void update(String commentContent) {
this.commentContent = commentContent;
this.updatedDate = LocalDateTime.now();
}
}
주요 구성 요소
- 클래스 어노테이션:
- @Entity: 이 클래스가 JPA 엔티티임을 나타낸다. 이 클래스는 데이터베이스 테이블에 매핑되며, comment 테이블과 연결된다.
- @Table(name = "comment"): 클래스가 매핑될 테이블의 이름을 comment로 지정한다.
- @Getter: Lombok 라이브러리를 사용하여, 클래스의 모든 필드에 대해 getter 메서드를 자동으로 생성한다.
- @RequiredArgsConstructor: Lombok을 사용해 final 또는 @NonNull로 선언된 필드를 포함한 생성자를 자동으로 생성한다. 이 코드에서는 필드가 final로 선언되지 않았기 때문에, 생성자 자동 생성보다는 커스텀 생성자를 사용하고 있다.
- 필드:
- commentId: @Id와 @GeneratedValue 어노테이션이 붙어 있는 이 필드는 기본 키를 나타내며, 자동으로 생성되게 한다.
- username: 댓글을 작성한 사용자의 이름을 저장한다. 이 필드는 @Column(nullable = false) 어노테이션을 통해 필수 값임을 지정하고 있다.
- commentContent: 댓글 내용을 저장한다. 이 필드도 필수 값이다.
- schedule: 댓글이 속한 Schedule 엔티티와의 다대일 관계를 나타낸다. @ManyToOne 어노테이션을 사용해 관계를 설정하며, @JoinColumn을 통해 외래 키로 schedule_id를 지정한다. 이 외래 키는 Schedule 엔티티의 기본 키를 참조하며, nullable = false로 설정되어 있어 NULL 값을 허용하지 않는다. fetch = FetchType.LAZY는 연관된 Schedule 엔티티를 실제로 필요할 때까지 로딩하지 않음을 의미한다.
- createdDate, updatedDate: 댓글이 생성된 날짜와 시간, 그리고 마지막으로 수정된 날짜와 시간을 저장한다.
- 생성자:
- Comment(String username, String commentContent, Schedule schedule): 사용자의 이름, 댓글 내용, 그리고 연관된 일정(Schedule)을 입력받아 Comment 객체를 생성한다. 생성 시점의 현재 날짜와 시간을 createdDate와 updatedDate에 설정한다.
- 메서드:
- update(String commentContent): 댓글의 내용을 수정하며, 수정된 시점의 날짜와 시간을 updatedDate에 갱신한다.
이 Comment 클래스는 댓글 데이터를 데이터베이스에 저장하고 관리하기 위한 JPA 엔티티이다. 이 클래스는 댓글이 속한 Schedule과 다대일 관계를 가지며, 댓글 작성자와 댓글 내용을 저장한다. 또한, 댓글이 생성된 시간과 수정된 시간을 기록하여 데이터의 변경 이력을 관리한다. 이 클래스는 주어진 일정에 속한 댓글을 생성하고 관리하는 역할을 한다.
package com.sparta.scheduleserver.model.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Table(name = "users")
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
@Column(nullable = false)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String email;
@OneToMany(mappedBy = "author", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Schedule> createSchedule = new ArrayList<>();
@OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<UserSchedule> userSchedules = new ArrayList<>();
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
public User(String username ,String password, String email) {
this.username = username;
this.password = password;
this.email = email;
this.createdDate = LocalDateTime.now();
this.updatedDate = LocalDateTime.now();
}
// 유저 정보 수정
public void update(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
this.updatedDate = LocalDateTime.now();
}
public void setPassword(String encodedPassword) {
}
}
주요 구성 요소
- 클래스 어노테이션:
- @Entity: 이 클래스가 JPA 엔티티임을 나타낸다. 데이터베이스의 users 테이블과 연결된다.
- @Table(name = "users"): 클래스가 매핑될 테이블의 이름을 users로 지정한다.
- @Getter: Lombok 라이브러리를 사용하여, 클래스의 모든 필드에 대해 getter 메서드를 자동으로 생성한다.
- @NoArgsConstructor: Lombok을 사용해 기본 생성자를 자동으로 생성한다.
- 필드:
- userId: @Id와 @GeneratedValue 어노테이션이 붙어 있는 이 필드는 기본 키를 나타내며, 자동으로 생성되게 한다.
- username, password, email: 각각의 필드는 사용자의 이름, 암호, 이메일 주소를 저장하며, @Column(nullable = false) 어노테이션을 통해 필수 값임을 지정한다.
- createSchedule: 사용자가 작성한 Schedule 객체들의 리스트를 관리하며, Schedule 엔티티와의 일대다 관계를 나타낸다. @OneToMany 어노테이션을 통해 관계를 설정하고, mappedBy = "author"로 연관된 Schedule 엔티티에서 이 관계를 참조하도록 한다. cascade 옵션을 통해 이 엔티티가 생성되거나 삭제될 때 연관된 Schedule도 함께 처리되게 한다.
- userSchedules: 사용자가 관련된 UserSchedule 객체들의 리스트를 관리하며, UserSchedule 엔티티와의 일대다 관계를 나타낸다. @OneToMany 어노테이션을 통해 관계를 설정하고, mappedBy = "user"로 연관된 UserSchedule 엔티티에서 이 관계를 참조하도록 한다. cascade 옵션을 통해 연관된 UserSchedule도 함께 처리되게 한다.
- createdDate, updatedDate: 사용자가 생성된 날짜와 시간, 그리고 마지막으로 수정된 날짜와 시간을 저장한다.
- 생성자:
- User(String username, String password, String email): 사용자의 이름, 암호, 이메일을 입력받아 User 객체를 생성한다. 객체 생성 시점의 날짜와 시간을 createdDate와 updatedDate에 설정한다.
- 메서드:
- update(String username, String password, String email): 사용자의 정보를 수정하며, 수정된 시점의 날짜와 시간을 updatedDate에 갱신한다.
- setPassword(String encodedPassword): 사용자의 비밀번호를 설정하는 메서드로, 주로 암호화된 비밀번호를 설정할 때 사용된다. 현재는 구현이 되어 있지 않다.
이 User 클래스는 사용자의 정보를 관리하기 위한 JPA 엔티티이다. 사용자의 기본 정보인 username, password, email을 저장하며, 사용자가 작성한 일정(Schedule) 및 관련된 사용자 일정(UserSchedule)과의 관계를 관리한다. 사용자가 생성되거나 수정될 때 해당 정보를 기록하며, 비밀번호를 설정하는 기능도 제공한다.
UserSchedule.class
이 코드는 UserSchedule이라는 이름의 엔티티 클래스를 정의하고 있다. 이 클래스는 JPA(Java Persistence API)를 사용하여 데이터베이스 테이블과 매핑되며, 사용자가 특정 일정에 연관된 정보를 저장하는 역할을 한다.
package com.sparta.scheduleserver.model.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@Entity
@Getter
@Setter
@RequiredArgsConstructor
public class UserSchedule {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "schedule_id", nullable = false)
private Schedule schedule;
}
주요 구성 요소
- 클래스 어노테이션:
- @Entity: 이 클래스가 JPA 엔티티임을 나타낸다. 이 클래스는 데이터베이스 테이블과 연결된다.
- @Getter, @Setter: Lombok 라이브러리를 사용하여, 클래스의 모든 필드에 대해 getter와 setter 메서드를 자동으로 생성한다. 이로 인해 직접 메서드를 작성하지 않고도 필드에 접근하거나 값을 변경할 수 있다.
- @RequiredArgsConstructor: Lombok을 사용해 final 또는 @NonNull로 선언된 필드를 포함한 생성자를 자동으로 생성한다. 현재 필드들이 final로 선언되지 않았기 때문에 이 생성자는 디폴트 생성자와 유사하게 동작한다.
- 필드:
- id: @Id와 @GeneratedValue 어노테이션이 붙어 있는 이 필드는 기본 키를 나타내며, 자동으로 생성된다.
- user: User 엔티티와 다대일 관계를 가진다. @ManyToOne 어노테이션을 통해 관계를 설정하고, @JoinColumn(name = "user_id", nullable = false)으로 외래 키 컬럼을 user_id로 지정하며, 이 필드는 nullable = false로 설정되어 NULL 값을 허용하지 않는다. fetch = FetchType.LAZY를 통해 연관된 User 엔티티를 실제로 필요할 때까지 로딩하지 않도록 한다.
- schedule: Schedule 엔티티와 다대일 관계를 가진다. @ManyToOne 어노테이션을 통해 관계를 설정하고, @JoinColumn(name = "schedule_id", nullable = false)으로 외래 키 컬럼을 schedule_id로 지정하며, 이 필드도 NULL 값을 허용하지 않는다. 마찬가지로 fetch = FetchType.LAZY를 사용하여 지연 로딩을 설정한다.
UserSchedule 클래스는 사용자와 일정 간의 연관 관계를 나타내는 JPA 엔티티이다. 이 엔티티는 사용자(User)와 일정(Schedule) 간의 다대일 관계를 정의하며, 각 관계는 외래 키(user_id, schedule_id)를 통해 연결된다. 이 클래스는 데이터베이스에서 각 사용자가 참여하거나 연관된 일정 정보를 관리하는 역할을 수행한다. @Getter와 @Setter를 사용하여 필드에 접근하고 수정할 수 있게 하며, @RequiredArgsConstructor를 통해 필드 초기화를 자동으로 처리한다.
-@Setter 지정 이유
(여기서 @Setter를 지정한 이유는 User와 Schedule을 다대다 관계로 연결시켜줘야 하기 때문에 여러 곳에서 사용이 가능하게 Setter를 지정해줬다.)
Model
dto
CommentRequestDto.class
이 코드는 CommentRequestDto라는 이름의 DTO(Data Transfer Object) 클래스를 정의하고 있다. DTO는 계층 간 데이터 전달을 위해 사용되는 객체로, 주로 클라이언트와 서버 간의 데이터 교환을 간단하게 하기 위해 사용된다.
package com.sparta.scheduleserver.model.entity.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
// 댓글 요청 클래스
public class CommentRequestDto{
private Long id; //schedule 고유식별자 아이디
private String username; // 댓글 이름 등록
private String commentContent; // 댓글 내용 등록
}
주요 구성 요소
- 클래스 어노테이션:
- @Getter, @Setter: Lombok 라이브러리를 사용하여, 클래스의 모든 필드에 대해 getter와 setter 메서드를 자동으로 생성한다. 이를 통해 직접 메서드를 작성하지 않고도 필드 값을 읽고 수정할 수 있다.
- 필드:
- id: 일정(Schedule)의 고유 식별자 ID를 저장한다. 이 필드는 서버에 댓글을 요청할 때, 어떤 일정과 관련된 댓글인지 식별하기 위해 사용된다.
- username: 댓글을 작성한 사용자의 이름을 저장한다. 클라이언트가 댓글을 등록할 때 사용자 이름을 전달하기 위해 사용된다.
- commentContent: 댓글의 내용을 저장한다. 클라이언트가 서버에 댓글 내용을 전달할 때 이 필드를 사용한다.
CommentRequestDto 클래스는 클라이언트에서 서버로 댓글 관련 데이터를 전송할 때 사용되는 데이터 전송 객체이다. 이 클래스는 특정 일정에 대한 댓글 요청을 처리하기 위해 필요한 데이터를 담고 있다. @Getter와 @Setter를 사용하여 필드 값에 쉽게 접근하고 수정할 수 있도록 하며, 클라이언트가 댓글을 작성할 때 서버에 필요한 데이터를 전달할 수 있게 한다.
(Request, Response는 여러곳에서 사용해야되기 때문에 @Setter를 지정해준다.)
CommentResponseDto.class
이 코드는 CommentResponseDto라는 이름의 DTO(Data Transfer Object) 클래스를 정의하고 있다. 이 클래스는 서버에서 클라이언트로 댓글 데이터를 응답할 때 사용된다.
package com.sparta.scheduleserver.model.entity.dto;
import com.sparta.scheduleserver.model.entity.Comment;
import lombok.Getter;
import java.time.LocalDateTime;
@Getter
// 댓글 응답 클래스
public class CommentResponseDto {
private String username;
private String commentContent;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
// 댓글 응답 생성자
public CommentResponseDto(Comment comment) {
this.username = comment.getUsername();
this.commentContent = comment.getCommentContent();
this.createdDate = comment.getCreatedDate();
this.updatedDate = comment.getUpdatedDate();
}
}
주요 구성 요소
- 클래스 어노테이션:
- @Getter: Lombok 라이브러리를 사용하여 클래스의 모든 필드에 대해 getter 메서드를 자동으로 생성한다. 이를 통해 필드 값을 쉽게 읽을 수 있다.
- 필드:
- username: 댓글을 작성한 사용자의 이름을 저장한다.
- commentContent: 댓글의 내용을 저장한다.
- createdDate: 댓글이 처음 생성된 날짜와 시간을 저장한다.
- updatedDate: 댓글이 마지막으로 수정된 날짜와 시간을 저장한다.
- 생성자:
- CommentResponseDto(Comment comment): 이 생성자는 Comment 엔티티 객체를 받아서, 해당 객체의 데이터를 CommentResponseDto로 변환한다.
- username 필드에는 Comment 객체의 getUsername() 메서드로 가져온 값이 저장된다.
- commentContent 필드에는 Comment 객체의 getCommentContent() 메서드로 가져온 값이 저장된다.
- createdDate 필드에는 Comment 객체의 getCreatedDate() 메서드로 가져온 값이 저장된다.
- updatedDate 필드에는 Comment 객체의 getUpdatedDate() 메서드로 가져온 값이 저장된다.
- CommentResponseDto(Comment comment): 이 생성자는 Comment 엔티티 객체를 받아서, 해당 객체의 데이터를 CommentResponseDto로 변환한다.
CommentResponseDto 클래스는 서버가 클라이언트에게 댓글 데이터를 응답할 때 사용되는 DTO이다. 이 클래스는 Comment 엔티티 객체로부터 필요한 데이터를 받아와 클라이언트에 전달할 형식으로 가공한다. @Getter를 통해 필드 값에 쉽게 접근할 수 있으며, 생성자를 통해 Comment 객체를 CommentResponseDto 객체로 변환할 수 있다. 이는 클라이언트가 댓글에 대한 정보를 요청할 때 서버가 응답으로 제공할 데이터 형식을 정의한 것이다.
ScheduleRequestDto.class
이 코드는 ScheduleRequestDto라는 이름의 DTO(Data Transfer Object) 클래스를 정의하고 있다. 이 클래스는 클라이언트가 서버에 일정을 생성하거나 업데이트할 때 필요한 데이터를 전달하기 위해 사용된다.
package com.sparta.scheduleserver.model.entity.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
//일정 요청 클래스
public class ScheduleRequestDto {
private Long authorId;// 유저id에서 받아온 id
private String title; // 일정 제목
private String content; // 일정 내용
}
주요 구성 요소
- 클래스 어노테이션:
- @Getter, @Setter: Lombok 라이브러리를 사용하여, 클래스의 모든 필드에 대해 getter와 setter 메서드를 자동으로 생성한다. 이를 통해 직접 메서드를 작성하지 않고도 필드 값을 읽고 수정할 수 있다.
- 필드:
- authorId: 일정 작성자의 고유 식별자 ID를 저장한다. 이 필드는 클라이언트가 특정 사용자가 작성한 일정임을 서버에 전달하기 위해 사용된다.
- title: 일정의 제목을 저장한다. 클라이언트가 서버에 일정을 등록할 때 제목을 전달하기 위해 사용된다.
- content: 일정의 내용을 저장한다. 클라이언트가 서버에 일정의 구체적인 내용을 전달하기 위해 사용된다.
ScheduleRequestDto 클래스는 클라이언트에서 서버로 일정 생성이나 업데이트 요청을 할 때 사용되는 데이터 전송 객체이다. 이 클래스는 일정 작성자의 ID, 제목, 내용을 담고 있으며, 클라이언트가 입력한 데이터를 서버에 전달하기 위해 사용된다. @Getter와 @Setter를 사용하여 필드 값에 쉽게 접근하고 수정할 수 있도록 한다.
ScheduleResponseDto.class
이 코드는 ScheduleResponseDto라는 이름의 DTO(Data Transfer Object) 클래스를 정의하고 있으며, 서버에서 클라이언트로 일정 정보를 응답할 때 사용된다.
package com.sparta.scheduleserver.model.entity.dto;
import com.sparta.scheduleserver.model.entity.Schedule;
import lombok.Getter;
import java.time.LocalDateTime;
@Getter
// 일정 응답 클래스
public class ScheduleResponseDto {
private Long id;
private String title;
private String content;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
private int commentCount;
private UserDto author;
// 일정 응답 생성자
public ScheduleResponseDto(Schedule schedule) {
this.id = schedule.getId();
this.title = schedule.getTitle();
this.content = schedule.getContent();
this.createdDate = schedule.getCreatedDate(); // 일정 등록일
this.updatedDate = schedule.getUpdatedDate(); // 일정 수정일
this.commentCount = schedule.getComments().size(); // 댓글 개수, 크키
// 유저 정보 포함
this.author = new UserDto(schedule.getAuthor());
}
}
주요 구성 요소
- 클래스 어노테이션:
- @Getter: Lombok 라이브러리를 사용하여 클래스의 모든 필드에 대해 getter 메서드를 자동으로 생성한다. 이를 통해 필드 값을 쉽게 읽을 수 있다.
- 필드:
- id: 일정의 고유 식별자 ID를 저장한다.
- title: 일정의 제목을 저장한다.
- content: 일정의 내용을 저장한다.
- createdDate: 일정이 처음 생성된 날짜와 시간을 저장한다.
- updatedDate: 일정이 마지막으로 수정된 날짜와 시간을 저장한다.
- commentCount: 해당 일정에 달린 댓글의 수를 저장한다.
- author: 일정을 작성한 사용자의 정보를 담는 UserDto 객체를 저장한다.
- 생성자:
- ScheduleResponseDto(Schedule schedule): 이 생성자는 Schedule 엔티티 객체를 받아서, 해당 객체의 데이터를 ScheduleResponseDto로 변환한다.
- id 필드는 Schedule 객체의 ID를 가져온다.
- title과 content 필드는 각각 일정의 제목과 내용을 가져온다.
- createdDate와 updatedDate 필드는 일정이 생성된 날짜와 수정된 날짜를 가져온다.
- commentCount 필드는 Schedule 객체에 연결된 댓글 리스트의 크기를 가져와 댓글 수를 설정한다.
- author 필드는 일정 작성자의 정보를 UserDto 객체로 변환하여 저장한다.
- ScheduleResponseDto(Schedule schedule): 이 생성자는 Schedule 엔티티 객체를 받아서, 해당 객체의 데이터를 ScheduleResponseDto로 변환한다.
ScheduleResponseDto 클래스는 서버가 클라이언트에게 일정 정보를 응답할 때 사용되는 DTO이다. 이 클래스는 Schedule 엔티티로부터 필요한 데이터를 받아와 클라이언트에 전달할 형식으로 가공한다. @Getter를 통해 필드 값에 쉽게 접근할 수 있으며, 생성자를 통해 Schedule 객체를 ScheduleResponseDto 객체로 변환하여, 클라이언트가 요청한 일정에 대한 세부 정보를 제공한다. 이 DTO는 댓글 수와 작성자 정보도 포함하고 있어, 일정과 관련된 추가적인 정보를 클라이언트에 전달할 수 있다.
UserRequestDto.class
이 코드는 UserRequestDto라는 이름의 DTO(Data Transfer Object) 클래스를 정의하고 있다. 이 클래스는 클라이언트가 서버에 유저 정보를 요청할 때 사용되며, 주로 유저 생성 또는 업데이트 요청에 필요한 데이터를 포함한다.
package com.sparta.scheduleserver.model.entity.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
//유저 요청 클래스
public class UserRequestDto {
private Long id;
private String username;
private String password;
private String email;
}
주요 구성 요소
- 클래스 어노테이션:
- @Getter, @Setter: Lombok 라이브러리를 사용하여 클래스의 모든 필드에 대해 getter와 setter 메서드를 자동으로 생성한다. 이를 통해 필드에 쉽게 접근하고 값을 설정할 수 있다.
- 필드:
- id: 유저의 고유 식별자 ID를 저장한다. 유저 생성 요청 시에는 주로 설정되지 않지만, 유저 업데이트 요청 시에는 특정 유저를 식별하기 위해 사용된다.
- username: 유저의 이름을 저장한다. 유저 생성 및 업데이트 시 입력받는 필드이다.
- password: 유저의 비밀번호를 저장한다. 유저 생성 및 업데이트 시 입력받는 필드이며, 보통 암호화된 형태로 저장된다.
- email: 유저의 이메일 주소를 저장한다. 유저 생성 및 업데이트 시 입력받는 필드이다.
UserRequestDto 클래스는 클라이언트가 서버에 유저 정보를 생성하거나 업데이트할 때 사용되는 데이터 전송 객체이다. 이 클래스는 유저의 ID, 이름, 비밀번호, 이메일 주소를 담고 있으며, 서버에 필요한 데이터를 클라이언트로부터 받아서 처리할 수 있도록 한다. @Getter와 @Setter를 통해 필드 값을 쉽게 읽고 수정할 수 있으며, 유저 생성 및 업데이트 요청을 효과적으로 관리할 수 있다.
UserScheduleResponseDto.class
이 코드는 UserResponseDto라는 이름의 DTO(Data Transfer Object) 클래스를 정의하고 있다. 이 클래스는 서버가 클라이언트에게 유저 정보를 응답할 때 사용된다.
package com.sparta.scheduleserver.model.entity.dto;
import com.sparta.scheduleserver.model.entity.User;
import lombok.Getter;
import java.time.LocalDateTime;
@Getter
//유저 응답 클래스
public class UserResponseDto {
private Long id;
private String username;
private String password;
private String email;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
// 일정 응답 생성자
public UserResponseDto(User user) {
this.id = user.getUserId();
this.password = user.getPassword();
this.username = user.getUsername();
this.email = user.getEmail();
this.createdDate = user.getCreatedDate(); // 일정 등록일
this.updatedDate = user.getUpdatedDate(); // 일정 수정일
}
}
주요 구성 요소
- 클래스 어노테이션:
- @Getter: Lombok 라이브러리를 사용하여 클래스의 모든 필드에 대해 getter 메서드를 자동으로 생성한다. 이를 통해 필드 값을 쉽게 읽을 수 있다.
- 필드:
- id: 유저의 고유 식별자 ID를 저장한다.
- username: 유저의 이름을 저장한다.
- password: 유저의 비밀번호를 저장한다. 응답에 포함될 수 있지만, 일반적으로 보안상의 이유로 비밀번호는 응답 데이터에 포함되지 않는 것이 좋다.
- email: 유저의 이메일 주소를 저장한다.
- createdDate: 유저가 생성된 날짜와 시간을 저장한다.
- updatedDate: 유저가 마지막으로 수정된 날짜와 시간을 저장한다.
- 생성자:
- UserResponseDto(User user): 이 생성자는 User 엔티티 객체를 받아서, 해당 객체의 데이터를 UserResponseDto로 변환한다.
- id 필드는 User 객체의 getUserId() 메서드로 가져온 값을 사용한다.
- username, password, email 필드는 각각 User 객체의 getUsername(), getPassword(), getEmail() 메서드로 가져온 값을 사용한다.
- createdDate와 updatedDate 필드는 각각 User 객체의 getCreatedDate()와 getUpdatedDate() 메서드로 가져온 값을 사용한다.
- UserResponseDto(User user): 이 생성자는 User 엔티티 객체를 받아서, 해당 객체의 데이터를 UserResponseDto로 변환한다.
UserResponseDto 클래스는 서버가 클라이언트에게 유저 정보를 응답할 때 사용되는 DTO이다. 이 클래스는 User 엔티티 객체로부터 필요한 데이터를 추출하여 클라이언트에 제공하는 역할을 한다. @Getter를 통해 필드 값을 쉽게 읽을 수 있으며, 생성자를 통해 User 객체를 UserResponseDto 객체로 변환하여 클라이언트에 유저 정보를 전달한다. 비밀번호를 포함시키는 것은 보안상의 이유로 피해야 하며, 실제 응답에서는 비밀번호 대신 다른 안전한 정보를 제공하는 것이 좋다.
LoginRequestDto.class
이 코드는 LoginRequestDto라는 이름의 DTO(Data Transfer Object) 클래스를 정의하고 있다. 이 클래스는 클라이언트가 서버에 로그인 요청을 할 때 필요한 데이터를 전달하기 위해 사용된다.
package com.sparta.scheduleserver.model.entity.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
//로그인 요청
public class LoginRequestDto {
private String email;
private String password;
}
주요 구성 요소
- 클래스 어노테이션:
- @Data: Lombok 라이브러리를 사용하여, 클래스의 모든 필드에 대해 getter와 setter 메서드, toString(), equals(), hashCode() 메서드를 자동으로 생성한다. 이 어노테이션은 간결하게 데이터 객체를 정의하는 데 도움을 준다.
- @NoArgsConstructor: 기본 생성자를 자동으로 생성한다. 이 생성자는 인자가 없는 생성자로, 객체를 기본 상태로 초기화할 때 사용된다.
- @AllArgsConstructor: 모든 필드를 인자로 받는 생성자를 자동으로 생성한다. 이 생성자는 모든 필드를 초기화하는 데 사용된다.
- 필드:
- email: 사용자의 이메일 주소를 저장한다. 로그인 요청 시 클라이언트가 입력한 이메일 주소를 전달하기 위해 사용된다.
- password: 사용자의 비밀번호를 저장한다. 로그인 요청 시 클라이언트가 입력한 비밀번호를 전달하기 위해 사용된다.
LoginRequestDto 클래스는 클라이언트가 서버에 로그인 요청을 보낼 때 필요한 데이터를 담는 DTO이다. 이 클래스는 이메일 주소와 비밀번호를 포함하며, 로그인 요청을 처리하기 위해 서버가 클라이언트로부터 이 정보를 받아야 한다. @Data, @NoArgsConstructor, @AllArgsConstructor 어노테이션을 통해, 필드에 대한 접근 메서드와 다양한 생성자를 자동으로 생성하여, 코드의 간결함과 유지보수성을 높였다.
UserDto.class
이 코드는 UserDto라는 이름의 DTO(Data Transfer Object) 클래스를 정의하고 있다. 이 클래스는 클라이언트에게 User 객체의 일부 속성을 노출하기 위해 사용된다.
package com.sparta.scheduleserver.model.entity.dto;
import com.sparta.scheduleserver.model.entity.User;
import lombok.Getter;
@Getter
//user객체의 일부 속성을 클라이언트에게 노출하기 위한 클래스
public class UserDto {
private final Long userId;
private final String username;
private final String email;
public UserDto(User user) {
this.userId = user.getUserId();
this.username = user.getUsername();
this.email = user.getEmail();
}
}
주요 구성 요소
- 클래스 어노테이션:
- @Getter: Lombok 라이브러리를 사용하여 클래스의 모든 필드에 대해 getter 메서드를 자동으로 생성한다. 이를 통해 필드 값을 쉽게 읽을 수 있다.
- 필드:
- userId: 사용자의 고유 식별자 ID를 저장한다. User 객체의 getUserId() 메서드로 값을 가져온다.
- username: 사용자의 이름을 저장한다. User 객체의 getUsername() 메서드로 값을 가져온다.
- email: 사용자의 이메일 주소를 저장한다. User 객체의 getEmail() 메서드로 값을 가져온다.
- 생성자:
- UserDto(User user): 이 생성자는 User 엔티티 객체를 받아서 UserDto 객체를 초기화한다.
- userId, username, email 필드는 User 객체의 해당 메서드를 호출하여 값을 설정한다.
- UserDto(User user): 이 생성자는 User 엔티티 객체를 받아서 UserDto 객체를 초기화한다.
UserDto 클래스는 클라이언트에게 User 객체의 일부 속성을 노출하기 위해 사용되는 DTO이다. 이 클래스는 사용자 ID, 이름, 이메일 주소를 포함하며, 이러한 정보만을 클라이언트에게 제공할 때 사용된다. @Getter를 통해 필드 값에 접근할 수 있으며, final로 선언된 필드를 통해 객체 불변성을 보장한다. 생성자를 통해 User 객체를 UserDto로 변환하여 필요한 정보를 제공할 수 있다. 이 DTO는 클라이언트가 사용자 정보를 필요로 할 때, 필요한 최소한의 데이터만을 제공하는 데 유용하다.
Model
error
ErrorCode.enum
이 코드는 ErrorCode라는 이름의 enum 클래스를 정의하고 있으며, 애플리케이션 내에서 발생할 수 있는 다양한 오류를 코드와 메시지로 관리하기 위해 사용된다. 이 enum은 오류 코드와 관련 메시지를 정의하고 제공한다.
package com.sparta.scheduleserver.model.entity.error;
public enum ErrorCode {
// User 관련 예외 메시지
USER_NOT_FOUND("U001", "사용자를 찾을 수 없습니다."),
USER_ALREADY_EXISTS("U002", "이미 존재하는 사용자입니다."),
EMAIL_ALREADY_EXISTS("U003", "이미 존재하는 이메일입니다."),
INVALID_PASSWORD("U004", "비밀번호가 일치하지 않습니다."),
// Schedule 관련 예외 메시지
SCHEDULE_NOT_FOUND("S001", "일정을 찾을 수 없습니다."),
SCHEDULE_CREATION_FAILED("S002", "일정 생성에 실패했습니다."),
SCHEDULE_UPDATE_FAILED("S003", "일정 수정에 실패했습니다."),
// JWT 관련 예외 메시지
INVALID_TOKEN("J001", "유효하지 않은 토큰입니다."),
TOKEN_EXPIRED("J002", "토큰이 만료되었습니다."),
TOKEN_CREATION_FAILED("J003", "토큰 생성에 실패했습니다.");
private final String code;
private final String message;
ErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
주요 구성 요소
- 열거형 상수:
- User 관련 예외 메시지:
- USER_NOT_FOUND("U001", "사용자를 찾을 수 없습니다."): 사용자 정보를 찾을 수 없을 때의 오류 코드와 메시지.
- USER_ALREADY_EXISTS("U002", "이미 존재하는 사용자입니다."): 이미 존재하는 사용자일 때의 오류 코드와 메시지.
- EMAIL_ALREADY_EXISTS("U003", "이미 존재하는 이메일입니다."): 이미 존재하는 이메일일 때의 오류 코드와 메시지.
- INVALID_PASSWORD("U004", "비밀번호가 일치하지 않습니다."): 비밀번호가 일치하지 않을 때의 오류 코드와 메시지.
- Schedule 관련 예외 메시지:
- SCHEDULE_NOT_FOUND("S001", "일정을 찾을 수 없습니다."): 일정을 찾을 수 없을 때의 오류 코드와 메시지.
- SCHEDULE_CREATION_FAILED("S002", "일정 생성에 실패했습니다."): 일정 생성에 실패했을 때의 오류 코드와 메시지.
- SCHEDULE_UPDATE_FAILED("S003", "일정 수정에 실패했습니다."): 일정 수정에 실패했을 때의 오류 코드와 메시지.
- JWT 관련 예외 메시지:
- INVALID_TOKEN("J001", "유효하지 않은 토큰입니다."): 유효하지 않은 JWT 토큰일 때의 오류 코드와 메시지.
- TOKEN_EXPIRED("J002", "토큰이 만료되었습니다."): 만료된 JWT 토큰일 때의 오류 코드와 메시지.
- TOKEN_CREATION_FAILED("J003", "토큰 생성에 실패했습니다."): JWT 토큰 생성 실패 시의 오류 코드와 메시지.
- User 관련 예외 메시지:
- 필드:
- code: 오류 코드를 저장하는 필드. 오류를 구별하기 위한 식별자로 사용된다.
- message: 오류 메시지를 저장하는 필드. 오류의 원인에 대한 설명을 제공한다.
- 생성자:
- ErrorCode(String code, String message): enum 상수의 오류 코드와 메시지를 초기화하는 생성자이다. 이 생성자는 private으로 선언되어 있으며, enum의 상수 정의 시에만 사용된다.
- 메서드:
- getCode(): 오류 코드 값을 반환하는 메서드이다.
- getMessage(): 오류 메시지 값을 반환하는 메서드이다.
ErrorCode 클래스는 애플리케이션에서 발생할 수 있는 다양한 오류 상황을 코드와 메시지로 관리하는 데 사용된다. 각 오류는 고유한 코드와 설명 메시지를 가지며, 이를 통해 오류를 식별하고 적절한 처리를 할 수 있다. enum 상수로 정의된 오류 코드와 메시지는 일관된 오류 처리를 가능하게 하며, 클라이언트에게 명확한 오류 정보를 제공하는 데 유용하다.
Model
repository
ScheduleRepository.interface
이 코드는 ScheduleRepository라는 이름의 JPA 리포지토리 인터페이스를 정의하고 있다. 이 인터페이스는 Schedule 엔티티에 대한 CRUD(Create, Read, Update, Delete) 작업을 수행하기 위해 Spring Data JPA에서 제공하는 기본적인 리포지토리 기능을 상속받는다.
package com.sparta.scheduleserver.model.entity.repository;
import com.sparta.scheduleserver.model.entity.Schedule;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ScheduleRepository extends JpaRepository<Schedule, Long> {
}
주요 구성 요소
- 인터페이스 상속:
- JpaRepository<Schedule, Long>: JpaRepository는 Spring Data JPA가 제공하는 기본 리포지토리 인터페이스로, CRUD 및 페이징, 정렬과 같은 다양한 데이터베이스 작업을 수행할 수 있는 메서드를 제공한다.
- Schedule: 리포지토리가 다루는 엔티티 클래스이다.
- Long: Schedule 엔티티의 기본 키 타입이다.
- JpaRepository<Schedule, Long>: JpaRepository는 Spring Data JPA가 제공하는 기본 리포지토리 인터페이스로, CRUD 및 페이징, 정렬과 같은 다양한 데이터베이스 작업을 수행할 수 있는 메서드를 제공한다.
- 기능:
- JpaRepository를 상속받음으로써 ScheduleRepository는 다음과 같은 기본 메서드를 자동으로 제공받는다:
- CRUD 메서드: save(), findById(), findAll(), deleteById() 등.
- 페이징 및 정렬: findAll(Pageable pageable), findAll(Sort sort) 등.
- 쿼리 메서드: findBy 접두어를 사용하여 엔티티 필드를 기반으로 쿼리 메서드를 자동으로 생성할 수 있다.
- JpaRepository를 상속받음으로써 ScheduleRepository는 다음과 같은 기본 메서드를 자동으로 제공받는다:
ScheduleRepository는 Spring Data JPA를 사용하여 Schedule 엔티티에 대한 데이터 접근을 처리하는 리포지토리 인터페이스이다. JpaRepository를 상속받음으로써, 엔티티의 기본 CRUD 작업 및 추가적인 데이터베이스 작업을 손쉽게 수행할 수 있는 메서드를 자동으로 제공받는다. 이 리포지토리는 Schedule 엔티티와 연관된 데이터베이스 작업을 효율적으로 관리하고, 쿼리 메서드를 통해 복잡한 데이터 조회를 쉽게 구현할 수 있도록 돕는다.
CommentRepository.interface
package com.sparta.scheduleserver.model.entity.repository;
import com.sparta.scheduleserver.model.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface CommentRepository extends JpaRepository<Comment, Long> {
Optional<Comment> findByCommentId(long commentId); // commentId 값 여부 확인
}
추가 메서드:
Optional<Comment> findByCommentId(long commentId): Comment 엔티티에서 commentId 값을 기반으로 댓글을 조회하는 커스텀 메서드이다.
- 리턴 타입: Optional<Comment>는 해당 commentId에 맞는 Comment 엔티티가 존재할 수도 있고, 존재하지 않을 수도 있음을 나타낸다. Optional을 사용함으로써 null 체크를 강제하여, 존재하지 않을 경우의 처리를 명시적으로 할 수 있다.
- 매개변수: commentId는 댓글의 고유 식별자로, 댓글을 찾기 위해 사용된다.
UserRepository.interface
package com.sparta.scheduleserver.model.entity.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.sparta.scheduleserver.model.entity.User;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
}
커스텀 메서드:
- Optional<User> findByUsername(String username):
- 리턴 타입: Optional<User>는 username에 맞는 User 엔티티가 존재할 수도 있고, 존재하지 않을 수도 있음을 나타낸다. Optional을 사용하여 null 체크를 강제하고, 데이터가 존재하지 않을 경우의 처리를 명시적으로 할 수 있다.
- 매개변수: username은 사용자 이름으로, 사용자 정보를 조회하기 위해 사용된다.
- Optional<User> findByEmail(String email):
- 리턴 타입: Optional<User>는 email에 맞는 User 엔티티가 존재할 수도 있고, 존재하지 않을 수도 있음을 나타낸다. Optional을 사용하여 null 체크를 강제하고, 데이터가 존재하지 않을 경우의 처리를 명시적으로 할 수 있다.
- 매개변수: email은 사용자 이메일 주소로, 사용자 정보를 조회하기 위해 사용된다.
UserScheduleRepository.interface
이 인터페이스는 UserSchedule 엔티티와 관련된 데이터베이스 작업을 효율적으로 관리하고 지원한다.
package com.sparta.scheduleserver.model.entity.repository;
import com.sparta.scheduleserver.model.entity.UserSchedule;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserScheduleRepository extends JpaRepository<UserSchedule, Long> {
}
Controller
CommentController.class
이 코드는 CommentController라는 이름의 Spring MVC 컨트롤러 클래스를 정의하고 있으며, 댓글에 대한 CRUD(Create, Read, Update, Delete) 작업을 처리하는 REST API를 구현하고 있다. 이 컨트롤러는 CommentService를 사용하여 비즈니스 로직을 처리하고, 클라이언트 요청에 대한 적절한 응답을 반환한다.
package com.sparta.scheduleserver.controller;
import com.sparta.scheduleserver.model.entity.dto.CommentRequestDto;
import com.sparta.scheduleserver.model.entity.dto.CommentResponseDto;
import com.sparta.scheduleserver.service.CommentService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/comment")
@RequiredArgsConstructor
public class CommentController {
private final CommentService commentService;
// 댓글 등록
@PostMapping
public ResponseEntity<CommentResponseDto> createComment(
@RequestBody CommentRequestDto requestDto) {
CommentResponseDto responseDto = commentService.createComment(requestDto);
return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
}
// 댓글 조회 단건
@GetMapping("/{id}")
public ResponseEntity<CommentResponseDto> getComment(@PathVariable("id") long commentId) {
CommentResponseDto responseDto = commentService.getCommentById(commentId);
return new ResponseEntity<>(responseDto, HttpStatus.OK);
}
// 댓글 수정
@PutMapping("/{id}")
public ResponseEntity<CommentResponseDto> updateComment(
@PathVariable("id") long commentId,
@RequestBody CommentRequestDto requestDto) {
CommentResponseDto responseDto = commentService.updateComment(commentId, requestDto);
return new ResponseEntity<>(responseDto, HttpStatus.OK);
}
// 댓글 삭제
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteComment(@PathVariable("id") long commentId) {
commentService.deleteComment(commentId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
주요 구성 요소
- 클래스 및 어노테이션:
- @RestController: 이 클래스가 RESTful 웹 서비스의 컨트롤러임을 나타내며, JSON/XML 형식의 데이터를 반환하는 메서드를 포함한다.
- @RequestMapping("/api/comment"): 이 클래스의 모든 메서드가 /api/comment 경로에서 요청을 처리하도록 지정한다.
- @RequiredArgsConstructor: Lombok 어노테이션으로, 클래스의 final 필드에 대해 자동으로 생성자를 생성하여 의존성 주입을 수행한다.
- 메서드 및 매핑:
- 댓글 등록:
- @PostMapping: HTTP POST 요청을 처리하며, 댓글을 생성하는 메서드다.
- createComment(): @RequestBody로 받은 CommentRequestDto를 사용하여 댓글을 생성하고, 생성된 댓글을 CommentResponseDto로 반환한다.
- 응답: 생성된 댓글과 함께 HTTP 201 (Created) 상태 코드를 반환한다.
- 댓글 조회 단건:
- @GetMapping("/{id}"): HTTP GET 요청을 처리하며, URL 경로의 {id} 부분을 commentId로 매핑한다.
- getComment(): commentId에 해당하는 댓글을 조회하고, 조회된 댓글을 CommentResponseDto로 반환한다.
- 응답: 조회된 댓글과 함께 HTTP 200 (OK) 상태 코드를 반환한다.
- 댓글 수정:
- @PutMapping("/{id}"): HTTP PUT 요청을 처리하며, URL 경로의 {id} 부분을 commentId로 매핑한다.
- updateComment(): commentId에 해당하는 댓글을 수정하고, 수정된 댓글을 CommentResponseDto로 반환한다.
- 응답: 수정된 댓글과 함께 HTTP 200 (OK) 상태 코드를 반환한다.
- 댓글 삭제:
- @DeleteMapping("/{id}"): HTTP DELETE 요청을 처리하며, URL 경로의 {id} 부분을 commentId로 매핑한다.
- deleteComment(): commentId에 해당하는 댓글을 삭제한다.
- 응답: 삭제 완료 후 HTTP 204 (No Content) 상태 코드를 반환한다.
- 댓글 등록:
CommentController는 댓글에 대한 CRUD 작업을 처리하는 REST API를 제공하는 Spring MVC 컨트롤러이다. 댓글을 등록, 조회, 수정, 삭제하는 엔드포인트를 제공하며, 각각의 메서드는 CommentService를 통해 비즈니스 로직을 처리하고, 클라이언트에게 적절한 HTTP 응답을 반환한다. @RestController와 @RequestMapping을 사용하여 RESTful 웹 서비스의 요청을 처리하며, Lombok의 @RequiredArgsConstructor를 사용하여 의존성 주입을 간편하게 처리하고 있다.
ScheduleController.class
이 코드는 ScheduleController라는 이름의 Spring MVC 컨트롤러 클래스를 정의하고 있으며, 일정에 대한 CRUD(Create, Read, Update, Delete) 작업 및 기타 기능을 처리하는 REST API를 구현하고 있다. 이 컨트롤러는 ScheduleService를 통해 비즈니스 로직을 처리하고, 클라이언트 요청에 대한 적절한 응답을 반환한다.
package com.sparta.scheduleserver.controller;
import com.sparta.scheduleserver.model.entity.dto.ScheduleExceptionAuthorDto;
import com.sparta.scheduleserver.model.entity.dto.ScheduleRequestDto;
import com.sparta.scheduleserver.model.entity.dto.ScheduleResponseDto;
import com.sparta.scheduleserver.service.ScheduleService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/schedules")
@RequiredArgsConstructor
public class ScheduleController {
private final ScheduleService scheduleService;
//일정 등록
@PostMapping
public ResponseEntity<ScheduleResponseDto> createSchedule(@RequestBody ScheduleRequestDto requestDto) {
ScheduleResponseDto responseDto = scheduleService.createSchedule(requestDto);
return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
}
// 전체 일정 조회
@GetMapping("/all")
public ResponseEntity<List<ScheduleExceptionAuthorDto>> getAllSchedules(){
List<ScheduleExceptionAuthorDto> schedules = scheduleService.getAllSchedules();
return new ResponseEntity<>(schedules, HttpStatus.OK);
}
//일정 조회 단건
@GetMapping("/{id}")
public ResponseEntity<ScheduleResponseDto> getSchedule(@PathVariable long id) {
ScheduleResponseDto responseDto = scheduleService.getScheduleById(id);
return new ResponseEntity<>(responseDto, HttpStatus.OK);
}
//일정 수정
@PutMapping("/{id}")
public ResponseEntity<ScheduleResponseDto> updateSchedule(
@PathVariable long id,
@RequestBody ScheduleRequestDto requestDto) {
ScheduleResponseDto responseDto = scheduleService.updateSchedule(id, requestDto);
return new ResponseEntity<>(responseDto, HttpStatus.OK);
}
//일정 삭제
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteSchedule(
@PathVariable("id") long id){
scheduleService.deleteSchedule(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
//댓글 개수,페이지 조회
@GetMapping("/page")
public ResponseEntity<Page<ScheduleResponseDto>> getSchedules(
@RequestParam(value = "page", defaultValue = "0") int page, //페이지 번호는 0부터 시작
@RequestParam(value = "size", defaultValue = "10") int size) // 기본 페이지 크기는 10으로 설정함
{
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "updatedDate"));
Page<ScheduleResponseDto> schedulePage = scheduleService.getSchedules(pageable);
// ScheduleService.getSchedules(pageable)에서 반환된 Page<ScheduleResponseDto>는 페이지네이션과 정렬이 적용된 데이터를 제공한다.
return new ResponseEntity<>(schedulePage, HttpStatus.OK);
}
//담당 유저 등록
@PostMapping("/{scheduleId}/assign")
public ResponseEntity<Void> assignUsersToSchedule(
@PathVariable Long scheduleId,
@RequestBody List<Long> userIds)
{
scheduleService.assignUsersToSchedule(scheduleId, userIds);
return ResponseEntity.ok().build();
//성공시 :HTTPstatus 200반환, build를 통해 body없이 클라이언트에게 응답을 보냄
}
}
주요 구성 요소
- 클래스 및 어노테이션:
- @RestController: 이 클래스가 RESTful 웹 서비스의 컨트롤러임을 나타내며, JSON/XML 형식의 데이터를 반환하는 메서드를 포함한다.
- @RequestMapping("/api/schedules"): 이 클래스의 모든 메서드가 /api/schedules 경로에서 요청을 처리하도록 지정한다.
- @RequiredArgsConstructor: Lombok 어노테이션으로, 클래스의 final 필드에 대해 자동으로 생성자를 생성하여 의존성 주입을 수행한다.
- 메서드 및 매핑:
- 일정 등록:
- @PostMapping: HTTP POST 요청을 처리하며, 새로운 일정을 생성하는 메서드이다.
- createSchedule(): @RequestBody로 받은 ScheduleRequestDto를 사용하여 일정을 생성하고, 생성된 일정 정보를 ScheduleResponseDto로 반환한다.
- 응답: 생성된 일정과 함께 HTTP 201 (Created) 상태 코드를 반환한다.
- 전체 일정 조회:
- @GetMapping("/all"): HTTP GET 요청을 처리하며, 모든 일정을 조회하는 메서드이다.
- getAllSchedules(): ScheduleService를 호출하여 모든 일정을 조회하고, ScheduleExceptionAuthorDto 리스트로 반환한다.
- 응답: 조회된 일정 리스트와 함께 HTTP 200 (OK) 상태 코드를 반환한다.
- 일정 조회 단건:
- @GetMapping("/{id}"): HTTP GET 요청을 처리하며, URL 경로의 {id} 부분을 id로 매핑한다.
- getSchedule(): id에 해당하는 일정을 조회하고, ScheduleResponseDto로 반환한다.
- 응답: 조회된 일정과 함께 HTTP 200 (OK) 상태 코드를 반환한다.
- 일정 수정:
- @PutMapping("/{id}"): HTTP PUT 요청을 처리하며, URL 경로의 {id} 부분을 id로 매핑한다.
- updateSchedule(): id에 해당하는 일정을 수정하고, 수정된 일정 정보를 ScheduleResponseDto로 반환한다.
- 응답: 수정된 일정과 함께 HTTP 200 (OK) 상태 코드를 반환한다.
- 일정 삭제:
- @DeleteMapping("/{id}"): HTTP DELETE 요청을 처리하며, URL 경로의 {id} 부분을 id로 매핑한다.
- deleteSchedule(): id에 해당하는 일정을 삭제한다.
- 응답: 삭제 완료 후 HTTP 204 (No Content) 상태 코드를 반환한다.
- 페이지 조회:
- @GetMapping("/page"): HTTP GET 요청을 처리하며, 일정 목록을 페이지 단위로 조회하는 메서드이다.
- getSchedules(): page와 size 파라미터를 통해 페이지 번호와 페이지 크기를 지정하고, PageRequest를 사용하여 페이지네이션 및 정렬을 적용한 Page<ScheduleResponseDto>를 반환한다.
- 응답: 페이지네이션된 일정과 함께 HTTP 200 (OK) 상태 코드를 반환한다.
- 담당 유저 등록:
- @PostMapping("/{scheduleId}/assign"): HTTP POST 요청을 처리하며, 일정에 담당 유저를 배정하는 메서드이다.
- assignUsersToSchedule(): scheduleId에 해당하는 일정에 userIds 리스트에 있는 유저들을 배정한다.
- 응답: 성공 시 HTTP 200 (OK) 상태 코드를 반환하며, ResponseEntity.ok().build()를 통해 본문 없이 응답을 보낸다.
- 일정 등록:
ScheduleController는 일정에 대한 CRUD 작업과 추가 기능을 처리하는 RESTful API 컨트롤러이다. 일정을 등록, 조회, 수정, 삭제하며, 전체 일정 조회와 페이지 단위의 일정 조회 기능을 제공한다. 또한, 일정에 담당 유저를 배정할 수 있는 기능을 포함하고 있다. @RestController와 @RequestMapping을 사용하여 RESTful 웹 서비스의 요청을 처리하며, Lombok의 @RequiredArgsConstructor를 사용하여 의존성 주입을 간편하게 처리하고 있다.
UserController.class
이 코드는 UserController라는 이름의 Spring MVC 컨트롤러 클래스를 정의하고 있으며, 사용자 관련 기능을 처리하는 REST API를 구현하고 있다. 이 컨트롤러는 사용자 등록, 조회, 수정, 삭제와 함께 로그인 기능을 담당한다.
package com.sparta.scheduleserver.controller;
import com.sparta.scheduleserver.model.entity.dto.LoginRequestDto;
import com.sparta.scheduleserver.model.entity.dto.UserRequestDto;
import com.sparta.scheduleserver.model.entity.dto.UserResponseDto;
import com.sparta.scheduleserver.service.jwt.JwtUtil;
import com.sparta.scheduleserver.service.UserService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final JwtUtil jwtUtil;
// 유저 등록
@PostMapping
public ResponseEntity<UserResponseDto> createUser(@RequestBody UserRequestDto requestDto) {
UserResponseDto responseDto = userService.createUser(requestDto);
return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
}
// 유저 조회 단건
@GetMapping("/{id}")
public ResponseEntity<UserResponseDto> getUser(@PathVariable long id) {
UserResponseDto responseDto = userService.getUserById(id);
return new ResponseEntity<>(responseDto, HttpStatus.OK);
}
// 유저 수정
@PutMapping("/{id}")
public ResponseEntity<UserResponseDto> updateUser(
@PathVariable long id,
@RequestBody UserRequestDto requestDto) {
UserResponseDto responseDto = userService.updateUser(id, requestDto);
return new ResponseEntity<>(responseDto, HttpStatus.OK);
}
// 유저 삭제
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable long id) {
userService.deleteUser(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
// 로그인 엔드포인트
@PostMapping("/login")
public ResponseEntity<String> loginUser(
@RequestBody LoginRequestDto loginRequestDto,
HttpServletResponse response) {
// 사용자 인증 로직
userService.authenticateUser(loginRequestDto.getEmail(), loginRequestDto.getPassword());
// JWT 토큰 생성
String token = jwtUtil.createToken(loginRequestDto.getPassword());
// JWT 토큰을 쿠키에 추가
jwtUtil.addJwtToCookie(token, response);
return ResponseEntity.ok("로그인 성공");
}
}
주요 구성 요소
- 클래스 및 어노테이션:
- @RestController: 이 클래스가 RESTful 웹 서비스의 컨트롤러임을 나타내며, JSON/XML 형식의 데이터를 반환하는 메서드를 포함한다.
- @RequestMapping("/api/user"): 이 클래스의 모든 메서드가 /api/user 경로에서 요청을 처리하도록 지정한다.
- @RequiredArgsConstructor: Lombok 어노테이션으로, 클래스의 final 필드에 대해 자동으로 생성자를 생성하여 의존성 주입을 수행한다.
- 메서드 및 매핑:
- 유저 등록:
- @PostMapping: HTTP POST 요청을 처리하며, 새로운 사용자를 생성하는 메서드이다.
- createUser(): @RequestBody로 받은 UserRequestDto를 사용하여 사용자를 생성하고, 생성된 사용자 정보를 UserResponseDto로 반환한다.
- 응답: 생성된 사용자와 함께 HTTP 201 (Created) 상태 코드를 반환한다.
- 유저 조회 단건:
- @GetMapping("/{id}"): HTTP GET 요청을 처리하며, URL 경로의 {id} 부분을 id로 매핑한다.
- getUser(): id에 해당하는 사용자를 조회하고, UserResponseDto로 반환한다.
- 응답: 조회된 사용자와 함께 HTTP 200 (OK) 상태 코드를 반환한다.
- 유저 수정:
- @PutMapping("/{id}"): HTTP PUT 요청을 처리하며, URL 경로의 {id} 부분을 id로 매핑한다.
- updateUser(): id에 해당하는 사용자를 수정하고, 수정된 사용자 정보를 UserResponseDto로 반환한다.
- 응답: 수정된 사용자와 함께 HTTP 200 (OK) 상태 코드를 반환한다.
- 유저 삭제:
- @DeleteMapping("/{id}"): HTTP DELETE 요청을 처리하며, URL 경로의 {id} 부분을 id로 매핑한다.
- deleteUser(): id에 해당하는 사용자를 삭제한다.
- 응답: 삭제 완료 후 HTTP 204 (No Content) 상태 코드를 반환한다.
- 로그인:
- @PostMapping("/login"): HTTP POST 요청을 처리하며, 사용자 로그인 요청을 처리하는 메서드이다.
- loginUser(): @RequestBody로 받은 LoginRequestDto에서 이메일과 비밀번호를 사용하여 사용자를 인증하고, 성공적으로 인증된 경우 JWT 토큰을 생성한다.
- JwtUtil 클래스의 createToken() 메서드를 사용하여 JWT 토큰을 생성하고, addJwtToCookie() 메서드를 사용하여 생성된 토큰을 쿠키에 추가한다.
- 응답: 로그인 성공 메시지와 함께 HTTP 200 (OK) 상태 코드를 반환한다.
- 유저 등록:
UserController는 사용자 관련 CRUD 작업과 로그인 기능을 제공하는 RESTful API 컨트롤러이다. 사용자를 등록, 조회, 수정, 삭제하며, 사용자 인증 후 JWT 토큰을 생성하고 쿠키에 추가하는 로그인 기능을 포함하고 있다. @RestController와 @RequestMapping을 사용하여 RESTful 웹 서비스의 요청을 처리하고, Lombok의 @RequiredArgsConstructor를 통해 의존성 주입을 간편하게 처리하고 있다.
이 코드는 CommentService라는 이름의 서비스 클래스를 정의하고 있으며, 댓글 관련 비즈니스 로직을 처리하는 메서드를 포함하고 있다. CommentService는 댓글의 조회, 등록, 수정, 삭제 작업을 수행하며, JPA를 사용하여 데이터베이스와 상호작용한다.
package com.sparta.scheduleserver.service;
import com.sparta.scheduleserver.model.entity.dto.CommentRequestDto;
import com.sparta.scheduleserver.model.entity.dto.CommentResponseDto;
import com.sparta.scheduleserver.model.entity.Comment;
import com.sparta.scheduleserver.model.entity.Schedule;
import com.sparta.scheduleserver.model.entity.error.ErrorCode;
import com.sparta.scheduleserver.model.entity.repository.CommentRepository;
import com.sparta.scheduleserver.model.entity.repository.ScheduleRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@RequiredArgsConstructor
@Transactional
public class CommentService {
private final CommentRepository commentRepository;
private final ScheduleRepository scheduleRepository;
// 댓글 조회 메서드
public CommentResponseDto getCommentById(long commentId) {
Optional<Comment> commentOptional = commentRepository.findByCommentId(commentId);
if (commentOptional.isPresent()) {
return new CommentResponseDto(commentOptional.get());
} else {
throw new RuntimeException(ErrorCode.USER_NOT_FOUND.getMessage() + commentId);
}
}
// 댓글 등록 값 반환
@Transactional
public CommentResponseDto createComment(CommentRequestDto requestDto){
Long id = requestDto.getId(); //변수명은 가독성 좋게
Schedule schedule = scheduleRepository.findById(id)
.orElseThrow(() -> new RuntimeException(ErrorCode.USER_NOT_FOUND.getMessage() + id));
Comment comment = new Comment(
requestDto.getUsername(),
requestDto.getCommentContent(),
schedule
);
commentRepository.save(comment);
return new CommentResponseDto(comment);
}
// 댓글 수정
@Transactional
public CommentResponseDto updateComment(long commentId, CommentRequestDto responseDto){
Comment comment = commentRepository.findByCommentId(commentId)
.orElseThrow(() -> new RuntimeException(ErrorCode.USER_NOT_FOUND.getMessage() + commentId));
comment.update(responseDto.getCommentContent());
commentRepository.save(comment);
return new CommentResponseDto(comment);
}
// 댓글 삭제
@Transactional
public void deleteComment(long commentId) {
Comment comment = commentRepository.findByCommentId(commentId)
.orElseThrow(() -> new RuntimeException(ErrorCode.USER_NOT_FOUND.getMessage() + commentId));
commentRepository.delete(comment);
}
}
주요 구성 요소
- 클래스 및 어노테이션:
- @Service: 이 클래스가 서비스 계층의 컴포넌트임을 나타내며, 비즈니스 로직을 구현한다.
- @RequiredArgsConstructor: Lombok 어노테이션으로, final 필드에 대해 자동으로 생성자를 생성하여 의존성 주입을 수행한다.
- @Transactional: 메서드나 클래스에서 트랜잭션을 관리하도록 지정한다. 모든 메서드가 하나의 트랜잭션에서 실행된다.
- 의존성 주입:
- CommentRepository: 댓글 데이터를 저장하고 조회하는 레포지토리 인터페이스이다.
- ScheduleRepository: 댓글이 연결된 일정을 조회하는 레포지토리 인터페이스이다.
- 메서드 및 로직:
- 댓글 조회:
- getCommentById(long commentId): 주어진 commentId로 댓글을 조회하고, 댓글이 존재하지 않으면 예외를 던진다. 댓글이 존재하면 CommentResponseDto로 변환하여 반환한다.
- 예외 처리: 댓글이 존재하지 않을 경우 ErrorCode.USER_NOT_FOUND 에러 메시지를 포함하여 RuntimeException을 던진다.
- 댓글 등록:
- createComment(CommentRequestDto requestDto): requestDto를 통해 받은 댓글 정보를 기반으로 댓글을 생성한다. 댓글이 연결된 Schedule을 조회하고, 댓글을 저장한 후 CommentResponseDto로 변환하여 반환한다.
- 예외 처리: 연결된 Schedule이 존재하지 않을 경우 ErrorCode.USER_NOT_FOUND 에러 메시지를 포함하여 RuntimeException을 던진다.
- 댓글 수정:
- updateComment(long commentId, CommentRequestDto responseDto): 주어진 commentId로 댓글을 조회하고, 댓글이 존재하면 responseDto에서 새로운 댓글 내용을 받아 업데이트한다. 업데이트된 댓글을 저장한 후 CommentResponseDto로 변환하여 반환한다.
- 예외 처리: 댓글이 존재하지 않을 경우 ErrorCode.USER_NOT_FOUND 에러 메시지를 포함하여 RuntimeException을 던진다.
- 댓글 삭제:
- deleteComment(long commentId): 주어진 commentId로 댓글을 조회하고, 댓글이 존재하면 삭제한다.
- 예외 처리: 댓글이 존재하지 않을 경우 ErrorCode.USER_NOT_FOUND 에러 메시지를 포함하여 RuntimeException을 던진다.
- 댓글 조회:
CommentService는 댓글 관련 비즈니스 로직을 담당하는 서비스 클래스이다. 댓글을 조회, 등록, 수정, 삭제하는 기능을 제공하며, JPA를 사용하여 데이터베이스와 상호작용한다. @Transactional 어노테이션을 사용하여 트랜잭션을 관리하고 있으며, 댓글이 존재하지 않을 경우 적절한 예외를 던져 에러 처리를 수행한다. 각 메서드는 댓글에 대한 CRUD 작업을 처리하며, CommentRequestDto와 CommentResponseDto를 통해 데이터 전송 및 응답을 수행한다.
ScheduleService.class
ScheduleService 클래스는 일정을 관리하는 다양한 비즈니스 로직을 처리하는 서비스 클래스이다. 주로 일정의 CRUD(생성, 조회, 수정, 삭제) 작업과 사용자 할당 기능을 구현하고 있다.
package com.sparta.scheduleserver.service;
import com.sparta.scheduleserver.model.entity.dto.ScheduleExceptionAuthorDto;
import com.sparta.scheduleserver.model.entity.dto.ScheduleRequestDto;
import com.sparta.scheduleserver.model.entity.dto.ScheduleResponseDto;
import com.sparta.scheduleserver.model.entity.Schedule;
import com.sparta.scheduleserver.model.entity.User;
import com.sparta.scheduleserver.model.entity.UserSchedule;
import com.sparta.scheduleserver.model.entity.error.ErrorCode;
import com.sparta.scheduleserver.model.entity.repository.ScheduleRepository;
import com.sparta.scheduleserver.model.entity.repository.UserRepository;
import com.sparta.scheduleserver.model.entity.repository.UserScheduleRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class ScheduleService {
private final ScheduleRepository scheduleRepository;
private final UserRepository userRepository;
private final UserScheduleRepository userScheduleRepository;
// 일정 조회 메서드
public ScheduleResponseDto getScheduleById(long id) {
Schedule schedule = scheduleRepository.findById(id)
.orElseThrow(() -> new RuntimeException(ErrorCode.USER_NOT_FOUND.getMessage() + id));
return new ScheduleResponseDto(schedule);
}
// 전체 일정 조회
@Transactional(readOnly = true)
public List<ScheduleExceptionAuthorDto> getAllSchedules(){
List<Schedule> scheduleList = scheduleRepository.findAll();
List<ScheduleExceptionAuthorDto> scheduleInquiry = new ArrayList<>();
for(Schedule s: scheduleList){
ScheduleExceptionAuthorDto dto = new ScheduleExceptionAuthorDto(
s.getId(),
s.getTitle(),
s.getContent()
);
scheduleInquiry.add(dto);
}
return scheduleInquiry;
}
// 일정 등록 메서드
@Transactional
public ScheduleResponseDto createSchedule(ScheduleRequestDto requestDto) {
Long authorId = requestDto.getAuthorId(); // 작성자의 ID를 가져옴
User author = userRepository.findById(authorId)
.orElseThrow(() -> new RuntimeException(ErrorCode.USER_NOT_FOUND.getMessage() + authorId)); // 작성자 조회
// 작성자를 포함한 스케줄 생성
Schedule schedule = new Schedule(
author,
requestDto.getTitle(),
requestDto.getContent()
);
scheduleRepository.save(schedule);
return new ScheduleResponseDto(schedule); // 생성된 스케줄의 정보를 반환
}
// 일정 수정 메서드
@Transactional
public ScheduleResponseDto updateSchedule(long id, ScheduleRequestDto requestDto) {
Schedule schedule = scheduleRepository.findById(id)
.orElseThrow(() -> new RuntimeException(ErrorCode.SCHEDULE_NOT_FOUND.getMessage() + id));
schedule.update(requestDto.getAuthorId() ,requestDto.getTitle(), requestDto.getContent());
scheduleRepository.save(schedule);
return new ScheduleResponseDto(schedule);
}
// 일정 삭제 메서드
@Transactional
public void deleteSchedule(long id) {
Schedule schedule = scheduleRepository.findById(id)
.orElseThrow(() -> new RuntimeException(ErrorCode.SCHEDULE_NOT_FOUND.getMessage() + id));
scheduleRepository.delete(schedule);
}
// 페이지네이션된 결과 조회
@Transactional(readOnly = true) //메서드 내에서 데이터베이스와의 상호작용이 읽기 전용
public Page<ScheduleResponseDto> getSchedules(Pageable pageable) {
//Schedule 엔티티의 모든 데이터를 페이징하여 조회
Page<Schedule> schedules = scheduleRepository.findAll(pageable);
List<ScheduleResponseDto> dtoList = new ArrayList<>();
for (Schedule schedule : schedules.getContent()){
dtoList.add(new ScheduleResponseDto(schedule));
}
return new PageImpl<>(dtoList, pageable, schedules.getTotalElements());
}
@Transactional
public void assignUsersToSchedule(Long scheduleId, List<Long> userIds){
Schedule schedule = scheduleRepository.findById(scheduleId)
.orElseThrow(() -> new IllegalArgumentException(ErrorCode.SCHEDULE_NOT_FOUND.getMessage()));
for(Long userId : userIds){
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException(ErrorCode.USER_NOT_FOUND.getMessage()));
UserSchedule userSchedule = new UserSchedule();
userSchedule.setUser(user);
userSchedule.setSchedule(schedule);
userScheduleRepository.save(userSchedule);
}
}
}
주요 기능 및 메서드 분석
- 일정 조회:
- getScheduleById(long id): 주어진 ID로 일정을 조회하고, 일정이 존재하지 않으면 RuntimeException을 발생시킨다. 일정이 존재할 경우 ScheduleResponseDto 객체로 변환하여 반환한다. 예외 메시지에 일정 ID를 포함시켜 디버깅을 용이하게 한다.
- 전체 일정 조회:
- getAllSchedules(): 모든 일정을 조회하여 ScheduleExceptionAuthorDto 리스트로 반환한다. 트랜잭션은 읽기 전용으로 설정되어 있어 데이터베이스 성능 최적화를 돕는다. ArrayList를 사용하여 각 Schedule 객체를 DTO로 변환 후 리스트에 추가한다.
- 일정 등록:
- createSchedule(ScheduleRequestDto requestDto): 요청 DTO에서 작성자 ID를 가져와 해당 사용자를 조회한 후, 새 일정을 생성한다. 생성된 일정은 ScheduleResponseDto 객체로 반환된다. 작성자가 존재하지 않으면 RuntimeException을 발생시킨다.
- 일정 수정:
- updateSchedule(long id, ScheduleRequestDto requestDto): 주어진 ID로 일정을 조회하고, 일정이 존재하면 요청 DTO의 내용으로 일정을 업데이트한다. 업데이트 후에는 ScheduleResponseDto로 반환된다. 일정이 존재하지 않을 경우 RuntimeException을 발생시킨다.
- 일정 삭제:
- deleteSchedule(long id): 주어진 ID로 일정을 조회하고, 일정이 존재하면 삭제한다. 일정이 존재하지 않으면 RuntimeException을 발생시킨다.
- 페이지네이션된 일정 조회:
- getSchedules(Pageable pageable): 페이지 요청을 받아 페이지네이션된 일정을 조회하고, 조회된 일정을 ScheduleResponseDto 리스트로 변환하여 PageImpl 객체로 반환한다. PageImpl을 사용하여 페이지네이션 및 정렬 정보를 포함시킨다.
- 유저를 일정에 할당:
- assignUsersToSchedule(Long scheduleId, List<Long> userIds): 주어진 일정 ID로 일정을 조회하고, 각 사용자 ID를 통해 사용자를 조회한 후, UserSchedule 엔티티를 생성하여 사용자와 일정 사이의 관계를 저장한다. 일정 또는 사용자가 존재하지 않을 경우 IllegalArgumentException을 발생시킨다.
UserSevice.class
UserService 클래스는 사용자 관리와 관련된 다양한 비즈니스 로직을 처리하는 서비스 클래스이다. 이 클래스는 사용자 등록, 수정, 삭제 및 인증을 담당하며, 비밀번호 인코딩과 JWT 토큰 생성 및 검증을 포함한다.
package com.sparta.scheduleserver.service;
import com.sparta.scheduleserver.config.PasswordEncoder;
import com.sparta.scheduleserver.model.entity.dto.UserRequestDto;
import com.sparta.scheduleserver.model.entity.dto.UserResponseDto;
import com.sparta.scheduleserver.model.entity.User;
import com.sparta.scheduleserver.model.entity.error.ErrorCode;
import com.sparta.scheduleserver.service.jwt.JwtUtil;
import com.sparta.scheduleserver.model.entity.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;
// 유저 아이디 조회
public UserResponseDto getUserById(long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException(ErrorCode.USER_NOT_FOUND.getMessage() + id));
return new UserResponseDto(user);
}
// 사용자 프로필 조회 메서드
public UserResponseDto getUserProfile(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new IllegalArgumentException(ErrorCode.USER_NOT_FOUND.getMessage()));
return new UserResponseDto(user);
}
// 유저 생성 메서드
@Transactional
public UserResponseDto createUser(UserRequestDto requestDto) {
//회원 확인
String username = requestDto.getUsername();
Optional<User> checkUsername = userRepository.findByUsername(username);
if(checkUsername.isPresent()){
throw new IllegalArgumentException(ErrorCode.USER_ALREADY_EXISTS.getMessage());
}
String encodedPassword = passwordEncoder.encode(requestDto.getPassword());
// email 중복확인
String email = requestDto.getEmail();
Optional<User> checkEmail = userRepository.findByEmail(email);
if(checkEmail.isPresent()){
throw new IllegalArgumentException(ErrorCode.EMAIL_ALREADY_EXISTS.getMessage());
}
User user = new User(username, encodedPassword, email);
userRepository.save(user);
return new UserResponseDto(user);
}
// 유저 수정 메서드
@Transactional
public UserResponseDto updateUser(long id, UserRequestDto requestDto) {
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException(ErrorCode.USER_NOT_FOUND.getMessage() + id));
if(requestDto.getPassword() !=null){
String encodedPassword = passwordEncoder.encode(requestDto.getPassword());
user.setPassword(encodedPassword);
}
// 사용자 정보 업데이트
user.update(requestDto.getUsername(), requestDto.getPassword(), requestDto.getEmail());
userRepository.save(user);
return new UserResponseDto(user);
}
// 유저 삭제 메서드
@Transactional
public void deleteUser(long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException(ErrorCode.USER_NOT_FOUND.getMessage() + id));
userRepository.delete(user);
}
// 로그인 메서드
@Transactional
public UserResponseDto authenticateUser(String email, String password) {
// 사용자 존재 확인
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException(ErrorCode.USER_NOT_FOUND.getMessage()));
// 비밀번호 확인
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new IllegalArgumentException(ErrorCode.INVALID_TOKEN.getMessage());
}
// 인증된 사용자 정보 반환
return new UserResponseDto(user);
}
}
주요 기능 및 메서드 분석
- 유저 아이디 조회:
- getUserById(long id): 주어진 ID로 사용자를 조회하고, 사용자가 존재하지 않으면 RuntimeException을 발생시킨다. 사용자가 존재할 경우 UserResponseDto 객체로 변환하여 반환한다. 예외 메시지에 사용자 ID를 포함하여 디버깅을 용이하게 한다.
- 사용자 프로필 조회:
- getUserProfile(String username): 주어진 사용자 이름으로 사용자를 조회하고, 사용자가 존재하지 않으면 IllegalArgumentException을 발생시킨다. 사용자 정보가 존재할 경우 UserResponseDto 객체로 반환한다. 예외 메시지에 사용자 이름을 포함하여 디버깅을 용이하게 한다.
- 유저 생성:
- createUser(UserRequestDto requestDto):
- 사용자 이름 중복 확인 후, 중복 시 IllegalArgumentException을 발생시킨다.
- 비밀번호를 인코딩한 후 사용자 정보를 저장한다.
- 이메일 중복 확인 후, 중복 시 IllegalArgumentException을 발생시킨다.
- 새로운 사용자 객체를 생성하고 저장 후 UserResponseDto로 반환한다.
- createUser(UserRequestDto requestDto):
- 유저 수정:
- updateUser(long id, UserRequestDto requestDto):
- 주어진 ID로 사용자를 조회하고, 사용자가 존재하지 않으면 RuntimeException을 발생시킨다.
- 비밀번호가 제공된 경우 비밀번호를 인코딩하여 사용자 정보에 업데이트한다.
- 사용자의 이름, 비밀번호, 이메일을 업데이트한 후, 변경된 사용자 정보를 저장하고 UserResponseDto로 반환한다.
- updateUser(long id, UserRequestDto requestDto):
- 유저 삭제:
- deleteUser(long id):
- 주어진 ID로 사용자를 조회하고, 사용자가 존재하지 않으면 RuntimeException을 발생시킨다.
- 사용자가 존재할 경우 해당 사용자를 삭제한다.
- deleteUser(long id):
- 로그인:
- authenticateUser(String email, String password):
- 이메일로 사용자를 조회하고, 사용자가 존재하지 않으면 IllegalArgumentException을 발생시킨다.
- 제공된 비밀번호가 저장된 비밀번호와 일치하는지 확인한다. 일치하지 않으면 IllegalArgumentException을 발생시킨다.
- 인증이 성공할 경우 UserResponseDto로 인증된 사용자 정보를 반환한다.
- authenticateUser(String email, String password):
드디어 마지막 부분인 config...
config
PasswordEncoder.class
PasswordEncoder 클래스는 비밀번호 암호화 및 확인을 담당하는 유틸리티 클래스이다. 이 클래스는 BCrypt 알고리즘을 사용하여 비밀번호를 안전하게 처리한다. 다음은 이 클래스의 주요 구성 요소와 기능에 대한 설명이다:
package com.sparta.scheduleserver.config;
import at.favre.lib.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Component;
@Component
public class PasswordEncoder {
// BCrypt의 기본 설정으로 암호화 작업을 설정
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;
}
}
주요 구성 요소 및 설명
- encode 메서드
- 기능: 사용자가 입력한 평문 비밀번호를 해시하여 암호화된 비밀번호를 생성한다.
- 구현:
- BCrypt.withDefaults()를 호출하여 BCrypt의 기본 설정으로 해시 작업을 수행한다.
- hashToString 메서드를 사용하여 비밀번호를 해시하며, BCrypt.MIN_COST를 사용하여 해시 비용을 설정한다. (비용이 낮으면 빠르지만 보안성이 낮을 수 있으며, 비용이 높으면 느리지만 보안성이 높아진다.)
- matches 메서드
- 기능: 입력된 평문 비밀번호와 저장된 해시된 비밀번호를 비교하여 비밀번호가 일치하는지 확인한다.
- 구현:
- BCrypt.verifyer().verify를 사용하여 평문 비밀번호와 저장된 해시된 비밀번호를 비교한다.
- verify 메서드는 비밀번호가 일치하면 verified 속성을 true로 설정하고, 그렇지 않으면 false로 설정한다.
주요 메서드 설명
- encode(String rawPassword)
- 매개변수: 평문 비밀번호 (rawPassword)
- 반환값: 해시된 비밀번호 (암호화된 문자열)
- 설명: 사용자가 입력한 비밀번호를 BCrypt 알고리즘으로 해시하여 암호화된 비밀번호를 반환한다.
- matches(String rawPassword, String encodedPassword)
- 매개변수:
- 평문 비밀번호 (rawPassword)
- 저장된 해시된 비밀번호 (encodedPassword)
- 반환값: 비밀번호 일치 여부 (boolean)
- 설명: 입력된 평문 비밀번호와 저장된 해시된 비밀번호를 비교하여 일치 여부를 반환한다.
- 매개변수:
동작 흐름
- 비밀번호 암호화:
- 사용자가 비밀번호를 등록하거나 변경할 때 encode 메서드를 호출하여 비밀번호를 암호화한다.
- 암호화된 비밀번호는 데이터베이스에 저장된다.
- 비밀번호 확인:
- 사용자가 로그인할 때 입력한 비밀번호를 matches 메서드를 통해 확인한다.
- 입력된 비밀번호와 데이터베이스에 저장된 해시된 비밀번호를 비교하여 일치 여부를 판단한다.
이 클래스는 비밀번호의 보안을 강화하고, 사용자 인증 과정에서 비밀번호의 안전성을 유지하는 데 중요한 역할을 한다. BCrypt 알고리즘을 사용하여 비밀번호를 해시하고 검증함으로써, 비밀번호 저장 및 비교 과정에서 보안성을 확보하는데 기여한다.
JwtUtil.class
JwtUtil 클래스는 JWT (JSON Web Token)를 생성, 검증, 저장 및 추출하는 기능을 제공하는 유틸리티 클래스이다. 다음은 이 클래스의 주요 기능과 각 메서드의 설명이다:
package com.sparta.scheduleserver.config.jwt;
import com.sparta.scheduleserver.model.entity.error.ErrorCode;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
@Component
public class JwtUtil {
// Header KEY 값
public static final String AUTHORIZATION_HEADER = "Authorization";
// Token 식별자
private static final String BEARER_PREFIX = "Bearer ";
// 토큰 만료시간
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
@Value("${jwt.secret.key}") // base64 Encode 한 SecretKey
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 로그 설정
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
// 토큰 생성
public String createToken(String email) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(email) // 사용자 식별값(ID)
.setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
.setIssuedAt(date) // 발급일
.signWith(key, signatureAlgorithm) // 암호화 알고리즘
.compact();
}
// JWT Cookie 저장
public void addJwtToCookie(String token, HttpServletResponse res) {
try {
token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20");
// Cookie Value 에는 공백이 불가능해서 encoding 진행
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
cookie.setPath("/");
// Response 객체에 Cookie 추가
res.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
logger.error(e.getMessage());
}
}
// JWT 토큰 substring
public String substringToken(String tokenValue) {
if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
return tokenValue.substring(7);
}
logger.error(ErrorCode.INVALID_TOKEN.getMessage());
throw new NullPointerException(ErrorCode.INVALID_TOKEN.getMessage());
}
// 토큰 검증
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException | SignatureException e) {
logger.error(ErrorCode.INVALID_TOKEN.getMessage());
} catch (ExpiredJwtException e) {
logger.error(ErrorCode.TOKEN_EXPIRED.getMessage());
} catch (UnsupportedJwtException e) {
logger.error(ErrorCode.TOKEN_CREATION_FAILED.getMessage());
} catch (IllegalArgumentException e) {
logger.error(ErrorCode.INVALID_TOKEN.getMessage());
}
return false;
}
// 토큰에서 사용자 정보 가져오기
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
// HttpServletRequest 에서 Cookie Value : JWT 가져오기
public String getTokenFromRequest(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(AUTHORIZATION_HEADER)) {
try {
return URLDecoder.decode(cookie.getValue(), "UTF-8"); // Encode 되어 넘어간 Value 다시 Decode
} catch (UnsupportedEncodingException e) {
return null;
}
}
}
}
return null;
}
}
주요 구성 요소 및 설명
- 상수 필드
- AUTHORIZATION_HEADER: JWT를 HTTP 요청의 헤더에서 찾을 때 사용하는 헤더 이름이다.
- BEARER_PREFIX: JWT의 값 앞에 붙는 접두사로, 일반적으로 "Bearer "이다.
- TOKEN_TIME: 토큰의 만료 시간으로, 60분(1시간)을 밀리초로 표현한 값이다.
- 비밀 키와 서명 알고리즘
- secretKey: JWT를 서명하는 데 사용하는 비밀 키로, base64로 인코딩된 문자열 형태로 application.properties 또는 application.yml에서 주입된다.
- key: 실제 JWT 서명을 위해 사용되는 Key 객체로, init() 메서드에서 초기화된다.
- signatureAlgorithm: JWT 서명에 사용되는 알고리즘으로, 여기서는 HMAC SHA-256이 사용된다.
- 로그 설정
- logger: SLF4J를 사용하여 로그를 기록하는 Logger 객체이다.
주요 메서드
- init()
- @PostConstruct 애노테이션을 통해 클래스가 초기화될 때 호출된다. secretKey를 base64로 디코딩하고, HMAC SHA-256 서명 알고리즘에 사용할 Key 객체를 생성한다.
- createToken(String email)
- 주어진 이메일을 포함한 JWT를 생성한다. JWT에는 사용자 식별값(이메일), 발급일, 만료일이 포함되며, 서명 알고리즘으로 서명된다.
- addJwtToCookie(String token, HttpServletResponse res)
- JWT를 URL 인코딩하여 쿠키에 저장한다. 쿠키의 이름은 AUTHORIZATION_HEADER이고, 쿠키의 경로는 루트("/")로 설정된다. 쿠키는 HTTP 응답에 추가된다.
- substringToken(String tokenValue)
- JWT 문자열에서 "Bearer " 접두사를 제거하고 실제 토큰 값을 추출한다. 접두사가 없거나 문자열이 비어 있는 경우 예외를 발생시킨다.
- validateToken(String token)
- 주어진 JWT가 유효한지 검증한다. 서명 검증, 만료 여부 등을 체크하며, 검증 실패 시 적절한 로그를 기록한다.
- getUserInfoFromToken(String token)
- JWT에서 클레임을 추출하여 반환한다. 이 클레임에는 사용자 정보가 포함되어 있다.
- getTokenFromRequest(HttpServletRequest req)
- HTTP 요청의 쿠키에서 JWT를 추출한다. 쿠키의 이름이 AUTHORIZATION_HEADER인 것을 찾고, 쿠키 값을 URL 디코딩하여 반환한다.
간단히 말하자면...
이 클래스는 JWT를 효과적으로 다루기 위해 필요한 메서드들을 포함하고 있으며, JWT를 생성하고, 검증하고, 쿠키에 저장하거나 요청에서 추출하는 기능을 지원한다.
AuthFilter.class
AuthFilter 클래스는 HTTP 요청에 대해 인증을 수행하는 서블릿 필터이다. 이 필터는 주로 JWT 토큰을 사용하여 요청의 유효성을 검증하고, 인증된 사용자의 정보를 요청에 설정한다. 다음은 이 클래스의 주요 구성 요소와 기능 설명이다:
package com.sparta.scheduleserver.config.filter;
import com.sparta.scheduleserver.model.entity.User;
import com.sparta.scheduleserver.service.jwt.JwtUtil;
import com.sparta.scheduleserver.model.entity.repository.UserRepository;
import io.jsonwebtoken.Claims;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
@Slf4j(topic = "AuthFilter")
@Component
@Order(2)
public class AuthFilter implements Filter {
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
public AuthFilter(UserRepository userRepository, JwtUtil jwtUtil) {
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI();
if (StringUtils.hasText(url) &&
(url.startsWith("/api/user") )
) {
log.info("인증 처리를 하지 않는 URL : " + url);
// 회원가입, 로그인 관련 API 는 인증 필요없이 요청 진행
chain.doFilter(request, response); // 다음 Filter 로 이동
} else {
// 나머지 API 요청은 인증 처리 진행
// 토큰 확인
String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest);
if (StringUtils.hasText(tokenValue)) { // 토큰이 존재하면 검증 시작
// JWT 토큰 substring
String token = jwtUtil.substringToken(tokenValue);
// 토큰 검증
if (!jwtUtil.validateToken(token)) {
throw new IllegalArgumentException("Token Error");
}
// 토큰에서 사용자 정보 가져오기
Claims info = jwtUtil.getUserInfoFromToken(token);
User user = userRepository.findByUsername(info.getSubject()).orElseThrow(() ->
new NullPointerException("Not Found User")
);
request.setAttribute("user", user);
chain.doFilter(request, response); // 다음 Filter 로 이동
} else {
throw new IllegalArgumentException("Not Found Token");
}
}
}
}
주요 구성 요소 및 설명
- 의존성 주입
- UserRepository: 사용자 정보를 데이터베이스에서 조회하기 위한 레포지토리.
- JwtUtil: JWT 토큰을 생성, 검증, 추출하는 데 사용되는 유틸리티 클래스.
- 필터 설정
- @Component: 스프링 컴포넌트로 등록되어, 필터 체인에 포함된다.
- @Order(2): 필터의 실행 순서를 정의한다. 숫자가 낮을수록 우선 순위가 높다.
- doFilter 메서드
- URL 확인: 요청 URL이 "/api/user"로 시작하면, 해당 URL에 대해 인증을 수행하지 않고 요청을 다음 필터로 전달한다. 일반적으로 회원가입, 로그인 등 인증이 필요 없는 API에 사용된다.
- 인증 처리: 인증이 필요한 요청에 대해 JWT 토큰을 추출하고, 검증 후 사용자 정보를 데이터베이스에서 조회한다. 유효한 사용자가 확인되면 요청 속성에 사용자 정보를 설정하고 다음 필터로 전달한다.
주요 메서드 설명
- doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- URL 검사: 요청 URL이 "/api/user"로 시작하면 인증 처리를 건너뛰고, 필터 체인의 다음 필터로 요청을 전달한다.
- 토큰 검증:
- 요청에서 JWT 토큰을 추출한다.
- 추출한 토큰의 유효성을 검증한다. 유효하지 않으면 예외를 발생시킨다.
- 유효한 토큰일 경우, 토큰에서 사용자 정보를 추출하고, 해당 사용자를 데이터베이스에서 조회한다.
- 조회된 사용자 정보를 요청 속성에 설정하고, 다음 필터로 요청을 전달한다.
- 예외 처리: 토큰이 없거나 유효하지 않거나 사용자를 찾을 수 없는 경우, 적절한 예외를 발생시킨다.
동작 흐름
- 회원가입 또는 로그인 API: 인증이 필요 없는 API 요청은 필터를 통과하여 바로 처리된다.
- 기타 API:
- JWT 토큰을 추출하고, 검증한다.
- 검증이 성공하면 토큰에서 사용자 정보를 추출하여 데이터베이스에서 조회한다.
- 사용자 정보가 요청 속성에 설정되며, 요청은 다음 필터로 전달된다.
- 오류가 발생할 경우, 예외를 발생시켜 인증 오류를 처리한다.
이 필터는 보안 관련 요구사항을 처리하며, 인증되지 않은 요청을 차단하고, 인증된 요청에 사용자 정보를 설정하여 애플리케이션의 보안을 강화하는 역할을 한다.
'Spring > 미니 프로젝트(뉴스피드)' 카테고리의 다른 글
KPT 회고록+뉴스피드 프로젝트 정리본 (4) | 2024.09.06 |
---|---|
SNS 팀 프로젝트(1) (4) | 2024.09.02 |
일정 관리 시스템 flowchart (0) | 2024.08.16 |
일정 관리 시스템 (0) | 2024.08.16 |