본문 바로가기
Project/2차 프로젝트 리뷰

2차 프로젝트 - 'BBOL BBOL BBOL' 리뷰

by ys2ys2 2024. 11. 24.

프로젝트 개요     

  • 프로젝트 주제 : 공공데이터 활용 여행지 추천 및 여행 기록 공유 커뮤니티 서비스
  • 기간 : 2024.10.07 ~ 2024.11.05
  • 팀 내 역할 : 팀원 (문서 기록 및 개발)
  • 개발 페이지 : 메인 페이지, 공공데이터 저장소, 핫플/여행콕콕/배너/맛집 페이지, 여행일정 작성 페이지, 여행뽈뽈 페이지
  • 기술 스택

 


 

 

프로젝트 취지     

 

코로나 엔데믹 이후 여행에 대한 관심과 수요가 급격히 증가함에 따라, 신뢰할 수 있는 여행지 추천과 사용자 간 소통이 가능한 커뮤니티 사이트를 제작하고자 하였습니다.

 

자료를 조사하는 과정에서 많은 사람들이 여행 계획을 세울 때 여러 가지 어려움을 겪고 있다는 점을 발견했습니다.

예를 들어, 어떻게 여행 계획을 세워야 할지 막막해하거나, 안전하면서도 신뢰할 수 있는 여행지를 찾는 데 어려움을 느끼는 경우가 많았습니다.

또한, 상업적 광고로 인해 진짜 정보를 구별하기가 힘들다는 점도 중요한 문제로 파악되었습니다.

 

이러한 문제들을 해결하기 위해

공공데이터를 활용하여 신뢰성 있는 최신 여행 정보를 제공하고,

사용자 간의 경험 공유를 통해 풍부한 아이디어를 얻을 수 있는 환경을 마련하고자 했습니다.

더 나아가, 커뮤니티 기능을 활성화하여 사용자가 자연스럽게 참여하고 소통할 수 있도록 유도하며,

여행 정보를 쉽게 저장하고 공유할 수 있는 기능도 함께 도입하였습니다.

 

이런 모든 요소를 통해 사용자 중심의 문제 해결에 중점을 두며 프로젝트를 진행하게 되었습니다.

 

저는 이번 프로젝트에서

메인 페이지, 공공데이터 저장소, 핫플페이지, 맛집페이지, 여행지 페이지, 여행일정 페이지, 여행뽈뽈 페이지를 담당했습니다.

 

 


 

 

✅ 공공데이터 저장소(여행지DB, 맛집DB)     

 

공공데이터 저장소 페이지입니다.

필요한 공공데이터를 매번 호출해서 사용하기 어렵다고 판단되어 데이터베이스에 저장하기 위해 만든 페이지입니다.

여행지 DB 페이지에서 사용한 공공데이터는

공공데이터포털의 한국관광공사_국문 관광정보 서비스_GW를 사용하였습니다.

 

공공데이터포털에서 제공하는 API를 활용해 데이터를 데이터베이스에 저장하는 방법을 이해하기 위해, API 명세서를 참고하며 가이드를 최대한 숙지하고 이를 실제로 적용해보려고 노력했습니다.

 

관광정보 서비스 API에서는 여러 기능들을 제공하고 있었습니다.

공공데이터 API 기능

 

핫플레이스, 맛집, 여행지 페이지에 필요한 데이터들을 사전에 정리하며, 이를 어떻게 받을지에 대해 고민했습니다.

여행지 이름과 주소, Google Maps API에 전달할 위도와 경도, 여행지의 상세 정보 및 이미지를 포함한 데이터를 수집해야 한다고 판단하여 이에 맞춰 로직을 설계했습니다.

 

먼저 관광정보 API를 활용해 /areaCode1을 통해 지역 코드를 조회한 뒤,

해당 지역 코드를 이용해 /detailCommon1으로 해당 지역의 여행지 정보를 조회할 수 있도록 구현했습니다.

지역 코드를 매번 호출해서 사용하는건 비효율적이라고 생각해서 첫 페이지에서 사용자에게 제공하도록 하였습니다.

다음 로직을 위해 사용자에게 받은 regionCode를 processCodeApi.jsp의 api 호출 부분에 전달하도록 구현했습니다.

<input class="inputText" type="text" id="regionCode" name="regionCode" placeholder="지역 코드를 입력해 주세요." required>

inputApi

 

사용자에게 받은 지역코드인 regionCode를 통해 API를 호출합니다.

String regionCode = request.getParameter("regionCode");

 

