프로그래밍/Project

[Java] ToyProject 1단계

코딩중독 2023. 12. 29. 12:16

패스트캠퍼스 : 백엔드 개발 부트캠프

ToyProject 1

  • 1단계, 2단계, 3단계로 진행
  • 최종 구현 목표는 '여행과 여정을 기록/관리하는 SNS 서비스'

1단계

  • 달성목표 : Java 문법, 파일 입출력, 예외처리, 클래스 설계

[프로젝트 정의]

  1. 회원은 여러 여행 기록을 저장할 수 있습니다.
    회원 가입은 다음 단계에서 고려하고 본 프로젝트는 여행과 여정을 기록하고 조회하는 기능을 구현하기로 한다.
  2. 여행에는 여러 여정이 있을 수 있습니다.
    여정정보는 이동정보(출발지, 목적지, 출발시간, 도착시간), 숙박정보(체크인, 체크아웃) 등의 종류를 포함할 수 있다.
  3. 회원의 여행기록 목록이 출력되며, 각 여행기록 목록을 선택하면 상세한 여정기록이 출력됩니다.
  4. 회원 및 여행기록은 향후 데이터베이스 테이블로 관리되지만 1단계 프로젝트에서는 여행별 여정 정보는 json 파일로 관리됩니다.
  5. 여행정보 구조는 다음과 같습니다.
필드 필드ID 유형 설명
여행_id trip_id 정수 여행의 고유 식별자입니다.
여행_이름 trip_name 문자열 여행의 이름 또는 제목
시작_날짜 start_date 문자열(날짜) 여행 시작일
종료_날짜 end_date 문자열(날짜) 여행 종료일
여정 itineraries 여정 배열 여정 개체를 포함하는 배열입니다.
  1. 여정정보 구조는 다음과 같습니다.
필드 필드ID 유형 설명
여정_id itinerary_id 정수 각 여정 항목의 고유 식별자입니다.
출발지 departure_place 문자열 출발 장소
도착지 destination 문자열(날짜) 도착 장소
출발_시간 departure_time 문자열(날짜) 여행 출발 시간
도착_시간 itineraries 여정 배열 여행 도착 시간
체크인 check_in 문자열(날짜 시간) 체크인 시간
체크아웃 check_out 문자열(날짜 시간) 체크아웃 시간
  1. JSON 파일 이름 규칙은 다음과 같습니다.
    _.json

[프로젝트 요구사항]

  1. 요구 사항
  • 개별 여행은 여러 여정 정보로 구성됩니다. (여행 : 여정 = 1 : n)
  • 여정 정보에는 이동 유형(출발지, 도착지, 출발 시간, 도착 시간) 및 숙박(체크인, 체크아웃)이 포함될 수 있습니다.
  • 여행 정보 및 특정 여행 정보의 여정 목록은 데이터 저장 경로에 JSON 파일 형태로 저장됩니다.
  • 객체지향 성격이 잘 드러날 수 있도록 클래스를 설계합니다.
  • 프로젝트 기능구현을 위해 필요한 메소드를 정의하고 구현해봅니다.
  1. 세부기능 구현
    • 여행일정을 기록해야 한다.
    • 하나의 여행에 여러 개의 여정 정보를 기록해야 한다.
    • 여정 정보를 N개 입력 후 계속 기록 여부를 묻고(Y/N) 기록을 종료 할 수 있다.
    2) 여행 및 여정 조회 하는 기능
    • 여행 전체 리스트를 조회 할 수 있어야 한다.
    • 여행 전체 리스트에서 확인된 여행 아이디를 입력하면 해당 여행 정보와 여정 정보를 조회 할 수 있어야 한다.
    • 여행 정보와 여정 정보 조회는 JSON 파일에서 조회가 가능해야 한다.
    3) 예외처리
    • 여행 정보가 없으면 오류 메시지를 출력한다.
    • 여행에 여정이 없으면 추가할 수 있어야 한다.
    • JSON 형식에 맞지 않으면 오류 메시지를 출력한다.
    • 한글 문자가 깨지지 않도록 해야 한다.
  2. 1) 여행 및 여정 정보 기록 기능

[개발환경]

  • Language : Java
  • Build system : IntelliJ
  • JDK : Amazon Corretto version 11
  • Lib : gson-2.8.9

[필요기능 정리]

FileUtil.java

  • public static void saveFile(TripEntity tripEntity) : 여행 정보 JSON 파일 저장
  • public static TripDto getTripFromFileById(int id) : 여행 ID로 JSON 파일 읽기
  • public static TripDto getTripFromFileByFilename(String filename) : 파일이름으로 JSON 파일 읽기
  • public static boolean isFileExist(String filename) : 파일이 있는지 체크
  • public static int getCountTripRecord() : 저장된 여행의 개수

FormatUtil.java

  • private static String checkLocalDateTimeFormat(String date) : 문자열을 LocalDateTime 으로 변환하기 전 체크(LocalDateTime 에서는 시간값이
    필수)
  • public static LocalDateTime stringToLocalDateTime(String date) : 문자열을 LocalDateTime 으로 변환
  • public static String localDateTimeToString(LocalDateTime localDateTime, boolean isLong) : LocalDateTime 을 문자열로 변환
  • public static boolean isValid(String input) : 날짜타입 입력 상태 체크

TripConverter.java, ItineraryConverter.java

  • Entity <-> Dto 변환 클래스
  • Entity 날짜타입은 LocalDateTime(JSON 파일 저장), Dto 날짜타입은 String(사용자 화면 출력)

[여행/여정 정보 파일 구조]

  • travles/
    • 여행_1.json
    • 여행_2.json
    • 여행_3.json
    • ...

