✔️ 주요 코드 중심 분석 ✔️
카카오 지 API을 통한 카페 정보 저장
Back
public void saveCafe() throws Exception {
List<Cafe> cafes = new ArrayList<>();
List<String> regions = new ArrayList<>();
// 1
regions.add("서울");
// 지역 정보 추가하는 코드 생략
int totalCount = 45;
int countPerPage = 15;
int totalPages = (totalCount + countPerPage - 1) / countPerPage;
// 2
for (String region : regions) {
for (int page = 1; page <= totalPages; page++) {
StringBuilder urlBuilder = new StringBuilder("https://dapi.kakao.com/v2/local/search/keyword.json");
urlBuilder.append("?query=" + URLEncoder.encode(region + "테마카페", "UTF-8"));
urlBuilder.append("&page=" + page);
// 3
URL url = new URL(urlBuilder.toString());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("content-type", "application/json;charset=UTF-8");
conn.setRequestProperty("Authorization", "KakaoAK "+ apiKey);
StringBuilder resBuilder = new StringBuilder();
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
String line;
while ((line = br.readLine()) != null) {
resBuilder.append(line);
}
} finally {
conn.disconnect();
}
// 4
JSONParser parser = new JSONParser();
JSONObject mobj = (JSONObject) parser.parse(resBuilder.toString());
JSONArray data = (JSONArray) mobj.get("documents");
for (int i = 0; i < data.size(); i++) {
JSONObject cafeJson = (JSONObject) data.get(i);
Cafe cafe = new Cafe();
cafe.setCafeName((String) cafeJson.get("place_name"));
cafe.setTel((String) cafeJson.get("phone"));
cafe.setAddress((String) cafeJson.get("address_name"));
cafe.setLat((String) cafeJson.get("y"));
cafe.setLng((String) cafeJson.get("x"));
cafes.add(cafe);
}
}
}
cafeRepository.saveAll(cafes);
}
- 전 지역의 카페 데이터를 수집하기 위해 지역 정보를 담는 리스트 생성
- API를 통해 1에서 저장한 지역의 '테마카페' 관련한 정보(주소, 위경도, 카페명)를 수집
- 외부 API에 HTTP GET 요청
- 응답 받은 데이터 중 필요한 정보를 파싱하여 데이터 베이스에 저장
관련 이슈
카카오맵 키워드 검색 API 결과값으로 최대 45개까지만 제공한다고 함
그러나 우리는 전국의 카페들을 지도에 노출해주어야 함
해결 방법
행정 구역을 리스트에 담아 리스트를 돌며 '서울 테마카페', '강원 테마카페' 와 같은 방식으로 각 행정구역별 45개의 데이터를 담도록 함
모든 카페 위치 정보
Front
useEffect(() => {
axios.get(`${url}/mapMarker`) // 1
.then(response => {
setCafes(response.data);
})
.catch(error => {
console.error('에러:', error);
});
}, []);
useEffect(() => {
var mapContainer = document.getElementById("mapView"),
mapOption = {
center: new kakao.maps.LatLng(37.4738645692092, 126.885434915952),
level: 4,
};
var map = new kakao.maps.Map(mapContainer, mapOption);
// 2
cafes.forEach((cafe) => {
var imageSrc = cafe.existing
? "/img/map3.png" // 입점카페
: "/img/marker_basic.png", // 기본카페
imageSize = cafe.existing ? new kakao.maps.Size(60, 60) : new kakao.maps.Size(50, 50),
imageOption = { offset: new kakao.maps.Point(27, 69) };
var markerImage = new kakao.maps.MarkerImage(
imageSrc, imageSize, imageOption
);
const markerPosition = new kakao.maps.LatLng(cafe.lat, cafe.lng);
const marker = new kakao.maps.Marker({
position: markerPosition,
map: map,
image: markerImage,
});
// 3
kakao.maps.event.addListener(marker, "click", function () {
setSelectCafe(cafe);
});
});
// 4
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
const locPosition = new kakao.maps.LatLng(lat, lon);
map.setCenter(locPosition); // 지도 중심 좌표 설정
});
}
}, [cafes]);
- mapMarker 엔드포인트로 카페 정보를 불러오는 GET 요청 전달하고 응답으로 받은 데이터를 cafes[]에 저장
- 저장된 카페 데이터를 순회하며 서비스 입점 여부를 파악하여 마커를 달리 표시하도록 설정
- 마커를 클릭하면 선택한 카페의 정보를 selectCafe에 저장
- 사용자의 현재 위치를 기반으로 지도의 중심 좌표를 설정

