단계
<필수>
Lv1 - 일정 CRUD(생성,조회,수정,삭제/[필드 - 작성 유저명, 일정 제목, 할 일, 작성일, 수정일])
Lv2 - 유저 CRUD(유저명, 이메일, 작성일 수정일/일정에서 작성유저명을 유저 고유식별자로 변경)
Lv3 - 유저에 비밀번호 추가
Lv4 - Cookie/Session을 통해 로그인 기능 구현(이메일과 비밀번호 사용)
<도전>
Lv5 - 다양한 예외처리 적용
Lv6 - 비밀번호 암호화
Lv7 - 댓글 CRUD
Lv8 - 일정 페이징 조회
-- 구현 단계 Lv 4
구현중 문제점
일정/유저/회원가입/로그인 기능들을 만들고 연결하는 과정에서 지속적인 로그인 실패
(아직 해결단계이며 예측상 일정을 생성할 때 세션이 전달되지않아 발생하는것으로 보임)
환경변수
spring.application.name=scheduleDevelop
spring.datasource.url=jdbc:mysql://localhost:3306/Develop
spring.datasource.username=[소스 아이디]
spring.datasource.password=[소스 비밀번호]
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create // 어플리케이션 실행시 기존 데이터베이스 삭제후 재생성
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
Api 명세서

ERD