/detailCommon1에 API 요청이 들어오면 각 여행지의 상세 정보를 조회하는 데 필요한 contentId 값을 받을 수 있습니다.

API 호출 시 API 명세서 가이드에 있는 내용에 신경쓰면서 구현하였습니다.

필수 값으로 serviceKey, MobileOS, MobileApp을 넣어주었고

contentTypeId에는 관광지에 해당하는 12로 조회하게 하였습니다.

너무 많은 데이터들을 한번에 불러오면 과부하가 걸려서 매번 랜덤으로 10개씩 불러오게 하였습니다.

제목(title), 설명(overview), 주소(addr1), 위도/경도(mapy, mapx), 대표 이미지(firstimage) 등의 기본 정보를 불러오게 하였습니다.

 

전체 로직

String apiKey = "SERVICEKEY";
String apiUrl = "http://apis.data.go.kr/B551011/KorService1/areaBasedList1";

Random random = new Random();
int randomPage = random.nextInt(20) + 5;


String params = "?serviceKey=" + apiKey
              + "&MobileOS=ETC"
              + "&MobileApp=APP"
              + "&_type=json"
              + "&arrange=O"
              + "&contentTypeId=12"
              + "&numOfRows=10"
              + "&numOfRows=10"
              + "&areaCode=" + regionCode
              + "&pageNo=" + randomPage;

try {
    StringBuilder urlBuilder = new StringBuilder(apiUrl);
    urlBuilder.append(params);

    URL url = new URL(urlBuilder.toString());
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.setRequestProperty("Content-type", "application/json");

    int responseCode = conn.getResponseCode();
    BufferedReader rd;

    if (responseCode >= 200 && responseCode <= 300) {
        rd = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
    } else {
        rd = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "UTF-8"));
    }

    StringBuilder sb = new StringBuilder();
    String line;
    while ((line = rd.readLine()) != null) {
        sb.append(line);
    }
    rd.close();
    conn.disconnect();

    String jsonResponse = sb.toString();

    if (jsonResponse.startsWith("{")) {

        JSONObject jsonObject = new JSONObject(jsonResponse);
        if (jsonObject.has("response")) {
            JSONObject responseObj = jsonObject.getJSONObject("response");

            if (responseObj.has("body")) {
                JSONObject responseBody = responseObj.getJSONObject("body");

                if (responseBody.has("items") && !responseBody.isNull("items")) {
                    JSONArray itemsArray = responseBody.getJSONObject("items").optJSONArray("item");

                    if (itemsArray != null && itemsArray.length() > 0) {
                        List<Map<String, String>> itemList = new ArrayList<>();

                        for (int i = 0; i < itemsArray.length(); i++) {
                            JSONObject item = itemsArray.getJSONObject(i);
                            Map<String, String> itemData = new HashMap<>();
                            itemData.put("title", item.optString("title", "정보 없음"));
                            itemData.put("addr1", item.optString("addr1", "정보 없음"));
                            itemData.put("areacode", item.optString("areacode", "정보 없음"));
                            itemData.put("contentid", item.optString("contentid", "정보 없음"));
                            itemData.put("firstimage", item.optString("firstimage", "정보 없음"));


                            itemList.add(itemData);
                        }
               
                        request.setAttribute("items", itemList);
                    }
                }
            }
        }
    } else {
        request.setAttribute("errorMessage", "API 응답이 유효한 JSON 형식이 아닙니다. API 키가 유효하지 않거나 요청에 문제가 있을 수 있습니다.");
    }
} catch (IOException e) {
    e.printStackTrace();
    request.setAttribute("errorMessage", "API 호출 중 오류가 발생했습니다: " + e.getMessage());
}

 

 

processCodeApi

 

콘텐츠 ID에 있는 체크박스를 체크하거나 상단에 콘텐츠ID 부분에 직접 입력을 하게 되면 해당 contentId를 /detailImage1에 전달해 부가적으로 더 많은 이미지를 가져오게 하였습니다.

모든 데이터들은 detailItemList에 저장이 되어집니다.

저장하고 싶은 데이터들을 확인 후 선택하고 'DB에 저장' 버튼을 눌러서 테이블에 저장되게 하였습니다.

processDetail

 

DB에 저장 버튼을 누르면 selectedContentIds의 값이 post형식으로 controller에 전달됩니다.

