-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[hotfix] Spot 저장방식 수정 및 추가 #155
Changes from all commits
fa94f1a
bc2cfea
f99adeb
c4e31ad
dcc4cfb
a2b63ef
65c16f8
8dd2a23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,12 +7,12 @@ | |
import com.kuddy.common.response.StatusResponse; | ||
import com.kuddy.common.spot.domain.Spot; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.json.simple.JSONArray; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
|
||
@RestController | ||
@RequestMapping("/api/v1/spots") | ||
@RequiredArgsConstructor | ||
|
@@ -21,11 +21,9 @@ public class SpotController { | |
private final SpotService spotService; | ||
private final TourApiService tourApiService; | ||
|
||
//테스트용으로 관광정보 저장하는 api | ||
@PostMapping("/test") | ||
public ResponseEntity<StatusResponse> saveTestData(@RequestParam(value = "page") int page, @RequestParam(value = "category") int category) { | ||
spotService.changeAndSave(tourApiService.getApiDataList(page, category)); | ||
|
||
@PostMapping("/sync") | ||
public ResponseEntity<StatusResponse> syncTourData(@RequestParam(value = "page") int page, @RequestParam(value = "size") int size, @RequestParam(value = "date") String date) { | ||
spotService.synchronizeTourData(tourApiService.getSyncList(page, size, date)); | ||
return ResponseEntity.ok(StatusResponse.builder() | ||
.status(StatusEnum.OK.getStatusCode()) | ||
.message(StatusEnum.OK.getCode()) | ||
|
@@ -54,7 +52,7 @@ public ResponseEntity<StatusResponse> getSpotsList(@RequestParam(value = "page") | |
@GetMapping("/{contentId}") | ||
public ResponseEntity<StatusResponse> getSpotDetail(@PathVariable Long contentId) { | ||
Spot spot = spotService.findSpotByContentId(contentId); | ||
Object commonDetail = tourApiService.getCommonDetail(spot); | ||
Object commonDetail = tourApiService.getCommonDetail(spot.getCategory().getCode(), spot.getContentId()); | ||
Object detailInfo = tourApiService.getDetailInfo(spot); | ||
JSONArray imageArr = tourApiService.getDetailImages(contentId); | ||
return spotService.responseDetailInfo(commonDetail, detailInfo, imageArr, spot); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 코드 패치를 간단히 검토해 드리겠습니다.
개선 제안:
참고: 이 답변은 지식 절단(latest 2021)을 기준으로 작성되었습니다. 코드 패치 내의 사용되는 외부 라이브러리나 프레임워크에 대한 자세한 내용은 최신 문서 또는 해당 라이브러리/프레임워크 공식 문서를 참조해야 합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 코드 패치에 대한 간단한 코드 리뷰를 도와드리겠습니다. 버그 위험 및 개선 제안 사항이 있다면 언제든지 환영합니다:
주의할 점: |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,14 +19,21 @@ | |
import com.kuddy.common.spot.exception.SpotNotFoundException; | ||
import com.kuddy.common.spot.repository.SpotRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.commons.lang3.EnumUtils; | ||
import org.json.simple.JSONArray; | ||
import org.json.simple.JSONObject; | ||
import org.springframework.data.domain.*; | ||
import org.springframework.data.jpa.domain.Specification; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.scheduling.annotation.EnableScheduling; | ||
import org.springframework.scheduling.annotation.Scheduled; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.time.LocalDate; | ||
import java.time.ZoneId; | ||
import java.time.format.DateTimeFormatter; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
@@ -36,40 +43,113 @@ | |
@Service | ||
@Transactional | ||
@RequiredArgsConstructor | ||
@EnableScheduling | ||
@Slf4j | ||
public class SpotService { | ||
|
||
private final SpotRepository spotRepository; | ||
private final HeartRepository heartRepository; | ||
private final SpotQueryService spotQueryService; | ||
private final TourApiService tourApiService; | ||
|
||
//JSON 응답값 중 필요한 정보(이름, 지역, 카테고리, 사진, 고유id)만 db에 저장 | ||
public void changeAndSave(JSONArray spotArr) { | ||
@Scheduled(cron = "0 0 6 * * *") | ||
public void syncTourDataEveryDay() { | ||
LocalDate seoulNow = LocalDate.now(ZoneId.of("Asia/Seoul")); | ||
LocalDate oneDayAgo = seoulNow.minusDays(1); | ||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); | ||
String modifiedTime = oneDayAgo.format(formatter); | ||
log.info(modifiedTime); | ||
synchronizeTourData(tourApiService.getSyncList(1, 50, modifiedTime)); | ||
} | ||
|
||
public void synchronizeTourData(JSONArray spotArr) { | ||
for (Object o : spotArr) { | ||
JSONObject item = (JSONObject) o; | ||
String contentType = ""; | ||
String areaCode = ""; | ||
Long contentId = Long.valueOf((String) item.get("contentid")); | ||
log.info("sync contentId = " + contentId); | ||
|
||
if (!spotRepository.existsByContentId(Long.valueOf((String) item.get("contentid")))) { | ||
for (Category category : Category.values()) { | ||
if (item.get("contenttypeid").equals(category.getCode())) | ||
contentType = category.name(); | ||
if(Category.hasValue((String) item.get("contenttypeid"))) { | ||
//이미 저장되어 있을 경우 내용 업데이트 | ||
if (spotRepository.existsByContentId(contentId)) { | ||
updateSpot(item, contentId); | ||
} | ||
for (District district : District.values()) { | ||
if (item.get("sigungucode").equals(district.getCode())) | ||
areaCode = district.name(); | ||
//표출되고 있는 데이터이고 db에 저장되어 있지 않은 경우 새로 저장 | ||
if ((item.get("showflag").equals("1")) && (!spotRepository.existsByContentId(contentId))) { | ||
saveSpot(item, contentId); | ||
addImage(contentId); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public void updateSpot(JSONObject item, Long contentId) { | ||
Spot spot = findSpotByContentId(contentId); | ||
//더이상 표출되지 않는 데이터이면 내용 비우기 | ||
if(item.get("showflag").equals("0")) { | ||
spot.update((String) item.get("title"), | ||
District.valueOfCode((String) item.get("sigungucode")), | ||
"", | ||
Category.valueOfCode((String) item.get("contenttypeid")), | ||
"", | ||
"", | ||
"No more information found for this spot.", | ||
(String) item.get("modifiedtime")); | ||
log.info("update showflag = 0, contentId = " + contentId); | ||
} | ||
//표출되고 있고 저장되어 있는 데이터와 modifiedTime이 다르면 정보 업데이트 | ||
if((item.get("showflag").equals("1")) && (!spot.getModifiedTime().equals(item.get("modifiedtime")))) { | ||
spot.update((String) item.get("title"), | ||
District.valueOfCode((String) item.get("sigungucode")), | ||
(String) item.get("firstimage"), | ||
Category.valueOfCode((String) item.get("contenttypeid")), | ||
(String) item.get("mapx"), | ||
(String) item.get("mapy"), | ||
getSpotAbout(item, contentId), | ||
(String) item.get("modifiedtime")); | ||
log.info("update showflag = 1, contentId = " + contentId); | ||
addImage(contentId); | ||
} | ||
|
||
} | ||
|
||
spotRepository.save(Spot.builder() | ||
.name((String) item.get("title")) | ||
.contentId(Long.valueOf((String) item.get("contentid"))) | ||
.district(District.valueOf(areaCode)) | ||
.category(Category.valueOf(contentType)) | ||
.imageUrl((String) item.get("firstimage")) | ||
.numOfHearts(0L) | ||
.mapX((String) item.get("mapx")) | ||
.mapY((String) item.get("mapy")) | ||
.build()); | ||
public void saveSpot(JSONObject item, Long contentId) { | ||
log.info("save contentId = " + contentId); | ||
spotRepository.save(Spot.builder() | ||
.name((String) item.get("title")) | ||
.contentId(contentId) | ||
.district(District.valueOfCode((String) item.get("sigungucode"))) | ||
.category(Category.valueOfCode((String) item.get("contenttypeid"))) | ||
.imageUrl((String) item.get("firstimage")) | ||
.numOfHearts(0L) | ||
.mapX((String) item.get("mapx")) | ||
.mapY((String) item.get("mapy")) | ||
.about(getSpotAbout(item, contentId)) | ||
.modifiedTime((String) item.get("modifiedtime")) | ||
.build()); | ||
} | ||
|
||
//TourAPI에서 spot about만 가져오기 | ||
public String getSpotAbout(JSONObject item, Long contentId) { | ||
String about; | ||
Object commonDetail = tourApiService.getCommonDetail((String) item.get("contenttypeid"), contentId); | ||
JSONObject detail = (JSONObject) commonDetail; | ||
about = (String) detail.get("overview"); | ||
return about; | ||
|
||
} | ||
|
||
//썸네일 없을때 상세 이미지들 중 첫번째 사진을 썸네일로 지정 | ||
public void addImage(Long contentId) { | ||
Spot spot = findSpotByContentId(contentId); | ||
if(spot.getImageUrl().isEmpty()){ | ||
JSONArray imageArr = tourApiService.getDetailImages(contentId); | ||
if(imageArr == null) | ||
spot.setImageUrl(""); | ||
if(!(imageArr == null)) { | ||
JSONObject detailImage = (JSONObject) imageArr.get(0); | ||
spot.setImageUrl((String) detailImage.get("originimgurl")); | ||
} | ||
log.info("addImage URL = " + spot.getImageUrl()); | ||
} | ||
} | ||
|
||
|
@@ -164,15 +244,16 @@ public ResponseEntity<StatusResponse> responseDetailInfo(Object commonDetail, Ob | |
|
||
//이미지 | ||
List<String> imageList = new ArrayList<>(); | ||
if(!spot.getImageUrl().isEmpty()) | ||
imageList.add(spot.getImageUrl()); | ||
if(!(imageArr == null)) { | ||
for (Object object : imageArr) { | ||
JSONObject item = (JSONObject) object; | ||
String imageUrl = (String) item.get("originimgurl"); | ||
imageList.add(imageUrl); | ||
} | ||
} | ||
if(imageList.isEmpty() && !spot.getImageUrl().isEmpty()) { | ||
imageList.add(spot.getImageUrl()); | ||
} | ||
|
||
//상세 정보 | ||
JSONObject additionalInfo = (JSONObject) detailInfo; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 코드는 SpotService 클래스의 일부분입니다. 아래는 몇 가지 개선 제안과 버그 확인 사항입니다:
위의 내용을 고려하여 코드 리뷰를 진행할 수 있습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 코드 리뷰 요청을 받았습니다. 다음은 해당 코드 패치의 리뷰입니다:
이상이 코드 리뷰입니다. 해당 패치에서 발견된 각 위험한 버그 및 개선 사항에 대해 설명하였습니다. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,20 +21,18 @@ public class TourApiService { | |
|
||
@Value("${tourapi.secret-key}") | ||
private String SECRET_KEY; | ||
private final String DEFAULT_QUERY_PARAM = "&MobileOS=ETC&MobileApp=Kuddy&_type=json"; | ||
|
||
private static final String BASE_URL = "https://apis.data.go.kr/B551011/EngService1/"; | ||
|
||
//카테고리별로 20개씩 조회 | ||
public JSONArray getApiDataList(int page, int category) { | ||
|
||
public JSONArray getSyncList(int page, int size, String modifiedTime) { | ||
try { | ||
URL url = new URL(BASE_URL + "areaBasedList1?numOfRows=20&pageNo=" + | ||
page + | ||
"&MobileOS=ETC&MobileApp=Kuddy&_type=json&listYN=Y&arrange=A&contentTypeId=" + | ||
category + | ||
"&areaCode=1&serviceKey=" | ||
URL url = new URL(BASE_URL + "areaBasedSyncList1?numOfRows=" + | ||
size + "&pageNo=" + page + | ||
DEFAULT_QUERY_PARAM + "&listYN=Y&arrange=C&areaCode=1&modifiedtime=" + | ||
modifiedTime + | ||
"&serviceKey=" | ||
+ SECRET_KEY); | ||
|
||
JSONObject items = (JSONObject) extractBody(url).get("items"); | ||
JSONArray spotArr = (JSONArray) items.get("item"); | ||
|
||
|
@@ -46,34 +44,15 @@ public JSONArray getApiDataList(int page, int category) { | |
} | ||
} | ||
|
||
//현재 위치 기반으로 2km 반경 관광지 20개씩 조회 | ||
public JSONObject getLocationBasedApi(int page, int size, String mapX, String mapY) { | ||
|
||
try { | ||
URL url = new URL(BASE_URL + "locationBasedList1?numOfRows=" + | ||
size + "&pageNo=" + page + | ||
"&MobileOS=ETC&MobileApp=Kuddy&_type=json&listYN=Y&mapX=" + | ||
mapX + "&mapY=" + mapY + | ||
"&radius=2000&serviceKey=" | ||
+ SECRET_KEY); | ||
|
||
return extractBody(url); | ||
|
||
} catch(Exception e) { | ||
e.printStackTrace(); | ||
throw new TourApiExeption(); | ||
} | ||
} | ||
|
||
//각 관광지 공통 정보 조회 | ||
public Object getCommonDetail(Spot spot) { | ||
public Object getCommonDetail(String category, Long contentId) { | ||
|
||
try { | ||
URL url = new URL(BASE_URL + "detailCommon1?contentTypeId=" + | ||
spot.getCategory().getCode() + | ||
category + | ||
"&contentId=" + | ||
spot.getContentId() + | ||
"&MobileOS=ETC&MobileApp=Kuddy&defaultYN=Y&addrinfoYN=Y&overviewYN=Y&_type=json&ServiceKey=" | ||
contentId + | ||
DEFAULT_QUERY_PARAM + "&defaultYN=Y&addrinfoYN=Y&overviewYN=Y&ServiceKey=" | ||
+ SECRET_KEY); | ||
|
||
JSONObject items = (JSONObject) extractBody(url).get("items"); | ||
|
@@ -92,11 +71,11 @@ public Object getCommonDetail(Spot spot) { | |
public Object getDetailInfo(Spot spot) { | ||
|
||
try { | ||
URL url = new URL(BASE_URL + "detailIntro1?MobileOS=ETC&MobileApp=Kuddy&contentId=" + | ||
spot.getContentId() + | ||
URL url = new URL(BASE_URL + "detailIntro1?contentId=" + | ||
spot.getContentId() + DEFAULT_QUERY_PARAM + | ||
"&contentTypeId=" + | ||
spot.getCategory().getCode() + | ||
"&_type=json&serviceKey=" | ||
"&serviceKey=" | ||
+ SECRET_KEY); | ||
|
||
JSONObject items = (JSONObject) extractBody(url).get("items"); | ||
|
@@ -114,8 +93,8 @@ public Object getDetailInfo(Spot spot) { | |
//이미지 정보 조회 API | ||
public JSONArray getDetailImages(Long contentId) { | ||
try { | ||
URL url = new URL(BASE_URL + "detailImage1?MobileOS=ETC&MobileApp=Kuddy&_type=json&contentId=" + | ||
contentId + | ||
URL url = new URL(BASE_URL + "detailImage1?contentId=" + | ||
contentId + DEFAULT_QUERY_PARAM + | ||
"&serviceKey=" | ||
+ SECRET_KEY); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아래는 코드 패치입니다. 코드 리뷰를 간략하게 도와드리겠습니다. 버그의 위험과/또는 개선 제안을 환영합니다.
주의할 사항:
특정 버그를 식별하지 못하였으며, 리뷰를 통해 기능 및 성능 개선을 고려할 수 있을 것입니다. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,12 @@ | |
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public enum Category { | ||
|
@@ -15,4 +21,15 @@ public enum Category { | |
|
||
private final String code; | ||
private final String type; | ||
|
||
private static final Map<String, String> CATEGORY_CODE_MAP = Collections.unmodifiableMap( | ||
Stream.of(values()).collect(Collectors.toMap(Category::getCode, Category::name))); | ||
|
||
public static Category valueOfCode(final String code) { | ||
return Category.valueOf(CATEGORY_CODE_MAP.get(code)); | ||
} | ||
|
||
public static boolean hasValue(String code) { | ||
return Arrays.stream(Category.values()).anyMatch(v -> v.code.equals(code)); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 주어진 코드 패치에 대해 간단한 코드 리뷰를 도와드리겠습니다. 버그 위험 및 개선 제안을 환영합니다.
위의 내용은 주어진 코드 패치에 대한 간단한 개선 사항입니다. 다른 문제가 있는지 자세히 파악하려면 전체 소스 코드 및 프로그램 요구 사항을 확인해야 합니다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,4 +46,11 @@ public enum District { | |
public static District of(final String area) { | ||
return District.valueOf(DISTRICT_MAP.get(area)); | ||
} | ||
|
||
private static final Map<String, String> DISTRICT_CODE_MAP = Collections.unmodifiableMap( | ||
Stream.of(values()).collect(Collectors.toMap(District::getCode, District::name))); | ||
|
||
public static District valueOfCode(final String code) { | ||
return District.valueOf(DISTRICT_CODE_MAP.get(code)); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 코드 패치에 대해 간단한 코드 리뷰를 도와드리겠습니다.
다른 사소한 문제나 개선 포인트가 있다면 알려주세요. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드 리뷰를 해드리겠습니다.
해당 코드의 문제점이나 개선 제안은 다음과 같습니다.
if(heartRepository.findByMemberAndSpot(member, spot).isPresent())
)에서else
분기가 제거되었습니다. 이로 인해 이미 좋아요한 경우와 좋아요하지 않은 경우 구분이 없어지게 됩니다. 예외 처리 부분을 잘 검토해보세요.heartRepository.save()
,spot.likeSpot()
)이 항상 실행됩니다. 이미 좋아요한 경우라도 무조건 처리되므로, 중복 저장 및 카운트 증가 등의 부작용이 있을 수 있습니다. 불필요한 DB 작업을 줄일 수 있는 방법을 고려해보세요.StatusResponse
객체 생성 부분은 동일한 로직이throw
전과throw
후에 모두 존재합니다. 중복된 코드를 제거하고 한 곳에서 생성하여 사용하도록 개선해보세요.이상이 코드 리뷰에 대한 제안사항입니다. 해당 부분들을 고려하여 코드를 수정하면 더욱 개선된 결과를 얻을 수 있을 것입니다.