BaseEntity
package com.example.scheduledevelop.entity;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
// 일정, 유저등 생성하거나 수정할때 시간을 저장하기에 따로 만들어 extends 시킨다
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
Lv1 일정 CRUD작성
Entity
package com.example.scheduledevelop.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Getter
@NoArgsConstructor
public class Schedule extends BaseEntity{
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
private String title;
private String todo;
public Schedule(User user, String title, String todo) {
this.user = user;
this.title = title;
this.todo = todo;
}
public void update(String title, String todo){
this.title = title;
this.todo = todo;
}
}
Controller
package com.example.scheduledevelop.controller;
import com.example.scheduledevelop.dto.ScheduleRequestDto;
import com.example.scheduledevelop.dto.ScheduleResponseDto;
import com.example.scheduledevelop.service.ScheduleService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class ScheduleController {
private final ScheduleService scheduleService;
@PostMapping("/schedules")
public ResponseEntity<ScheduleResponseDto> save(@RequestBody ScheduleRequestDto dto){
return new ResponseEntity<>(scheduleService.save(dto), HttpStatus.CREATED);
}
@GetMapping("/schedules")
public ResponseEntity<List<ScheduleResponseDto>> findAll(){
return new ResponseEntity<>(scheduleService.findAll(), HttpStatus.OK);
}
@GetMapping("/schedules/{id}")
public ResponseEntity<ScheduleResponseDto> findOne(@PathVariable Long id){
return new ResponseEntity<>(scheduleService.findById(id), HttpStatus.OK);
}
@PutMapping("/schedules/{id}")
public ResponseEntity<ScheduleResponseDto> update(@PathVariable Long id, @RequestBody ScheduleRequestDto dto){
return new ResponseEntity<>(scheduleService.update(id, dto), HttpStatus.OK);
}
@DeleteMapping("/schedules/{id}")
public ResponseEntity<Void> deleteSchedule(@PathVariable Long id){
scheduleService.deleteById(id);
return ResponseEntity.ok().build(); // 빈 본문을 반환하기 위해 build사용
}
Service
package com.example.scheduledevelop.service;
import com.example.scheduledevelop.dto.ScheduleRequestDto;
import com.example.scheduledevelop.dto.ScheduleResponseDto;
import com.example.scheduledevelop.dto.UserResponseDto;
import com.example.scheduledevelop.entity.Schedule;
import com.example.scheduledevelop.entity.User;
import com.example.scheduledevelop.repository.ScheduleRepository;
import com.example.scheduledevelop.repository.UserRepository;
import lombok.RequiredArgsConstructor;
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;
@Transactional
public ScheduleResponseDto save(ScheduleRequestDto dto){
User user = userRepository.findById(dto.getUserId()).orElseThrow(()-> new IllegalArgumentException("유저를 찾을 수 없습니다"));
Schedule schedule = new Schedule(user, dto.getTitle(), dto.getTodo());
Schedule saveSchedule = scheduleRepository.save(schedule);
return new ScheduleResponseDto(saveSchedule.getId(), user.getName(), saveSchedule.getTitle(), saveSchedule.getTodo());
}
@Transactional(readOnly = true)
public List<ScheduleResponseDto> findAll(){
List<Schedule> schedules = scheduleRepository.findAll();
List<ScheduleResponseDto> dtos = new ArrayList<>();
for(Schedule schedule : schedules){
ScheduleResponseDto dto = new ScheduleResponseDto(schedule.getId(), schedule.getTitle(), schedule.getTodo());
UserResponseDto userDto = new UserResponseDto(schedule.getUser().getId(), schedule.getUser().getName());
dtos.add(dto);
}
// 빠른 for문 생성 schedules.for + tab키
return dtos;
}
@Transactional(readOnly = true)
public ScheduleResponseDto findById(Long id){
Schedule schedule = scheduleRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("작성된 일정이 없습니다!")
);
return new ScheduleResponseDto(schedule.getId(), schedule.getUser().getName(), schedule.getTitle(), schedule.getTodo());
}
@Transactional
public ScheduleResponseDto update(Long id, ScheduleRequestDto dto){
Schedule schedule = scheduleRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("작성된 일정이 없습니다!")
);
schedule.update(dto.getTitle(),dto.getTodo());
return new ScheduleResponseDto(schedule.getId(), schedule.getUser().getName(), schedule.getTitle(), schedule.getTodo());
}
@Transactional
public void deleteById(Long id){
if(!scheduleRepository.existsById(id)){
throw new IllegalArgumentException("작성된 일정이 없습니다!");
}
scheduleRepository.deleteById(id);
}
}
Repository
package com.example.scheduledevelop.repository;
import com.example.scheduledevelop.entity.Schedule;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
public interface ScheduleRepository extends JpaRepository<Schedule, Long> {
List<Schedule> findByUserId(Long userId); // ManyToOne으로 연결한 유저의 아이디를 가져오기 위해 작성
}
Lv2 유저 CRUD
package com.example.scheduledevelop.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@NoArgsConstructor
public class User extends BaseEntity{
@Id@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String name;
private String password;
public User(String email, String name, String password) {
this.email = email;
this.name = name;
this.password = password;
}
public void update(Long id, String email, String name, String password){
this.id = id;
this.email = email;
this.name = name;
this.password = password;
}
}
Controller
package com.example.scheduledevelop.controller;
import com.example.scheduledevelop.dto.SignUpRequestDto;
import com.example.scheduledevelop.dto.UserRequestDto;
import com.example.scheduledevelop.dto.UserResponseDto;
import com.example.scheduledevelop.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/users/signup")
public ResponseEntity<UserResponseDto> signup(@RequestBody SignUpRequestDto signUpRequestDto){
UserResponseDto userResponseDto = userService.signUp(signUpRequestDto);
return new ResponseEntity<>(userResponseDto, HttpStatus.CREATED);
}
@GetMapping("/users")
public ResponseEntity<List<UserResponseDto>> findAllUser(){
return new ResponseEntity<>(userService.findAllUser(), HttpStatus.OK);
}
@GetMapping("/users/{userId}")
public ResponseEntity<UserResponseDto> findUser(@PathVariable Long userId){
return new ResponseEntity<>(userService.findUserById(userId), HttpStatus.OK);
}
@PutMapping("/users/{userId}")
public ResponseEntity<UserResponseDto> updateUserData(@PathVariable Long userId, @RequestBody UserRequestDto dto){
return new ResponseEntity<>(userService.updateData(userId, dto), HttpStatus.OK);
}
@DeleteMapping("/users/{userId}")
public ResponseEntity<Void> deleteUserData(@PathVariable Long userId){
userService.deleteUserData(userId);
return new ResponseEntity<>(HttpStatus.OK);
}
}
Service
package com.example.scheduledevelop.service;
import com.example.scheduledevelop.dto.*;
import com.example.scheduledevelop.entity.User;
import com.example.scheduledevelop.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional
public UserResponseDto signUp(SignUpRequestDto dto){
User user = new User(dto.getEmail(), dto.getName(), dto.getPassword());
User saveUser = userRepository.save(user);
return new UserResponseDto(saveUser.getId(), saveUser.getEmail(), saveUser.getName());
}
@Transactional
public LoginResponseDto login(LoginRequestDto dto){
User user = userRepository.findByEmail(dto.getEmail()).orElseThrow(()-> new ResponseStatusException(HttpStatus.NOT_FOUND,"해당 이메일의 유저가 존재하지 않습니다"));
if(!user.getPassword().equals(dto.getPassword())){
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다.");
}
return new LoginResponseDto(user.getId(),user.getName(), user.getEmail(), user.getPassword());
}
@Transactional(readOnly = true)
public List<UserResponseDto> findAllUser(){
List<User> users = userRepository.findAll();
List<UserResponseDto> dtos = new ArrayList<>();
for (User user : users) {
dtos.add(new UserResponseDto(user.getId(), user.getEmail(), user.getName()));
}
return dtos;
}
@Transactional(readOnly = true)
public UserResponseDto findUserById(Long userId){
User user = userRepository.findById(userId).orElseThrow(
() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다")
);
return new UserResponseDto(user.getId(), user.getEmail(), user.getName());
}
@Transactional
public UserResponseDto updateData(Long userId, UserRequestDto dto){
User user = userRepository.findById(userId).orElseThrow(
() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다")
);
user.update(dto.getUserId(), dto.getEmail(), dto.getName(), dto.getPassword());
return new UserResponseDto(user.getId(), user.getEmail(), user.getName());
}
@Transactional
public void deleteUserData(Long userId){
if(!userRepository.existsById(userId)){
throw new IllegalArgumentException("해당 유저가 존재하지 않습니다");
}
userRepository.deleteById(userId);
}
}
Repository
package com.example.scheduledevelop.repository;
import com.example.scheduledevelop.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
Lv3 회원가입
Controller
@PostMapping("/users/signup")
public ResponseEntity<UserResponseDto> signup(@RequestBody SignUpRequestDto signUpRequestDto){
UserResponseDto userResponseDto = userService.signUp(signUpRequestDto);
return new ResponseEntity<>(userResponseDto, HttpStatus.CREATED);
}
- 유저 컨트롤러에서 유저서비스에 메소드를 만들어 작동
Service
@Transactional
public UserResponseDto signUp(SignUpRequestDto dto){
User user = new User(dto.getEmail(), dto.getName(), dto.getPassword());
User saveUser = userRepository.save(user);
return new UserResponseDto(saveUser.getId(), saveUser.getEmail(), saveUser.getName());
}
Dto
package com.example.scheduledevelop.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class SignUpRequestDto {
@NotBlank(message = "이름을 입력해주세요")
private String name;
@NotBlank(message = "이메일을 입력해주세요")
private String email;
@NotBlank(message = "비밀번호를 입력해주세요")
private String password;
public SignUpRequestDto(String email, String password) {
this.email = email;
this.password = password;
}
}
- dto로 요청 받은 데이터를 유저레포지토리를 통해 유저에 저장해 유저 리스폰스를 통해 출력
Lv4 로그인 인증
로그인 인증을 위해 필러를 등록
package com.example.scheduledevelop.config;
import com.example.scheduledevelop.filter.LoginFilter;
import jakarta.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean loginFilter() {
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new LoginFilter());
filterFilterRegistrationBean.setOrder(1);
filterFilterRegistrationBean.addUrlPatterns("/*");
return filterFilterRegistrationBean;
}
}
Filter
private static final String[] WHITE_LIST = {"/", "/users/signup", "/login", "/logout"};
해당 URL은 로그인 검증을 하지 않음
//화이트 리스트에 포함된 경우 true > !ture > false
if (!isWhiteList(requestURI)) {
HttpSession session = httpRequest.getSession(false);
if (session == null || session.getAttribute("userId") == null){
throw new RuntimeException("로그인 해주세요");
}
log.info("로그인에 성공하였습니다");
}
// 1번 경우 : 화이트 리스트에 등록된 유알엘이면 바로 체인두필터가 호출
// 2번 경우 : 화이트 리스트가 아닌 경우 위의 필터 로직을 통과후 체인두필터로 다음 필터나 서블릿을 호출
// 다음 필터가 없으면 서블릿이나 컨드롤러, 다음 필터가 있으면 다음 필터를 호출
filterChain.doFilter(servletRequest, servletResponse);
}
private boolean isWhiteList(String requestURI) {
return PatternMatchUtils.simpleMatch(WHITE_LIST, requestURI);
}
-화이트 리스트 검증 로직
Controller
package com.example.scheduledevelop.controller;
import com.example.scheduledevelop.dto.LoginRequestDto;
import com.example.scheduledevelop.dto.LoginResponseDto;
import com.example.scheduledevelop.dto.SignUpRequestDto;
import com.example.scheduledevelop.dto.UserResponseDto;
import com.example.scheduledevelop.entity.User;
import com.example.scheduledevelop.service.UserService;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequiredArgsConstructor
public class LoginController {
private final UserService userService;
@PostMapping("/login")
public ResponseEntity<LoginResponseDto> login(@RequestBody LoginRequestDto dto, HttpServletRequest request){
LoginResponseDto loginResponseDto = userService.login(dto);
HttpSession session = request.getSession(false); // 없을시 null 반환(실패한 적이 있다면 세션이 있을수도)
if (session == null) {
session = request.getSession(true); // 없을경우 있게 만들어줌
}
if (loginResponseDto != null) {
log.info("로그인 성공 유저(userId, name, email) = {}, {}, {}",
loginResponseDto.getUserId(),
loginResponseDto.getName(),
loginResponseDto.getEmail());
session.setAttribute("userId", loginResponseDto.getUserId());
session.setMaxInactiveInterval(1800); //30분
}
return new ResponseEntity<>(loginResponseDto, HttpStatus.OK);
}
@PutMapping("/logout")
private ResponseEntity<Void> logout(HttpServletRequest request){
HttpSession session = request.getSession(false);
if (session != null){
session.invalidate();
}
return new ResponseEntity<>(HttpStatus.OK);
}
}
Service
@Transactional
public LoginResponseDto login(LoginRequestDto dto){
User user = userRepository.findByEmail(dto.getEmail()).orElseThrow(()-> new ResponseStatusException(HttpStatus.NOT_FOUND,"해당 이메일의 유저가 존재하지 않습니다"));
if(!user.getPassword().equals(dto.getPassword())){
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다.");
}
return new LoginResponseDto(user.getId(),user.getName(), user.getEmail(), user.getPassword());
}
- 유저 서비스에 로그인 로직 작성
[Postman 실행]



- 회원 가입
- 로그인 성공
- 로그인 실패(401에러 발생)

-유저 조회

-전제 유저 조회

- 유저 정보 수정
(작성 안한부분은 원래대로 업데이트)

- 회원 삭제시 이메일과 비밀번호 입력

- 일정 생성

- 전체 일정 조회

- 일정 단일 조회
(유저아이디로)

- 일정 수정
(제목이나 내용이 전달이 안되면 기존 데이터로 업데이트)

- 일정 삭제
<느낀점>
전반적으로 큰 오류나 힘든 점은 없었으나 세션을 주고 받는것을 이해하는데 오래걸렸다.
'내일배움캠프 > TIL' 카테고리의 다른 글
| [Spring]_JWT Refresh token (0) | 2025.03.07 |
|---|---|
| [Spring]_뉴스피드 팀프로젝트 트러블 슈팅 (0) | 2025.02.20 |
| [Spring]_일정관리 트러블슈팅 (0) | 2025.02.04 |
| [Spring]_MVC (0) | 2025.01.23 |
| [Spring]_ Framework (0) | 2025.01.23 |