[Project : CAFEMATE] 카페 필터검색 웹사이트 개발일지

개인적인 계기(나중에 차차 기록할 예정이다)로 프론트엔드에서 백엔드로 전향하려고 준비하다가, 항상 클론 코딩 강의만 듣는게 지겨워서, 자바쪽은 프로젝트로 부딪히면서 배워보고 싶었다. (+ 협업도 해보고 싶었다)

그러던 중 카우치 코딩에서 6주 포트폴리오 수업이 딱 내 일정에 맞고 체계적이어 보여서 신청하게 됐다.👀 이 벨로그 시리즈에 그 과정에서의 이슈 등을 쭉 정리할 예정이다. 참고로 이 사이트에는 DB 더 추가하고, 코드 수정하면서 더 디벨롭할 생각이다. 다만 개강해서 시간이 남을 때만 조금씩 하고 있다😅

☕ 카페메이트 프로젝트

✅ 깃허브 링크 : https://github.com/sungeun101/cafemate

☕ 카페메이트 링크 : http://cafemate.shop

Problem : 이 사이트를 만든 계기

개인적으로 카페를 자주 가는데, 메인 포털 사이트에 '@@동 카페'라고 검색하면
중복도 많고 원하는 분위기의 카페를 찾기까지
여러 번의 스크롤과 많은 리뷰를 클릭해봐야 되는 게 너무 귀찮았다.😅

Solution

미리 카페들의 메뉴, 가격, 후기 등의 정보를 얻고 분위기를 유추해서 카테고리를 정해놓고,
사용자가 원하는 분위기/메뉴/가격 등을 선택하면 그에 맞는 카페들을 보여주기로 했다.

기존 포털 사이트에서 리뷰를 보면 자세한 후기를 볼 수 있지만, 정보가 너무 많은 것도 피로도가 올라간다고 생각한다. 그래서 카페마다 후기는 한줄 코멘트로 볼 수 있는 UI로 정했다.


🛠 서비스 구조

이 사이트의 서비스 구조를 직관적으로 볼 수 있게 이미지로 만들었다. (참고로 포토샵+일러로 했는데, 더 간편하게 이미지를 만들 수 있는 사이트를 찾고 있다.)

기술 스택

Front : Javascript, React, Redux, Ant design

Back : Java - version 11, SpringBoot, Spring Data JPA, Gradle, Junit4, MySQL, AWS(EC2, RDS)


주요 기능 & 적용 기술

백엔드인 내가 기여한 사항들은 카페메이트 대표 색상으로 표시했다.

  • 카페 검색 : 원하는 태그(카테고리, 메뉴 등) 선택하면 그에 맞는 카페들을 불러옴
  • 찜한 카페 : 원하는 카페를 내 찜한카페 리스트로 등록, 삭제, 조회
  • 댓글 기능 : 카페 상세페이지에서 댓글 등록, 수정, 삭제, 조회
  • 페이징 기능 : 메인 페이지 검색 결과로 카페리스트를 백에서 프론트로 10개씩 보내줌
  • 로그인 : 구글 OAuth 로그인 API 사용 (프론트쪽에서 처리)
  • 지도 : 카카오 지도 API 사용 (메인페이지, 카페 상세페이지)
  • 크롤링 : 카카오맵에서 카페 기본 정보와 네이버 블로그 리뷰 크롤링(Python 사용)
  • 카페 정보 저장 : 크롤링한 카페 기본 정보(이름, 위치 등)와 그를 활용하여 얻은 정보(카테고리, 가격 등)을 저장
  • 배포 : AWS EC2로 배포하고 도메인에 연동 (jar파일로 빌드)
  • DB : AWS MySQL에 미리 크롤링한 카페 데이터와 새로 생기는 데이터 저장

이제 기본 CRUD(ex. 댓글 등록/조회/수정/삭제)외에 이 사이트의 가장 메인이 되는 로직을 설명해보겠다!

메인 페이지에서 검색 결과로 카페들을 불러오는 로직은

이때 프론트에서 보내는 API를 RequestParam 방식으로 받는다.
예시)?dong=애월읍&filtering=milktea,wifi,chat,camera,cropple,over,5&sorting=star

1. 지역 선택
1번 단계에서 사용자가 지역을 선택하면 그때 해당 지역의 카페들만 불러온다.
(DB에는 미리 카페 엔티티에 'dong' 속성(지역 정보)도 저장해둔다.)

@Repository
public interface CafesRepository extends JpaRepository<Cafes, Long> {
    List<Cafes> findAllByDong(String dong);
}

2. 태그 선택

메인 페이지에서 카페 검색할 때 고를 수 있는 태그들은 다음과 같다.

  • 아메리카노 가격
  • 별점
  • 주차가능
  • 와이파이
  • 디저트 메뉴 (마카롱, 빙수, 와플, 허니브레드, 케이크, 스무디, 밀크티, 스콘, 에이드, 아이스티, 크로플, 샌드위치, 베이글)
  • 카테고리 (깔끔한, 과제하기 좋은, 수다떨기 좋은, 사진찍기 좋은, 디저트가 있는, 로스팅 직접하는)

이 태그들 하나 당 1점씩 매겨서, 사용자가 선택한 태그들과 가장 많이 일치하는 순서대로 카페들을 배열해서 검색결과로 보내준다. 만약 사용자가 10개의 태그를 선택했을때, 그게 다 일치하는 카페는 양이 너무 적어져서 최대한 일치하는 게 많은 카페를 우선으로 결과로 보여주었다.