Back
@GetMapping("/mapMarker")
public ResponseEntity<Object> getAllCafes() {
try {
List<CafeDto> cafes = service.getCafes(); // 1
return new ResponseEntity<>(cafes, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
public List<CafeDto> getCafes(){
List<Cafe> cafeList = cafeRepository.findAll(); // 2
List<CafeDto> cafeDTOList = new ArrayList<>();
for (Cafe cafe : cafeList) { // 3
CafeDto cafeDTO = cafe.toDTO();
cafeDTOList.add(cafeDTO);
}
return cafeDTOList;
}
- 클라이언트에서 전달 받은 데이터를 통해 서비스 레이어로 모든 카페 정보 요청
- 데이터베이스에서 모든 카페 정보를 불러옴
- Cafe 객체를 DTO로 변환하여 클라이언트에 반환
특정 카페 정보
Front
// 1
const MapCafeInfo = ({ selectCafe, setSelectCafe, wish, setWish, wishModal, wishCafeNo }) => {
useEffect(() => {
if(selectCafe !== null) {
axios.get(`${url}/review/storeList/${cafeNo}?page=${currentPage}&size=5`) // 2
.then((res) => {
setReviewList(res.data.data);
setTotalPages(res.data.pageInfo.totalPages);
})
.catch((error) => {
console.error("에러:" + error);
});
}
- 앞서 모든 카페 정보 노출시 마커를 클릭하여 저장한 selectCafe 정보를 props로 전달
- review/storeList/{cafeNo} 엔드포인트로 특정 카페에 작성된 리뷰 리스트 GET 요청 전달
- 특정 카페에 작성된 리뷰 리스트는 5개를 1페이지로 지정하므로 페이지 정보도 함께 저장
Back
@GetMapping("/review/storeList/{cafeNo}")
public ResponseEntity<Object> getStoreList(@RequestParam("page") Integer page,
@RequestParam("size") Integer size,
@PathVariable("cafeNo") Integer cafeNo){
try{
Page<Review> reviewPage = reviewService.storeReviewPage(page-1, size, cafeNo);
List<Review> responseList = reviewPage.getContent();
List<ReviewListResDto> responseLists = new ArrayList<>();
for(Review review : responseList){
responseLists.add(ReviewListResDto.reviewToReviewListRes(review));
}
return new ResponseEntity<>(new MultiResponseDto<>(responseLists, reviewPage), HttpStatus.OK);
}catch (Exception e){
e.printStackTrace();
return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
}
}

카페 찜하기
useEffect(() => {
if (memNo != null) {
axios.get(`${url}/member/cafeIsWish/${memNo}/${cafeNo}`, { // 1
headers : {
Authorization :accessToken,
Refresh : getCookie("refreshToken")
}
})
.then((res) => {
setWish(res.data);
})
.catch((error) => {
console.log(error);
})
}
}, [selectCafe, currentPage])
const toggleWish = () => {
if (memNo !== undefined) {
axios.post(`${url}/member/cafeWish/${memNo}/${selectCafe.cafeNo}`, null, { // 2
headers : {
Authorization :accessToken,
Refresh : getCookie("refreshToken")
}
.then(()=>{
setWish(res.data);
})
} else {
Toast('error', '로그인이 필요합니다')
};
}
- member/cafeIsWish/{memNo}/{cafeNo} 엔드포인트로 회원의 찜 여부를 확인하는 GET 요청 전달
- member/cafeWish/{memNo}/{cafeNo} 엔드포인트로 회원의 찜 선택 / 취소 상태를 관리하는 POST 요청 전달
Back
@GetMapping("member/cafeIsWish/{memNo}/{cafeNo}") // 특정 카페의 찜 여부
public ResponseEntity<Boolean> isWish(@PathVariable Integer memNo, @PathVariable Integer cafeNo) {
try {
Boolean isWish = service.isWishCafe(memNo, cafeNo);
return new ResponseEntity<>(isWish, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
@PostMapping("member/cafeWish/{memNo}/{cafeNo}") // 특정 카페 찜하기
public ResponseEntity<Boolean> isWishCafe(@PathVariable Integer memNo, @PathVariable Integer cafeNo) {
try {
Boolean toggleWish = service.toggleWishCafe(memNo, cafeNo);
return new ResponseEntity<>(toggleWish, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
public boolean isWishCafe(Integer memNo, Integer cafeNo){
return wishRepository.existsByMember_memNoAndCafe_cafeNo(memNo, cafeNo);
}
@Transactional
public boolean toggleWishCafe(Integer memNo, Integer cafeNo){
Cafe cafe = cafeRepository.findByCafeNo(cafeNo);
Member member = memberRepository.findByMemNo(memNo);
boolean isWish = wishRepository.existsByMember_memNoAndCafe_cafeNo(memNo, cafeNo);
if(isWish) {
wishRepository.deleteByMember_memNoAndCafe_cafeNo(memNo, cafeNo);
return false;
} else {
wishRepository.save(WishCafe.builder().member(member).cafe(cafe).build());
return true;
}
}'[Project] CafeHub' 카테고리의 다른 글
| [CafeHub] 코드 분석 및 이슈 기록 - 결제 (with. 토스 페이먼츠) (0) | 2024.01.10 |
|---|---|
| [CafeHub] 코드 분석 및 이슈 기록 - 회원 정보 수정 / 찜한 리뷰 리스트 / 찜한 카페 리스트 (0) | 2024.01.08 |
| [CafeHub] 코드 분석 및 이슈 기록 - 리뷰 상세 / 리뷰 찜 / 리뷰 추천 (0) | 2024.01.05 |
| [CafeHub] 코드 분석 및 이슈 기록 - 아이디 찾기 / 비밀번호 찾기 (0) | 2024.01.05 |
| [CafeHub] 기획 - 담당 역할에 따른 화면 정의서 (0) | 2023.12.23 |