<div class="search-detail-container">
    <h2>DB에 저장하기</h2>
    <form action="${pageContext.request.contextPath}/HotPlace/inputDB" method="post">
        <label class="searchContentId" for="selectedContentIds">콘텐츠 ID:</label>
        <input type="text" id="selectedContentIds" name="selectedContentIds" placeholder="체크된 콘텐츠 ID가 여기에 표시됩니다." required>
        <button type="submit">DB에 저장</button>
        <label>
            <input type="checkbox" id="selectAllCheckbox" onclick="toggleSelectAll(this)"> 전체 선택
        </label>
    </form>
</div>

 

Controller   

컨트롤러에서는 받은 요청을 처리합니다.

세션에서 저장된 detailItemList들을 받아서 service로 전달합니다.

@Autowired
private HotPlaceService hotPlaceService;

    @PostMapping("HotPlace/inputDB")
    public String inputData(@RequestParam("selectedContentIds") String selectedContentIds,
                    HttpSession session, RedirectAttributes redirectAttributes) {
        
        List<Map<String, Object>> detailItemList = (List<Map<String, Object>>) session.getAttribute("detailItemList");

        if (selectedContentIds == null || selectedContentIds.isEmpty()) {
            redirectAttributes.addFlashAttribute("message", "콘텐츠 ID를 선택해 주세요.");
            return "redirect:/HotPlace/errorPage";
        }

        if (detailItemList == null) {
            redirectAttributes.addFlashAttribute("message", "세션에서 데이터를 가져올 수 없습니다.");
            return "redirect:/HotPlace/errorPage";
        }

        String resultMessage = hotPlaceService.insertHotPlaceData(selectedContentIds, detailItemList);
        redirectAttributes.addFlashAttribute("message", resultMessage);

        return "redirect:/HotPlace/inputDB";
    }

 

Service(interface)   

String insertHotPlaceData(String selectedContentIds, List<Map<String, Object>> detailItemList);

 

ServiceImpl (구현체)   

Impl에서는 DAO를 호출해서 데이터를 전달합니다.

@Service
public class HotPlaceServiceImpl implements HotPlaceService {

    @Autowired
    private HotPlaceDAO hotPlaceDAO;

    @Transactional
    @Override
    public String insertHotPlaceData(String selectedContentIds, List<Map<String, Object>> detailItemList) {
        String[] contentIdArray = selectedContentIds.split(",");

        try {
            for (String contentId : contentIdArray) {
                contentId = contentId.trim();

                for (Map<String, Object> detailData : detailItemList) {
                    if (contentId.equals(detailData.get("contentid"))) {
                        
                        StringBuilder imageUrls = new StringBuilder((String) detailData.get("firstimage"));
                        List<Map<String, String>> images = (List<Map<String, String>>) detailData.get("images");

                        if (images != null && !images.isEmpty()) {
                            for (Map<String, String> imageData : images) {
                                if (imageUrls.length() > 0) {
                                    imageUrls.append(",");
                                }
                                imageUrls.append(imageData.get("originimgurl"));
                            }
                        }

                        hotPlaceDAO.insertHotPlace(
                            contentId,
                            (String) detailData.get("title"),
                            (String) detailData.get("addr1"),
                            (String) detailData.get("overview"),
                            Double.parseDouble((String) detailData.get("mapx")),
                            Double.parseDouble((String) detailData.get("mapy")),
                            imageUrls.toString(),
                            (String) detailData.get("areacode")
                        );

                        break;
                    }
                }
            }
            return "데이터가 성공적으로 저장되었습니다.";
        } catch (Exception e) {
            e.printStackTrace();
            return "데이터 저장 중 예외 발생: " + e.getMessage();
        }
    }
}

 

DAO   

MyBatis에서는 SQL 매퍼에서 파라미터를 하나의 객체로 받아야 하기 때문에 Java에서 제공하는 paramMap을 사용해

파라미터를 Map<String, Object> 형식인 키-값 으로 묶어서 데이터를 전달했습니다. 