아래는 관련 서비스단의 코드 일부이다. 간단히 wifi 여부만 체크하는 코드만 가져왔다.

public List<CafesSearchResponseDto> getCafesList (String sorting, String dong, String filtering) {
        // 1. 선택한 동네의 모든 Cafes 리스트 가져오기
        List<Cafes> cafesListEntity = cafesRepository.findAllByDong(dong);

        // 2. 엔티티 리스트를 DTO 리스트로 변환하는 과정 
	//(DTO에는 priorty 변수를 추가함)
        List<CafesResponseDto> cafesListDto =
                cafesListEntity.stream().map(CafesResponseDto::new).collect(Collectors.toList());

        //3. 필터링 (1,2차) - 우선 순위
	//(filtering으로 받아온 String에 wifi항목이 있을 경우)
        boolean wifi = checkFilter(filtering, "wifi");
        
	...생략...
    
        // 5. 객체들의 우선순위 매기기
        for (CafesResponseDto i :cafesListDto) {
            // 와이파이가 되는 카페이면 우선순위 +1
            if(wifi==i.isWifi()) {
                i.setPriority(i.getPriority()+1);
            }
        }

3. 별점순/가격순 정렬

2번에서 정렬된 리스트 중에서 우선순위가 같은 것들을 다시 별점순 혹은 가격순으로 정렬하게 된다. 아래는 우선순위가 높은 순대로 정렬 후 다시 별점순으로 정렬하는 코드이다.

if (sorting.equals("star")) {
            return  cafesListDto.stream()
                    .sorted(Comparator.comparingInt(CafesResponseDto::getPriority) //2. 태그선택 정렬
                            .thenComparingInt(CafesResponseDto::getStarInt).reversed())//3. 별점순/가격순 정렬
                    .map(CafesSearchResponseDto::new)
                    .collect(Collectors.toList());
   }

=> 이때, 태그 대부분은 카카오맵에서 크롤링하여 얻어올 수 있지만, 카테고리 항목은 블로그 리뷰에서 카페의 분위기를 유추해서 얻어와야하는 정보였다!

그럼 카페마다 카테고리를 어떻게 분류, 저장했나?

다음 3단계로 설명할 수 있다.

  1. 미리 카테고리와 키워드를 정해두었다.
카테고리키워드
과제하기 좋은조용, 스터디, 넓은 공간, 넓은 책상, 콘센트, 과제, 공부
수다떨기 좋은친구, 모임, 그룹, 수다
사진찍기 좋은인생샷, 인생 사진, 포토존, 소품, 분위기 좋, 좋은 분위기, 인테리어, 감성, 사진찍기 좋은
로스팅 직접 하는로스팅, 로스터기, 로스터리
깔끔한깔끔, 청결, 깨끗
  1. 블로그 리뷰에서 해당 키워드(ex. 조용)가 나오면, 그 카페의 DB에 해당 카테고리(ex. 과제하기 좋은)을 저장한다.

  2. 사용자가 카페 검색 시 카테고리명을 클릭하면 해당하는 카페들의 우선순위를 +1 한다.

물론 블로그 리뷰에서 키워드 추출해서 그 카페의 분위기를 파악한다는 게, 100% 확실하지는 않다. 예를 들어, 블로그 리뷰에서 "조용했으면 좋겠는데.." 라는 내용이 있어도 조용이라는 키워드가 추출되어서 과제하기 좋은 카테고리에 속하게 된다.(!) 사실 이 부분이 아쉽긴 한데, 아마 AI?로 들어가야 하지 않나 싶다..! 만약 각잡고 프로젝트를 더 디벨롭해보면 나름 괜찮은 서비스가 될 것 같다.

이 파트(카카오맵에서 크롤링)는 파이썬 코드로, 프론트분(이지만 백엔드도 하실줄 아시는...!)께서 수고해주셨다👍 나는 그 결과로 나온 csv파일을 MySQL DB에 넣기전에 컬럼 정리하는 정도의 간단한 코드만 짜봤었다.


기획 & 설계

DB 설계 (MySQL)

자세한 DB 명세서는 여기서 확인할 수 있다!

  • Users
  • Comments
  • Likes
  • Cafes

일대다관계 매핑(ex. Users - Comments)을 처음 해보면 살짝 헷갈릴 수 있는데, JPA 공부하면서 보충하고 있다.

사실 Cafes 테이블에서 dessert랑 category는 나눌 필요가 없는데, 후에 시간이 남으면 다른 방식으로 활용해보려고 일부러 나누어놨다. 지금 로직으로는 둘다 String으로 저장되고, 내부적으로는 ','으로 구분하기에 사실상 하나의 속성으로 저장해도 된다.

API 설계

자세한 API 문서는 여기서 확인할 수 있다!

API 문서를 처음 설계할때는 어떻게 쓰는 건지, 어떻게 해야되는 지도 모르고 진행했었다. 프론트와 어떤 식으로 데이터를 주고 받을 지 명시해놓는 것인데, 이걸 만들고 하는 게 훨씬 효율적이라 하셨다. 그리고 프론트와 연동하기 전 API 테스트는 Postman을 이용해서 했다.

페이지 기획과 디자인(피그마)

페이지 기획서 → ✨디자인

좋은 웹페이지 즐겨찾기