Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.server.running_handai.domain.course.dto;

public record CourseTrackPointDto(
long courseId,
double startPointLat,
double startPointLon,
double endPointLat,
double endPointLon
) {
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.server.running_handai.domain.course.repository;

import com.server.running_handai.domain.course.dto.CourseTrackPointDto;
import com.server.running_handai.domain.course.entity.Course;
import com.server.running_handai.domain.course.entity.TrackPoint;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface TrackPointRepository extends JpaRepository<TrackPoint, Long> {

Expand Down Expand Up @@ -32,4 +34,23 @@ public interface TrackPointRepository extends JpaRepository<TrackPoint, Long> {
* 코스 ID 목록으로 모든 트랙포인트를 한 번에 조회
*/
List<TrackPoint> findByCourseIdInOrderBySequenceAsc(List<Long> courseIds);

/**
* DB에 저장된 모든 코스별 시작점, 도착점 조회
*/
@Query(
value = "SELECT " +
" c.course_id AS courseId, " +
" stp.lat AS startPointLat, " +
" stp.lon AS startPointLon, " +
" etp.lat AS endPointLat, " +
" etp.lon AS endPointLon " +
"FROM course c " +
"JOIN track_point stp ON c.course_id = stp.course_id AND stp.sequence = 1 " +
"JOIN track_point etp ON c.course_id = etp.course_id AND etp.sequence = (" +
"SELECT MAX(mtp.sequence) FROM track_point mtp WHERE mtp.course_id = c.course_id" +
")",
nativeQuery = true
)
List<CourseTrackPointDto> findAllCourseTrackPoint();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.server.running_handai.domain.spot.client;

import com.server.running_handai.domain.spot.dto.SpotSyncApiResponseDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

@Slf4j
@Component
@RequiredArgsConstructor
public class SpotSyncApiClient {
private final WebClient webClient;

@Value("${external.api.spot.base-url}")
private String baseUrl;

@Value("${external.api.spot.service-key}")
private String serviceKey;

/**
* [국문 관광정보] 관광정보 동기화 목록 조회 API를 요청합니다.
* 요청한 수정일을 기준으로 해당 날짜에 변경된 콘텐츠가 있는 경우 해당 콘텐츠의 정보를 응답합니다.
*
* @param areaCode 지역 코드
* @return SpotSyncApiResponseDto
*/
public SpotSyncApiResponseDto fetchSpotSyncData(int areaCode, String date) {
// URL 생성
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(baseUrl)
.path("/areaBasedSyncList2")
.queryParam("MobileOS", "ETC")
.queryParam("MobileApp", "runninghandai")
.queryParam("_type", "json")
.queryParam("areaCode", String.valueOf(areaCode))
.queryParam("modifiedtime", date)
.queryParam("serviceKey", serviceKey);

URI uri = builder.build(true).toUri();

// API 호출
return webClient.get()
.uri(uri)
.retrieve()
.bodyToMono(SpotSyncApiResponseDto.class)
.block();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import com.server.running_handai.global.response.ResponseCode;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

@RestController
@RequestMapping("/api/admin/courses")
Expand All @@ -21,4 +21,23 @@ public ResponseEntity<CommonResponse<?>> updateSpots(@PathVariable Long courseId
spotDataService.updateSpots(courseId);
return ResponseEntity.ok().body(CommonResponse.success(ResponseCode.SUCCESS, null));
}

@PostMapping("/sync-spots/date")
public ResponseEntity<CommonResponse<?>> syncSpotsByDate(@RequestParam(required = false) String date) {
String targetDate = date;
if (targetDate == null) {
// 호출 기준 전날 날짜 계산 (YYYYMMDD)
targetDate = LocalDate.now()
.minusDays(1)
.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
}
spotDataService.syncSpotsByDate(targetDate);
return ResponseEntity.ok().body(CommonResponse.success(ResponseCode.SUCCESS, null));
}

@PostMapping("/sync-spots/location")
public ResponseEntity<CommonResponse<?>> syncSpotsByLocation() {
spotDataService.syncSpotsByLocation();
return ResponseEntity.ok().body(CommonResponse.success(ResponseCode.SUCCESS, null));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.server.running_handai.domain.spot.dto;

import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;

public record SpotExternalIdsDto(
Long courseId,
String externalIds
) {
public Set<String> getSpotExternalIds() {
if (externalIds == null || externalIds.isEmpty()) {
return Collections.emptySet();
}

return Arrays.stream(externalIds.split(","))
.map(String::trim)
.filter(externalId -> !externalId.isEmpty())
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.server.running_handai.domain.spot.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.Hidden;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.List;

@Hidden
@Getter
@ToString
@NoArgsConstructor
public class SpotSyncApiResponseDto {
@JsonProperty("response")
private SpotSyncApiResponseDto.Response response;

@Getter
@ToString
@NoArgsConstructor
public static class Response {
@JsonProperty("header")
private SpotSyncApiResponseDto.Header header;

@JsonProperty("body")
private SpotSyncApiResponseDto.Body body;
}

@Getter
@ToString
@NoArgsConstructor
public static class Header {
@JsonProperty("resultCode")
private String resultCode;

@JsonProperty("resultMsg")
private String resultMsg;
}

@Getter
@ToString
@NoArgsConstructor
public static class Body {
@JsonProperty("items")
private SpotSyncApiResponseDto.Items items;

@JsonProperty("numOfRows")
private int numOfRows;

@JsonProperty("pageNo")
private int pageNo;

@JsonProperty("totalCount")
private int totalCount;
}

@Getter
@ToString
@NoArgsConstructor
public static class Items {
@JsonProperty("item")
private List<SpotSyncApiResponseDto.Item> itemList;
}

/**
* [국문 관광정보] 관광정보 동기화 목록 조회
*/
@Getter
@ToString
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Item {

@JsonProperty("contentid")
private String spotExternalId; // 장소 고유번호 (Spot.externalId)

@JsonProperty("showflag")
private String spotShowflag; // 콘텐츠 표출여부 (1: 표출, 0: 비표출)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,44 @@ public Spot(String externalId, String name, String address, String description,
this.lon = lon;
}

/**
* API 데이터와 비교하여 Spot 엔티티의 필드를 업데이트합니다.
* 변경이 발생했을 경우에만 true를 반환합니다.
*
* @param source 비교 대상이 되는, API 응답으로부터 변환된 Spot 객체
* @return 내용 변경이 있었는지 여부
*/
public boolean syncWith(Spot source) {
boolean isUpdated = false;

if (!this.name.equals(source.getName())) {
this.name = source.getName();
isUpdated = true;
}
if (!this.address.equals(source.getAddress())) {
this.address = source.getAddress();
isUpdated = true;
}
if (!this.description.equals(source.getDescription())) {
this.description = source.getDescription();
isUpdated = true;
}
if (this.spotCategory != source.getSpotCategory()) {
this.spotCategory = source.getSpotCategory();
isUpdated = true;
}
if (this.lat != source.getLat()) {
this.lat = source.getLat();
isUpdated = true;
}
if (this.lon != source.getLon()) {
this.lon = source.getLon();
isUpdated = true;
}

return isUpdated;
}

// ==== 연관관계 편의 메서드 ==== //
public void setSpotImage(SpotImage spotImage) {
this.spotImage = spotImage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,20 @@
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.Set;

public interface CourseSpotRepository extends JpaRepository<CourseSpot, Long> {
/**
* 특정 코스에 연결된 모든 장소의 연관관계를 모두 삭제합니다.
*/
@Modifying
@Query("DELETE FROM CourseSpot cs WHERE cs.course.id = :courseId")
void deleteByCourseId(@Param("courseId") Long courseId);

/**
* 특정 코스에 연결된 장소 중 External Id의 목록에 포함된 장소의 연관관계만 삭제합니다.
*/
@Modifying
@Query("DELETE FROM CourseSpot cs WHERE cs.course.id = :courseId AND cs.spot.externalId IN :externalIds")
void deleteByCourseIdAndSpotExternalIdIn(@Param("courseId") Long courseId, @Param("externalIds") Set<String> externalIds);
}
Loading