[문제발생과 해결]

1) Entity -> JSON 파일에 저장
(Entity 변수명이 JSON 키 값으로 저장, 자바 네이밍 규칙(camelCase)을 지키면서 JSON 키는 제시된(snake_case)로 표기)

  • 예상했던 결과
    {
    "trip_id": 1,
    "trip_name": "오사카",
    "start_date": "2024-01-13 00:00:00",
    "end_date": "2024-01-15 00:00:00",
    "itineraries": []
    }
  • 실제 결과
    {
    "tripId": 1,
    "tripName": "오사카",
    "startDate": "2024-01-13 00:00:00",
    "endDate": "2024-01-15 00:00:00",
    "itineraryEntityList": []
    }
  • 해결 방법
    • Gson 라이브러리의 어노테이션 사용하여 직렬화/역직렬화 이름 지정
      @SerializedName("trip_id")
      @Expose
      private int tripId;
      

@SerializedName("trip_name")
@Expose
private String tripName;

@SerializedName("start_date")
@Expose
private LocalDateTime startDate;

@SerializedName("end_date")
@Expose
private LocalDateTime endDate;

@SerializedName("itineraries")
@Expose
private List itineraryEntityList;


2) Entity -> JSON 파일에 저장<br>(LocalDateTime date(year,month,day), time(hour,minute,second,nano) 형태로 저장됨💦)
* 예상했던 결과

````json
{
  "trip_id": 1,
  "trip_name": "오사카",
  "start_date": "2024-01-13 00:00:00",
  "end_date": "2024-01-15 00:00:00",
  "itineraries": []
}
  • 실제 결과
{
  "trip_id": 1,
  "trip_name": "오사카",
  "start_date": {
    "date": {
      "year": 2024,
      "month": 1,
      "day": 13
    },
    "time": {
      "hour": 0,
      "minute": 0,
      "second": 0,
      "nano": 0
    }
  },
  "end_date": {
    "date": {
      "year": 2024,
      "month": 1,
      "day": 15
    },
    "time": {
      "hour": 0,
      "minute": 0,
      "second": 0,
      "nano": 0
    }
  },
  "itineraries": []
}
  • 해결 방법
    • Gson 라이브러리의 직렬화 인터페이스를 구현하는 클래스를 생성하여 LocalDateTime 포맷 변경
      import com.google.gson.JsonElement;
      import com.google.gson.JsonPrimitive;
      import com.google.gson.JsonSerializationContext;
      import com.google.gson.JsonSerializer;
      

public class LocalDateTimeSerializer implements JsonSerializer {

private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

@Override
public JsonElement serialize(LocalDateTime localDateTime, Type type, JsonSerializationContext jsonSerializationContext) {
    return new JsonPrimitive(formatter.format(localDateTime));
}

}

// 사용
GsonBuilder gsonBuilder = new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer())
.setPrettyPrinting();
Gson gson = gsonBuilder.create();
String saveData = gson.toJson(tripEntity, TripEntity.class);


###
3) 날짜 타입 체크
````java
public static boolean isValid(String input) {    
    return input.split("-").length == 3
            && input.split("-")[0].length() == 4
            && input.split("-")[1].length() <= 2
            && input.split("-")[2].length() <= 2;
}
  • isValid 메서드로 사용자가 입력한 날짜의 형태를 체크하다가 여정의 정보가 저장되지 않음을 발견
  • 여행 정보는 2024-12-25 과 같은 형태로 날짜만 입력했을 때에 정상 작동 하였지만, 여정 정보는 시간까지 입력하기 때문에 input.split("-")[2] 의 길이가 예상과는 다름을 디버깅으로 발견
    public static boolean isValid(String input) {
      if (input.contains(" ")) {
          input = input.split(" ")[0];
      }
      return input.split("-").length == 3
              && input.split("-")[0].length() == 4
              && input.split("-")[1].length() <= 2
              && input.split("-")[2].length() <= 2;
    }
  • 사용자가 입력한 날짜의 형태가 2024-12-25 10:00 처럼 시간까지 포함 했다면 공백을 기준으로 앞부분의 날짜만 체크하는 것으로 변경하여 정상 작동

[1차 프로젝트 후기]

  • 처음 메뉴를 생각했을 때에는 여정 정보는 여행의 하위 목록으로만 생각해서 메뉴를 [1.여행기록 2.여행조회 3.종료] 3개로 진행했습니다.
  • 해당 기능이 완료 된 후에 요구사항에 있는 모든 메뉴를 구현하는게 옳다고 생각이 들어서 [3.여정기록 4.여정조회] 를 어떻게 구현해야 할지 고민했습니다.
  • [3.여정기록] 을 위해서는 우선적으로 여행정보가 필요하기 때문에 해당 메뉴를 선택하면 여행목록을 출력한 후에 여행을 선택하여 여정을 입력할 수 있도록 했습니다.
  • [4.여정조회] 의 기능은 사용자로부터 키워드를 입력받아 모든 여행의 모든 여정에 해당 키워드가 있는지 체크한 후 해당 여행의 기본정보(아이디, 여행이름)과 키워드가 포함된 여정만 리스트에 담아 출력했습니다.
  • 여정 정보를 입력받을 때에 체크인/체크아웃이 필수값이 아니라는 생각에 여정 정보를 입력받기 전에 여정의 타입(숙소인지 아닌지)을 사용자로부터 받은 후에 체크인/체크아웃을 선택적으로 받으려고 계획했지만 아직 구현하지 못한 상태입니다.
  • 2차 프로젝트가 시작되기 전에 여정의 타입을 입력받아서 체크인/체크아웃을 선택적으로 저장할 수 있도록 추가할 예정입니다.