@Repository
public class HotPlaceDAO {

@Autowired
private SqlSession sqlSession;
private static final String MAPPER = "com.human.web.mapper.HotPlaceMapper";


public int insertHotPlace(String contentId, String title, String addr1, String overview,
                          double mapx, double mapy, String firstimage, String areacode) {
    Map<String, Object> paramMap = new HashMap<>();
    paramMap.put("contentId", contentId);
    paramMap.put("title", title);
    paramMap.put("addr1", addr1);
    paramMap.put("overview", overview);
    paramMap.put("mapx", mapx);
    paramMap.put("mapy", mapy);
    paramMap.put("firstimage", firstimage);
    paramMap.put("areacode", areacode);

    try {
        return sqlSession.insert(MAPPER + ".insertHotPlace", paramMap);

    } catch (Exception e) {
        System.out.println("DB 삽입 중 오류 발생: " + e.getMessage());
        e.printStackTrace();
        return 0;
    }
}

 

Mapper   

Mapper에서는 받은 값들을 쿼리문을 통해 HotPlace 테이블에 저장합니다. 

<insert id="insertHotPlace">
    INSERT INTO HotPlace (contentid, title, addr1, overview, mapx, mapy, firstimage, areacode)
    VALUES (#{contentId}, #{title}, #{addr1}, #{overview}, #{mapx}, #{mapy}, #{firstimage}, #{areacode})
</insert>

 

 

🖥️ 화면 시연

 

 

 

 


 

✅ 메인 페이지     

메인 페이지는 여러가지 모델 사이트들을 참고하면서 구현하였습니다.

여행지 추천 사이트에 맞게 동적인 요소들을 많이 추가하였으며, 가을이라는 테마에 맞게 구현하였습니다.

 

이미지 슬라이드를 활용하여 배너를 구현하였으며 자바스크립트를 통해 슬라이드가 전환될 때 배경색이 동적으로 변경되도록 설정하였습니다.

슬라이드에 표시되는 여행지 사진은 데이터베이스 테이블에 저장된 사진들 중 랜덤으로 선택하여 표시되며

각 사진을 클릭하면 해당 여행지의 소개 페이지로 이동하도록 구성하였습니다.

또한 슬라이드 좌측에는 여행지에 대한 간략한 설명을 추가하여 사용자들이 해당 여행지를 쉽게 이해할 수 있도록 하였습니다.

 

 

중간 부분에는 11가지 테마로 구성된 여행지 소개 섹션을 구성했으며,

각 테마마다 글자를 넣어서 해당 글자를 클릭시 테마에 맞는 여행지 정보를 보여주게 구현했습니다.

각 패널에 display: block과 none;을 통해 사용자가 선택한 탭과 패널만 표시가 되게 하였습니다.

또한 JSTL의 <c:forEach>와 <c:if>를 통해 테이블에서 가져온 값을 3개씩 표시하게 하였습니다.

<c:forEach var="hotplace" items="${hotplaceDetails}" varStatus="status">
    <c:if test="${status.index >= 3 && status.index < 6}">
    <div class="image-box">
        <a href="${pageContext.request.contextPath}/HotPlace/${hotplace.contentid}">
        <img src="${hotplace.firstimage}" alt="${hotplace.title}" class="timage-placeholder" />
        <span class="image-box-2">
        나홀로 여행
        </span>
        <p>${hotplace.title}</p>
        </a>
    </div>
    </c:if>
</c:forEach>

 

 

여행지 소개 섹션 아래에는 사용자들에게 여행지를 추천하기 위한 여행콕콕 부분을 추가하였습니다.

마우스를 올리면 해당 요소가 넓게 펼쳐지는 효과를 적용하여 시각적 즐거움을 제공하고자 하였으며

이러한 동적 요소를 통해 페이지에 생동감을 더하고 사용자 경험을 향상시키기 위해 노력하였습니다.

 

 

여행콕콕 밑으로는 커뮤니티와 연결되는 탭을 추가하였습니다.

오늘의 인기 커뮤니티 탭에서는 실제 데이터베이스에 저장되어 있는 커뮤니티 글이 보여지며,

제목 클릭 시 해당 커뮤니티 게시글로 이동할 수 있게 구현하였습니다.

 

커뮤니티 탭 하단에는 축제 페이지와 연결하게 하였으며 마지막으로는 여행지 참여 기관을 슬라이드로 구성하였습니다.

 

 

메인 페이지 시연

 

 


 

 

핫플/맛집/여행지 페이지     

 

핫플레이스, 맛집, 여행지 페이지는 동일한 구성을 갖고 있어 한 번에 정리하고

각 페이지별 디테일이 다른 부분들만 추가해서 설명드리겠습니다.

각 페이지는 하나의 jsp로 구성되어있으며

메인페이지에서 각각의 여행지 콘텐츠들을 가져와서 사용자들에게 제공할 때 {contentId}로 가져와서 제공하고 있습니다.

 

각 페이지의 상단에는 해당 여행지의 이름과 주소를 사용자에게 제공하며

로그인한 유저가 저장하기 버튼을 누르면 세션에 저장된 m_idx 값과 해당 여행지의 contentId를 mypage 테이블에 전달하도록 구현했습니다.

이를 통해 마이페이지에서 사용자가 저장한 여행지를 확인할 수 있습니다.

로그인하지 않은 유저가 저장하기 버튼을 누를 경우, 세션에서 member 값이 null로 확인되면 로그인 페이지로 리다이렉트되도록 설계했습니다.

@PostMapping("/hotplace/save")
public String saveHotplaceToMypage(@RequestParam("contentid") String contentid, HttpSession session) {

    M_MemberVO member = (M_MemberVO) session.getAttribute("member");

    if (member == null) {
        System.out.println("로그인된 사용자가 없습니다.");
        return "redirect:/Member/login";
    }

    int m_idx = member.getM_idx();

    hotplaceService.saveHotplace(m_idx, contentid);

    return "redirect:/MyPage/myPageMain";
}

 

또한, 공유하기 버튼을 통해 해당 페이지의 URL을 공유할 수 있도록 구현했습니다.

저장하기와 공유하기 버튼 아래에는 고정된 탭을 배치하여 지정된 섹션으로 이동할 수 있는 네비게이션 바를 구성했습니다.

이미지 슬라이드를 통해 사용자가 해당 여행지의 사진을 선택하여 볼 수 있도록 구현했으며, 슬라이드 아래에는 여행지의 상세 정보를 배치했습니다.

그 아래에는 테이블에 저장된 위도,경도를 바탕으로 Google Maps API를 사용해 해당 여행지의 위치를 제공했습니다.

마지막으로, 해당 지역 코드를 활용한 필터링 기능을 추가하여 해당 지역의 다른 여행지를 추천하는 탭을 구성했습니다.

 

 

 

🖥️ 화면 시연

 

 

 

 

페이지별 디테일

  • 핫플레이스 페이지

핫플레이스 페이지의 하단에는 날씨 정보 API를 추가하였습니다.

해당 contentID에 저장된 위도,경도를 받아서 해당 지역의 날씨를 3시간 단위 15일치를 가져와서 제공하였습니다.

또한 canvas를 사용해 점과 선으로 그래프를 표시하여 시각적인 효과를 주었습니다.

 

마지막으로 여행톡 탭을 통해 로그인 한 유저가 해당 여행지에 대한 댓글을 남겨 커뮤니티 기능을 활성화 시키고자 하였습니다.

페이지가 생성될 때는 {contentId}값으로 생성이 되며, 내용 구성과 댓글도 contentId에 해당하는 내용들로 구성이 됩니다.

여행톡에 관한 페이지네이션도 적용했으며 여행톡이 저장이 될 때 contentId도 같이 저장이 되게 구현하였습니다.

여행톡은 댓글 기능이며 CRUD에 맞게 게시, 조회, 수정, 삭제가 가능하게 구현하였습니다.

@GetMapping("/HotPlace/{contentid}")
	public String showHotplaceByContentId(@PathVariable("contentid") int contentid,
			@RequestParam(defaultValue = "1") int page,
			Model model) {
		Map<String, Object> hotplace = hotplaceService.getHotplaceById(contentid);

		int commentsPerPage = 10;
		int offset = (page - 1) * commentsPerPage;

		List<TalkVO> talkList = talkService.getTalkList(contentid, "hotplace", offset, commentsPerPage);
		int totalTalkCount = talkService.getTotalTalkCount(contentid, "hotplace");
		int totalPages = (int) Math.ceil((double) totalTalkCount / commentsPerPage);

		model.addAttribute("hotplace", hotplace);
		model.addAttribute("talkList", talkList);
		model.addAttribute("currentPageNumber", page);
		model.addAttribute("totalTalkCount", totalTalkCount);
		model.addAttribute("totalPages", totalPages);

		return "HotPlace/hotdetail";
	}

 

 

🖥️ 화면 시연

 

 


 

✅ 여행뽈뽈 페이지     

여행뽈뽈 페이지는 구글 API와 공공데이터 API를 호출해서 자바스크립트로만 구현한 페이지입니다.

자연 관광지, 문화 탐방, 액티비티 등을 클릭하면 관광정보 API 호출시에 contentTypeId에 해당하는 값들이 다르게 호출되어 사용자들에게 표시되게 하였습니다.

 function setCategory(category) {
        let contentTypeId;
        if (category === 'nature') {
            contentTypeId = 12; // 자연 관광지
        } else if (category === 'culture') {
            contentTypeId = 14; // 문화탐방
        } else if (category === 'activity') {
            contentTypeId = 28; // 액티비티
        }
}

 

✅ 여행일정 작성 페이지     

 

여행 일정 작성하기 버튼을 눌러 이동한 화면입니다.

로그인한 사용자는 여행 일정을 작성하여 마이페이지에서 확인할 수 있으며, 저장된 여행 일정은 여행 일정 리스트 페이지에서도 확인할 수 있습니다.

 

페이지 상단의 ‘제목을 입력해 주세요’ 영역에서 여행기의 제목을 작성한 후, 날짜 선택 달력을 통해 여행 날짜를 설정할 수 있습니다.

우측의 큰 일정 추가 버튼을 눌러 DAY 카드를 추가할 수 있고, 각 DAY 카드 안의 일정 추가 버튼을 통해 해당 날짜에 방문할 여행지를 추가할 수 있도록 구현했습니다.

여행지를 추가하면 우측에 팝업 형태의 자동완성 리스트가 나타나며, 검색 후 여행지를 추가하면 지도에 마커가 표시됩니다. 여러 여행지를 추가할 경우, 순서대로 라인을 연결하여 사용자가 시각적으로 경로를 쉽게 확인할 수 있도록 설계했습니다.

 

여행 일정 테이블에서는 m_idx를 통해 회원과 여행 일정을 매칭하고, post_id를 사용해 각각의 여행 일정을 관리합니다.여행기 제목은 title로 저장되며,

여행 시작 날짜와 종료 날짜는 각각 period_start, period_end로 관리됩니다.

DAY별 데이터는 day_number를 사용해 저장하며,

label_number 속성을 통해 여행지 추가 순서를 관리하여 리스트에서 불러올 때 순서를 유지할 수 있게 구성했습니다.

 

여행지 추가 시에는 위도(place_latitude)와 경도(place_longitude), 도로명 주소(place_address) 정보를 함께 저장하며, Google Maps API의 썸네일 데이터를 활용해 각 여행 일정의 썸네일도 저장되도록 구현했습니다.

 

 

🖥️ 화면 시연

 

 

 


 

프로젝트 후기     

 

JSP와 MVC 패턴을 활용한 프로젝트는 처음이라 초기에는 상당히 어려웠지만, 어려운 만큼 더 잘해보고 싶다는 의욕으로 많은 시간을 투자했습니다.

이 과정에서 많은 공부를 할 수 있었고, 새로운 기술을 익히며 한 단계 성장할 수 있었습니다.

 

API를 처음 사용하다 보니 초기에는 막막함이 컸습니다. 그러나 API 명세서를 꼼꼼히 읽고 차근차근 접근한 덕분에 의도한 대로 API 호출을 성공적으로 구현할 수 있었습니다. 이를 통해 API 사용 시 명세서를 철저히 확인하는 것이 얼마나 중요한지 다시 한 번 깨달을 수 있었습니다.

 

이번 프로젝트에서는 협업의 중요성을 다시 한 번 실감했습니다. 처음에는 메인 페이지 하나만 맡았지만, 팀장님과 팀원들과의 소통을 통해 추가로 필요한 여러 페이지들을 파악하게 되었습니다. 이에 메인 페이지 작업을 신속히 마친 후, 팀에 부족한 페이지들을 맡아 진행했습니다. 결과적으로 13개가 넘는 페이지 작업을 맡았고, 처음엔 부담스럽고 막막했지만, 하나씩 완성해가는 과정을 통해 성취감을 느낄 수 있었습니다.

 

전체적인 프로젝트 완성도는 이전 1차 프로젝트에 비해 크게 향상되었지만 여전히 아쉬움도 남는 프로젝트였습니다.

프로젝트 초기에는 팀원들의 개인 역량을 충분히 파악하지 못한 상태에서 진행하다 보니 마감 기한이 다가오면서 여러 페이지가 완성되지 않아 초조함을 느끼기도 했습니다.

그러나 그럴 때마다 팀장님과 적극적으로 소통하며 부족한 부분들을 빠르게 보완해 나갔고, 이를 통해 프로젝트의 완성도와 디테일을 높일 수 있었습니다.

결과적으로 팀원들과의 협업과 의사소통이 프로젝트 성공의 핵심이었고, 이를 바탕으로 좋은 평가를 받을 수 있었습니다. 이번 경험을 통해 기술적인 성장뿐만 아니라 협업의 중요성과 문제 해결 능력을 기를 수 있었던 소중한 프로젝트였습니다.