ABOUT ME

주니어 개발자

Today
Yesterday
Total
  • [과제] 일정 관리 앱 만들기 필수 Lv1
    내일배움캠프/과제 2025. 3. 25. 12:41

    ✅ Lv1. 일정 생성 및 조회

    1️⃣ 과제 조건

    • 일정 생성(일정 작성하기)
      • 일정 생성 시, 포함되어야할 데이터
        • 할일, 작성자명, 비밀번호, 작성/수정일을 저장
        • 작성/수정일은 날짜와 시간을 모두 포함한 형태
      • 각 일정의 고유 식별자(ID)를 자동으로 생성하여 관리
      • 최초 입력 시, 수정일은 작성일과 동일
    • 전체 일정 조회(등록된 일정 불러오기)
      • 다음 조건을 바탕으로 등록된 일정 목록을 전부 조회
        • 수정일 (형식 : YYYY-MM-DD)
        • 작성자명
      • 조건 중 한 가지만을 충족하거나, 둘 다 충족을 하지 않을 수도, 두 가지를 모두 충족할 수도 있습니다.
      • 수정일 기준 내림차순으로 정렬하여 조회
    • 선택 일정 조회(선택한 일정 정보 불러오기)
      • 선택한 일정 단건의 정보를 조회할 수 있습니다.
      • 일정의 고유 식별자(ID)를 사용하여 조회합니다.

     

    2️⃣ 일정 생성

    1. 일정을 저장할 Schedule Entity 작성

    
      
    public class Schedule {
    private Long id; // PK
    private String schedule; // 일정 내용
    private String regNm; // 작성자 명
    private String password; // 비밀번호
    private LocalDateTime regDt; // 작성일
    private LocalDateTime modDt; // 수정일
    }

     

    2. 일정 저장을 위한 데이터 요청 Dto 작성

    
      
    public class ScheduleReqDto {
    @Schema(description = "일정 내용")
    private String schedule;
    @Schema(description = "작성자 명")
    private String regNm;
    @Schema(description = "비밀번호")
    private String password;
    }

     

    3. 일정 반환을 위한 응답 Dto 작성

    
      
    public class ScheduleResDto {
    @Schema(description = "PK")
    private Long id;
    @Schema(description = "일정 내용")
    private String schedule;
    @Schema(description = "작성자 명")
    private String regNm;
    @Schema(description = "등록일")
    private String regDt;
    @Schema(description = "수정일")
    private String modDt;
    }

     

    4. Controller @RequestBody 를 활용하여 json 형식으로 data 받음

     

    
      
    @PostMapping
    public BaseResponse<ScheduleResDto> saveSchedule(@RequestBody ScheduleReqDto dto) {
    return BaseResponse.from(scheduleService.saveSchedule(dto));
    }

     

    5. Service 에서는 저장 시간을 LocalDatime.now()를 사용하였다.

    
      
    @Override
    public ScheduleResDto saveSchedule(ScheduleReqDto dto) {
    LocalDateTime now = LocalDateTime.now(); // 현재 시각
    Schedule schedule = new Schedule(dto.getSchedule(), dto.getRegNm(), dto.getPassword(), now, now);
    return scheduleRepository.saveSchedule(schedule);
    }

     

    6. Repository에서는 SimpleJdbcinsert 를 사용하여 각 파라미터에 데이터를 넣어 insert 해줌

    
      
    public ScheduleResDto saveSchedule(Schedule schedule) {
    SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
    jdbcInsert.withTableName("schedule").usingGeneratedKeyColumns("id");
    Map<String, Object> parameters = new HashMap<>();
    parameters.put("schedule", schedule.getSchedule());
    parameters.put("regNm", schedule.getRegNm());
    parameters.put("password", schedule.getPassword());
    parameters.put("regDt", schedule.getRegDt());
    parameters.put("modDt", schedule.getModDt());
    Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters)); // PK Return
    schedule.setId(key.longValue());
    return new ScheduleResDto(schedule);
    }

     

     

    3️⃣ 전체 일정 조회

    1. @RequestParam 사용하여 수정일, 작성자 정보 입력 받기

    
      
    @GetMapping
    public BaseResponse<List<ScheduleResDto>> findAllSchedule(
    @RequestParam(required = false) String modDt
    , @RequestParam(required = false) String regNm) {
    return BaseResponse.from(scheduleService.findAllSchedule(modDt, regNm));
    }

     

    조건은 필수가 아니기에 required = false 처리를 해주었다.

     

    2. service 에서 repository 호출

    
      
    public List<ScheduleResDto> findAllSchedule(String modDt, String regNm) {
    return scheduleRepository.findAllSchedule(modDt, regNm);
    }

     

    3. Repository

    
      
    public List<ScheduleResDto> findAllSchedule(String modDt, String regNm) {
    List<Object> params = new ArrayList<>();
    StringBuilder query = new StringBuilder()
    .append("SELECT id, schedule, reg_nm, password, reg_dt, mod_dt FROM schedule \n")
    .append(" WHERE 1 = 1 \n");
    if (StringUtils.isNotBlank(modDt)) { // 수정일 검색조건이 있을 경우
    query.append(" AND DATE(mod_dt) = ? \n");
    params.add(modDt);
    }
    if (StringUtils.isNotBlank(regNm)) { // 작성자명 검색조건이 있을 경우
    query.append(" AND reg_nm = ? \n");
    params.add(regNm);
    }
    query.append(" ORDER BY mod_dt DESC");
    List<Schedule> resultList = jdbcTemplate.query(query.toString(), params.toArray(), this.scheduleRowMapper());
    return resultList.stream()
    .map(ScheduleResDto::new)
    .collect(Collectors.toList());
    }

     

    modDt 나 regNm의 검색 조건이 있을 경우 where 절을 추가해주었다.

    
      
    private RowMapper<Schedule> scheduleRowMapper() {
    return (rs, rowNum) -> new Schedule(
    rs.getLong("id"),
    rs.getString("schedule"),
    rs.getString("reg_nm"),
    rs.getString("password"),
    rs.getTimestamp("reg_dt").toLocalDateTime(),
    rs.getTimestamp("mod_dt").toLocalDateTime()
    );
    }

     

    rowMapper 에서 가져올 데이터를 지정해주었다.

     

    그 후 비밀번호를 제외한 형식으로 ResDto를 생성하여 List 목록으로 return

    
      
    public ScheduleResDto(Schedule schedule) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
    this.id = schedule.getId();
    this.schedule = schedule.getSchedule();
    this.regNm = schedule.getRegNm();
    this.regDt = formatter.format(schedule.getRegDt());
    this.modDt = formatter.format(schedule.getModDt());
    }

     

    4️⃣ 선택 일정 조회

    1. Controller @PathVariable 을 사용하여 일정 id값을 받음

    
      
    @GetMapping("/{id}")
    public BaseResponse<ScheduleResDto> findScheduleById(@PathVariable Long id) {
    return BaseResponse.from(scheduleService.findScheduleById(id));
    }

     

    2. Service

    
      
    public ScheduleResDto findScheduleById(Long id) {
    return scheduleRepository.findScheduleById(id);
    }

     

    3. Repository

    
      
    public ScheduleResDto findScheduleById(Long id) {
    StringBuilder query = new StringBuilder()
    .append("SELECT id, schedule, reg_nm, password, reg_dt, mod_dt FROM schedule \n")
    .append(" WHERE id = ? \n");
    List<Schedule> resultList = jdbcTemplate.query(query.toString(), this.scheduleRowMapper(), id);
    return resultList.stream().findAny()
    .map(ScheduleResDto::new)
    .orElseThrow(() -> new CustomException(CommonExceptionResultMessage.NOT_FOUND, "일정 조회 실패: ID " + id + "에 해당하는 일정 없음"));
    }

     

    pk로 조회. 만약 데이터 조회가 안될 경우 CustomException을 throw

     

     

    5️⃣트러블 슈팅

    그런데 데이터베이스를 열어보면 사용자의 비밀번호가 암호화 되지 않고 보안에 취약한 것을 보았다.

    보안에 취약하지 않게 비밀번호를 암호화 하는 방식을 선택했다.

    
      
    @Override
    public ScheduleResDto saveSchedule(ScheduleReqDto dto) {
    LocalDateTime now = LocalDateTime.now(); // 현재 시각
    String encodePw = passwordEncoder.encode(dto.getPassword()); // 비밀번호 암호화
    Schedule schedule = new Schedule(dto.getSchedule(), dto.getRegNm(), encodePw, now, now);
    return scheduleRepository.saveSchedule(schedule);
    }

     

    PasswordEncoder 를 사용하여  bcrypt로 비밀번호를 암호화 하여 데이터를 저장하였다.

     

    또 데이터를 입력받을 때 필수데이터를 따로 지정하고 싶었다.

     

    
      
    @PostMapping
    public BaseResponse<ScheduleResDto> saveSchedule(@RequestBody @Valid ScheduleReqDto dto) {
    return BaseResponse.from(scheduleService.saveSchedule(dto));
    }

     

    찾아보았더니 @Valid 를 사용하면 api 데이터를 받을 때 검증을 진행할 수 있었다.

    
      
    public class ScheduleReqDto {
    @Schema(description = "일정 내용")
    @NotBlank
    private String schedule;
    @Schema(description = "작성자 명")
    @NotBlank
    private String regNm;
    @Schema(description = "비밀번호")
    @NotBlank
    private String password;
    }

     

    각 필수 데이터 별로 @NotBlank 어노테이션을 사용하여 데이터를 필수로 받도록 검증하였다.

Designed by Tistory.