init
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
package com.ktds.hi.store.biz.usecase.out;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 캐시 포트 인터페이스
|
||||
* 캐시 기능을 정의
|
||||
*/
|
||||
public interface CachePort {
|
||||
|
||||
/**
|
||||
* 캐시에서 매장 데이터 조회
|
||||
*/
|
||||
Optional<Object> getStoreCache(String key);
|
||||
|
||||
/**
|
||||
* 캐시에 매장 데이터 저장
|
||||
*/
|
||||
void putStoreCache(String key, Object value, Duration ttl);
|
||||
|
||||
/**
|
||||
* 캐시 무효화
|
||||
*/
|
||||
void invalidateStoreCache(Long storeId);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ktds.hi.store.biz.usecase.out;
|
||||
|
||||
import com.ktds.hi.store.biz.domain.Store;
|
||||
|
||||
/**
|
||||
* 이벤트 포트 인터페이스
|
||||
* 이벤트 발행 기능을 정의
|
||||
*/
|
||||
public interface EventPort {
|
||||
|
||||
/**
|
||||
* 매장 생성 이벤트 발행
|
||||
*/
|
||||
void publishStoreCreatedEvent(Store store);
|
||||
|
||||
/**
|
||||
* 매장 수정 이벤트 발행
|
||||
*/
|
||||
void publishStoreUpdatedEvent(Store store);
|
||||
|
||||
/**
|
||||
* 매장 삭제 이벤트 발행
|
||||
*/
|
||||
void publishStoreDeletedEvent(Long storeId);
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.ktds.hi.store.infra.controller;
|
||||
|
||||
import com.ktds.hi.store.biz.usecase.in.ExternalIntegrationUseCase;
|
||||
import com.ktds.hi.store.infra.dto.ExternalSyncRequest;
|
||||
import com.ktds.hi.store.infra.dto.ExternalSyncResponse;
|
||||
import com.ktds.hi.store.infra.dto.ExternalConnectRequest;
|
||||
import com.ktds.hi.store.infra.dto.ExternalConnectResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 외부 연동 컨트롤러 클래스
|
||||
* 외부 플랫폼 연동 관련 API를 제공
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/external")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "외부연동 API", description = "외부 플랫폼 연동 및 동기화 관련 API")
|
||||
public class ExternalIntegrationController {
|
||||
|
||||
private final ExternalIntegrationUseCase externalIntegrationUseCase;
|
||||
|
||||
/**
|
||||
* 외부 플랫폼 리뷰 동기화 API
|
||||
*/
|
||||
@PostMapping("/stores/{storeId}/sync-reviews")
|
||||
@Operation(summary = "외부 플랫폼 리뷰 동기화", description = "외부 플랫폼의 리뷰를 동기화합니다.")
|
||||
public ResponseEntity<ExternalSyncResponse> syncReviews(
|
||||
@PathVariable Long storeId,
|
||||
@Valid @RequestBody ExternalSyncRequest request) {
|
||||
ExternalSyncResponse response = externalIntegrationUseCase.syncReviews(storeId, request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 외부 플랫폼 계정 연동 API
|
||||
*/
|
||||
@PostMapping("/stores/{storeId}/connect")
|
||||
@Operation(summary = "외부 플랫폼 계정 연동", description = "외부 플랫폼 계정을 연동합니다.")
|
||||
public ResponseEntity<ExternalConnectResponse> connectPlatform(
|
||||
@PathVariable Long storeId,
|
||||
@Valid @RequestBody ExternalConnectRequest request) {
|
||||
ExternalConnectResponse response = externalIntegrationUseCase.connectPlatform(storeId, request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ktds.hi.store.infra.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 외부 플랫폼 계정 연동 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ExternalConnectResponse {
|
||||
|
||||
private Boolean success;
|
||||
private String message;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ktds.hi.store.infra.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 외부 플랫폼 동기화 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ExternalSyncResponse {
|
||||
|
||||
private Boolean success;
|
||||
private String message;
|
||||
private Integer syncedCount;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ktds.hi.store.infra.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 메뉴 삭제 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MenuDeleteResponse {
|
||||
|
||||
private Boolean success;
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ktds.hi.store.infra.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 메뉴 저장 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MenuSaveResponse {
|
||||
|
||||
private Boolean success;
|
||||
private String message;
|
||||
private Long menuId;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ktds.hi.store.infra.dto;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 메뉴 수정 요청 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MenuUpdateRequest {
|
||||
|
||||
@NotBlank(message = "메뉴명은 필수입니다.")
|
||||
@Size(max = 100, message = "메뉴명은 100자를 초과할 수 없습니다.")
|
||||
private String menuName;
|
||||
|
||||
@Size(max = 500, message = "설명은 500자를 초과할 수 없습니다.")
|
||||
private String description;
|
||||
|
||||
@NotNull(message = "가격은 필수입니다.")
|
||||
@Min(value = 1, message = "가격은 0보다 큰 값이어야 합니다.")
|
||||
private Integer price;
|
||||
|
||||
private String category;
|
||||
private String imageUrl;
|
||||
private Boolean isAvailable;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ktds.hi.store.infra.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 메뉴 수정 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MenuUpdateResponse {
|
||||
|
||||
private Boolean success;
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ktds.hi.store.infra.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 매장 삭제 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class StoreDeleteResponse {
|
||||
|
||||
private Boolean success;
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ktds.hi.store.infra.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 매장 수정 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class StoreUpdateResponse {
|
||||
|
||||
private Boolean success;
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.ktds.hi.store.infra.gateway;
|
||||
|
||||
import com.ktds.hi.store.biz.usecase.out.CachePort;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 캐시 어댑터 클래스
|
||||
* Cache Port를 구현하여 Redis 캐시 기능을 제공
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class CacheAdapter implements CachePort {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Override
|
||||
public Optional<Object> getStoreCache(String key) {
|
||||
try {
|
||||
Object value = redisTemplate.opsForValue().get(key);
|
||||
return Optional.ofNullable(value);
|
||||
} catch (Exception e) {
|
||||
log.error("매장 캐시 조회 실패: key={}, error={}", key, e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putStoreCache(String key, Object value, Duration ttl) {
|
||||
try {
|
||||
redisTemplate.opsForValue().set(key, value, ttl);
|
||||
log.debug("매장 캐시 저장 완료: key={}, ttl={}분", key, ttl.toMinutes());
|
||||
} catch (Exception e) {
|
||||
log.error("매장 캐시 저장 실패: key={}, error={}", key, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateStoreCache(Long storeId) {
|
||||
try {
|
||||
// 매장 관련 모든 캐시 키 패턴 삭제
|
||||
String storeDetailKey = "store_detail:" + storeId;
|
||||
String myStoresKey = "my_stores:*";
|
||||
|
||||
redisTemplate.delete(storeDetailKey);
|
||||
|
||||
log.debug("매장 캐시 무효화 완료: storeId={}", storeId);
|
||||
} catch (Exception e) {
|
||||
log.error("매장 캐시 무효화 실패: storeId={}, error={}", storeId, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.ktds.hi.store.infra.gateway;
|
||||
|
||||
import com.ktds.hi.store.biz.domain.Store;
|
||||
import com.ktds.hi.store.biz.usecase.out.EventPort;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 이벤트 어댑터 클래스
|
||||
* Event Port를 구현하여 이벤트 발행 기능을 제공
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class EventAdapter implements EventPort {
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@Override
|
||||
public void publishStoreCreatedEvent(Store store) {
|
||||
log.info("매장 생성 이벤트 발행: storeId={}", store.getId());
|
||||
|
||||
try {
|
||||
StoreCreatedEvent event = new StoreCreatedEvent(store);
|
||||
eventPublisher.publishEvent(event);
|
||||
|
||||
log.info("매장 생성 이벤트 발행 완료: storeId={}", store.getId());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("매장 생성 이벤트 발행 실패: storeId={}, error={}", store.getId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishStoreUpdatedEvent(Store store) {
|
||||
log.info("매장 수정 이벤트 발행: storeId={}", store.getId());
|
||||
|
||||
try {
|
||||
StoreUpdatedEvent event = new StoreUpdatedEvent(store);
|
||||
eventPublisher.publishEvent(event);
|
||||
|
||||
log.info("매장 수정 이벤트 발행 완료: storeId={}", store.getId());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("매장 수정 이벤트 발행 실패: storeId={}, error={}", store.getId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishStoreDeletedEvent(Long storeId) {
|
||||
log.info("매장 삭제 이벤트 발행: storeId={}", storeId);
|
||||
|
||||
try {
|
||||
StoreDeletedEvent event = new StoreDeletedEvent(storeId);
|
||||
eventPublisher.publishEvent(event);
|
||||
|
||||
log.info("매장 삭제 이벤트 발행 완료: storeId={}", storeId);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("매장 삭제 이벤트 발행 실패: storeId={}, error={}", storeId, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 매장 이벤트 클래스들
|
||||
*/
|
||||
public static class StoreCreatedEvent {
|
||||
private final Store store;
|
||||
|
||||
public StoreCreatedEvent(Store store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public Store getStore() {
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StoreUpdatedEvent {
|
||||
private final Store store;
|
||||
|
||||
public StoreUpdatedEvent(Store store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public Store getStore() {
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StoreDeletedEvent {
|
||||
private final Long storeId;
|
||||
|
||||
public StoreDeletedEvent(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package com.ktds.hi.store.infra.gateway;
|
||||
|
||||
import com.ktds.hi.store.biz.usecase.out.ExternalPlatformPort;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 외부 플랫폼 어댑터 클래스
|
||||
* External Platform Port를 구현하여 외부 API 연동 기능을 제공
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ExternalPlatformAdapter implements ExternalPlatformPort {
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
@Value("${external-api.naver.client-id:}")
|
||||
private String naverClientId;
|
||||
|
||||
@Value("${external-api.naver.client-secret:}")
|
||||
private String naverClientSecret;
|
||||
|
||||
@Value("${external-api.kakao.api-key:}")
|
||||
private String kakaoApiKey;
|
||||
|
||||
@Value("${external-api.google.api-key:}")
|
||||
private String googleApiKey;
|
||||
|
||||
@Value("${external-api.hiorder.api-key:}")
|
||||
private String hiorderApiKey;
|
||||
|
||||
@Override
|
||||
public int syncNaverReviews(Long storeId, String externalStoreId) {
|
||||
log.info("네이버 리뷰 동기화 시작: storeId={}, externalStoreId={}", storeId, externalStoreId);
|
||||
|
||||
try {
|
||||
// 네이버 API 호출 (Mock)
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("X-Naver-Client-Id", naverClientId);
|
||||
headers.set("X-Naver-Client-Secret", naverClientSecret);
|
||||
|
||||
// 실제 API 호출 로직
|
||||
// ResponseEntity<Map> response = restTemplate.exchange(...);
|
||||
|
||||
// Mock 응답
|
||||
int syncedCount = 15; // Mock 데이터
|
||||
|
||||
log.info("네이버 리뷰 동기화 완료: storeId={}, syncedCount={}", storeId, syncedCount);
|
||||
return syncedCount;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("네이버 리뷰 동기화 실패: storeId={}, error={}", storeId, e.getMessage(), e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int syncKakaoReviews(Long storeId, String externalStoreId) {
|
||||
log.info("카카오 리뷰 동기화 시작: storeId={}, externalStoreId={}", storeId, externalStoreId);
|
||||
|
||||
try {
|
||||
// 카카오 API 호출 (Mock)
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", "KakaoAK " + kakaoApiKey);
|
||||
|
||||
// Mock 응답
|
||||
int syncedCount = 12;
|
||||
|
||||
log.info("카카오 리뷰 동기화 완료: storeId={}, syncedCount={}", storeId, syncedCount);
|
||||
return syncedCount;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("카카오 리뷰 동기화 실패: storeId={}, error={}", storeId, e.getMessage(), e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int syncGoogleReviews(Long storeId, String externalStoreId) {
|
||||
log.info("구글 리뷰 동기화 시작: storeId={}, externalStoreId={}", storeId, externalStoreId);
|
||||
|
||||
try {
|
||||
// 구글 Places API 호출 (Mock)
|
||||
String url = "https://maps.googleapis.com/maps/api/place/details/json?place_id=" +
|
||||
externalStoreId + "&fields=reviews&key=" + googleApiKey;
|
||||
|
||||
// Mock 응답
|
||||
int syncedCount = 20;
|
||||
|
||||
log.info("구글 리뷰 동기화 완료: storeId={}, syncedCount={}", storeId, syncedCount);
|
||||
return syncedCount;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("구글 리뷰 동기화 실패: storeId={}, error={}", storeId, e.getMessage(), e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int syncHiorderReviews(Long storeId, String externalStoreId) {
|
||||
log.info("하이오더 리뷰 동기화 시작: storeId={}, externalStoreId={}", storeId, externalStoreId);
|
||||
|
||||
try {
|
||||
// 하이오더 API 호출 (Mock)
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", "Bearer " + hiorderApiKey);
|
||||
|
||||
// Mock 응답
|
||||
int syncedCount = 8;
|
||||
|
||||
log.info("하이오더 리뷰 동기화 완료: storeId={}, syncedCount={}", storeId, syncedCount);
|
||||
return syncedCount;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("하이오더 리뷰 동기화 실패: storeId={}, error={}", storeId, e.getMessage(), e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectNaverAccount(Long storeId, String username, String password) {
|
||||
log.info("네이버 계정 연동 시작: storeId={}, username={}", storeId, username);
|
||||
|
||||
try {
|
||||
// 네이버 계정 인증 로직 (Mock)
|
||||
// 실제로는 OAuth2 플로우나 ID/PW 인증
|
||||
|
||||
// Mock 성공
|
||||
boolean connected = true;
|
||||
|
||||
if (connected) {
|
||||
// 연동 정보 저장
|
||||
saveExternalConnection(storeId, "NAVER", username);
|
||||
}
|
||||
|
||||
log.info("네이버 계정 연동 완료: storeId={}, connected={}", storeId, connected);
|
||||
return connected;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("네이버 계정 연동 실패: storeId={}, error={}", storeId, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectKakaoAccount(Long storeId, String username, String password) {
|
||||
log.info("카카오 계정 연동 시작: storeId={}, username={}", storeId, username);
|
||||
|
||||
try {
|
||||
// 카카오 계정 인증 로직 (Mock)
|
||||
boolean connected = true;
|
||||
|
||||
if (connected) {
|
||||
saveExternalConnection(storeId, "KAKAO", username);
|
||||
}
|
||||
|
||||
log.info("카카오 계정 연동 완료: storeId={}, connected={}", storeId, connected);
|
||||
return connected;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("카카오 계정 연동 실패: storeId={}, error={}", storeId, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectGoogleAccount(Long storeId, String username, String password) {
|
||||
log.info("구글 계정 연동 시작: storeId={}, username={}", storeId, username);
|
||||
|
||||
try {
|
||||
// 구글 계정 인증 로직 (Mock)
|
||||
boolean connected = true;
|
||||
|
||||
if (connected) {
|
||||
saveExternalConnection(storeId, "GOOGLE", username);
|
||||
}
|
||||
|
||||
log.info("구글 계정 연동 완료: storeId={}, connected={}", storeId, connected);
|
||||
return connected;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("구글 계정 연동 실패: storeId={}, error={}", storeId, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectHiorderAccount(Long storeId, String username, String password) {
|
||||
log.info("하이오더 계정 연동 시작: storeId={}, username={}", storeId, username);
|
||||
|
||||
try {
|
||||
// 하이오더 계정 인증 로직 (Mock)
|
||||
boolean connected = true;
|
||||
|
||||
if (connected) {
|
||||
saveExternalConnection(storeId, "HIORDER", username);
|
||||
}
|
||||
|
||||
log.info("하이오더 계정 연동 완료: storeId={}, connected={}", storeId, connected);
|
||||
return connected;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("하이오더 계정 연동 실패: storeId={}, error={}", storeId, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 외부 연동 정보 저장
|
||||
*/
|
||||
private void saveExternalConnection(Long storeId, String platform, String username) {
|
||||
// 실제로는 ExternalPlatformEntity에 연동 정보 저장
|
||||
log.info("외부 연동 정보 저장: storeId={}, platform={}, username={}", storeId, platform, username);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.ktds.hi.store.infra.gateway;
|
||||
|
||||
import com.ktds.hi.store.biz.domain.Menu;
|
||||
import com.ktds.hi.store.biz.usecase.out.MenuRepositoryPort;
|
||||
import com.ktds.hi.store.infra.gateway.entity.MenuEntity;
|
||||
import com.ktds.hi.store.infra.gateway.repository.MenuJpaRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 메뉴 리포지토리 어댑터 클래스
|
||||
* Menu Repository Port를 구현하여 데이터 영속성 기능을 제공
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MenuRepositoryAdapter implements MenuRepositoryPort {
|
||||
|
||||
private final MenuJpaRepository menuJpaRepository;
|
||||
|
||||
@Override
|
||||
public List<Menu> findMenusByStoreId(Long storeId) {
|
||||
return menuJpaRepository.findByStoreId(storeId)
|
||||
.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Menu> findMenuById(Long menuId) {
|
||||
return menuJpaRepository.findById(menuId)
|
||||
.map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Menu saveMenu(Menu menu) {
|
||||
MenuEntity entity = toEntity(menu);
|
||||
MenuEntity saved = menuJpaRepository.save(entity);
|
||||
return toDomain(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMenu(Long menuId) {
|
||||
menuJpaRepository.deleteById(menuId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity를 Domain으로 변환
|
||||
*/
|
||||
private Menu toDomain(MenuEntity entity) {
|
||||
return Menu.builder()
|
||||
.id(entity.getId())
|
||||
.storeId(entity.getStoreId())
|
||||
.menuName(entity.getMenuName())
|
||||
.description(entity.getDescription())
|
||||
.price(entity.getPrice())
|
||||
.category(entity.getCategory())
|
||||
.imageUrl(entity.getImageUrl())
|
||||
.isAvailable(entity.getIsAvailable())
|
||||
.createdAt(entity.getCreatedAt())
|
||||
.updatedAt(entity.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain을 Entity로 변환
|
||||
*/
|
||||
private MenuEntity toEntity(Menu domain) {
|
||||
return MenuEntity.builder()
|
||||
.id(domain.getId())
|
||||
.storeId(domain.getStoreId())
|
||||
.menuName(domain.getMenuName())
|
||||
.description(domain.getDescription())
|
||||
.price(domain.getPrice())
|
||||
.category(domain.getCategory())
|
||||
.imageUrl(domain.getImageUrl())
|
||||
.isAvailable(domain.getIsAvailable())
|
||||
.createdAt(domain.getCreatedAt())
|
||||
.updatedAt(domain.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.ktds.hi.store.infra.gateway;
|
||||
|
||||
import com.ktds.hi.store.biz.domain.Store;
|
||||
import com.ktds.hi.store.biz.usecase.out.StoreRepositoryPort;
|
||||
import com.ktds.hi.store.infra.gateway.entity.StoreEntity;
|
||||
import com.ktds.hi.store.infra.gateway.repository.StoreJpaRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 매장 리포지토리 어댑터 클래스
|
||||
* Store Repository Port를 구현하여 데이터 영속성 기능을 제공
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class StoreRepositoryAdapter implements StoreRepositoryPort {
|
||||
|
||||
private final StoreJpaRepository storeJpaRepository;
|
||||
|
||||
@Override
|
||||
public List<Store> findStoresByOwnerId(Long ownerId) {
|
||||
return storeJpaRepository.findByOwnerId(ownerId)
|
||||
.stream()
|
||||
.map(this::toDomain)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Store> findStoreById(Long storeId) {
|
||||
return storeJpaRepository.findById(storeId)
|
||||
.map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Store> findStoreByIdAndOwnerId(Long storeId, Long ownerId) {
|
||||
return storeJpaRepository.findByIdAndOwnerId(storeId, ownerId)
|
||||
.map(this::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Store saveStore(Store store) {
|
||||
StoreEntity entity = toEntity(store);
|
||||
StoreEntity saved = storeJpaRepository.save(entity);
|
||||
return toDomain(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStore(Long storeId) {
|
||||
storeJpaRepository.deleteById(storeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity를 Domain으로 변환
|
||||
*/
|
||||
private Store toDomain(StoreEntity entity) {
|
||||
return Store.builder()
|
||||
.id(entity.getId())
|
||||
.ownerId(entity.getOwnerId())
|
||||
.storeName(entity.getStoreName())
|
||||
.address(entity.getAddress())
|
||||
.latitude(entity.getLatitude())
|
||||
.longitude(entity.getLongitude())
|
||||
.category(entity.getCategory())
|
||||
.description(entity.getDescription())
|
||||
.phone(entity.getPhone())
|
||||
.operatingHours(entity.getOperatingHours())
|
||||
.tags(entity.getTagsJson() != null ? parseTagsJson(entity.getTagsJson()) : List.of())
|
||||
.status(entity.getStatus())
|
||||
.rating(entity.getRating())
|
||||
.reviewCount(entity.getReviewCount())
|
||||
.imageUrl(entity.getImageUrl())
|
||||
.createdAt(entity.getCreatedAt())
|
||||
.updatedAt(entity.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain을 Entity로 변환
|
||||
*/
|
||||
private StoreEntity toEntity(Store domain) {
|
||||
return StoreEntity.builder()
|
||||
.id(domain.getId())
|
||||
.ownerId(domain.getOwnerId())
|
||||
.storeName(domain.getStoreName())
|
||||
.address(domain.getAddress())
|
||||
.latitude(domain.getLatitude())
|
||||
.longitude(domain.getLongitude())
|
||||
.category(domain.getCategory())
|
||||
.description(domain.getDescription())
|
||||
.phone(domain.getPhone())
|
||||
.operatingHours(domain.getOperatingHours())
|
||||
.tagsJson(domain.getTags() != null ? String.join(",", domain.getTags()) : "")
|
||||
.status(domain.getStatus())
|
||||
.rating(domain.getRating())
|
||||
.reviewCount(domain.getReviewCount())
|
||||
.imageUrl(domain.getImageUrl())
|
||||
.createdAt(domain.getCreatedAt())
|
||||
.updatedAt(domain.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 태그를 List로 파싱
|
||||
*/
|
||||
private List<String> parseTagsJson(String tagsJson) {
|
||||
if (tagsJson == null || tagsJson.trim().isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return Arrays.asList(tagsJson.split(","));
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.ktds.hi.store.infra.gateway;
|
||||
|
||||
import com.ktds.hi.store.biz.domain.Store;
|
||||
import com.ktds.hi.store.biz.usecase.out.StoreRepositoryPort;
|
||||
import com.ktds.hi.store.infra.gateway.entity.StoreEntity;
|
||||
import com.ktds.hi.store.infra.gateway.repository.StoreJpaRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays; // 추가
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.ktds.hi.store.infra.gateway.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 메뉴 엔티티 클래스
|
||||
* 데이터베이스 menus 테이블과 매핑되는 JPA 엔티티
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "menus")
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class MenuEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "store_id", nullable = false)
|
||||
private Long storeId;
|
||||
|
||||
@Column(name = "menu_name", nullable = false, length = 100)
|
||||
private String menuName;
|
||||
|
||||
@Column(length = 500)
|
||||
private String description;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Integer price;
|
||||
|
||||
@Column(length = 50)
|
||||
private String category;
|
||||
|
||||
@Column(name = "image_url", length = 500)
|
||||
private String imageUrl;
|
||||
|
||||
@Column(name = "is_available")
|
||||
@Builder.Default
|
||||
private Boolean isAvailable = true;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ktds.hi.store.infra.gateway.repository;
|
||||
|
||||
import com.ktds.hi.store.infra.gateway.entity.MenuEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 메뉴 JPA 리포지토리 인터페이스
|
||||
* 메뉴 데이터의 CRUD 작업을 담당
|
||||
*/
|
||||
@Repository
|
||||
public interface MenuJpaRepository extends JpaRepository<MenuEntity, Long> {
|
||||
|
||||
/**
|
||||
* 매장 ID로 이용 가능한 메뉴 목록 조회
|
||||
*/
|
||||
List<MenuEntity> findByStoreIdAndIsAvailableTrue(Long storeId);
|
||||
|
||||
/**
|
||||
* 매장 ID로 모든 메뉴 목록 조회
|
||||
*/
|
||||
List<MenuEntity> findByStoreId(Long storeId);
|
||||
|
||||
/**
|
||||
* 매장 ID로 메뉴 삭제
|
||||
*/
|
||||
void deleteByStoreId(Long storeId);
|
||||
|
||||
/**
|
||||
* 카테고리별 메뉴 조회
|
||||
*/
|
||||
List<MenuEntity> findByStoreIdAndCategoryAndIsAvailableTrue(Long storeId, String category);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
server:
|
||||
port: ${STORE_SERVICE_PORT:8082}
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: store-service
|
||||
|
||||
datasource:
|
||||
url: ${STORE_DB_URL:jdbc:postgresql://localhost:5432/hiorder_store}
|
||||
username: ${STORE_DB_USERNAME:hiorder_user}
|
||||
password: ${STORE_DB_PASSWORD:hiorder_pass}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: ${JPA_DDL_AUTO:validate}
|
||||
show-sql: ${JPA_SHOW_SQL:false}
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
|
||||
external-api:
|
||||
naver:
|
||||
client-id: ${NAVER_CLIENT_ID:}
|
||||
client-secret: ${NAVER_CLIENT_SECRET:}
|
||||
base-url: https://openapi.naver.com
|
||||
kakao:
|
||||
api-key: ${KAKAO_API_KEY:}
|
||||
base-url: https://dapi.kakao.com
|
||||
google:
|
||||
api-key: ${GOOGLE_API_KEY:}
|
||||
base-url: https://maps.googleapis.com
|
||||
hiorder:
|
||||
api-key: ${HIORDER_API_KEY:}
|
||||
base-url: ${HIORDER_BASE_URL:https://api.hiorder.com}
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
Reference in New Issue
Block a user