diff --git a/common/src/main/java/com/ktds/hi/common/security/JwtTokenProvider.java b/common/src/main/java/com/ktds/hi/common/security/JwtTokenProvider.java index 8a31172..dd3a104 100644 --- a/common/src/main/java/com/ktds/hi/common/security/JwtTokenProvider.java +++ b/common/src/main/java/com/ktds/hi/common/security/JwtTokenProvider.java @@ -1,5 +1,7 @@ package com.ktds.hi.common.security; +import jakarta.servlet.http.HttpServletRequest; +import com.ktds.hi.common.exception.BusinessException; import com.ktds.hi.common.constants.SecurityConstants; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; @@ -55,6 +57,82 @@ public class JwtTokenProvider { .verifyWith(secretKey) .build(); } + /** + * HTTP 요청에서 점주 정보 추출 + */ + public Long extractOwnerInfo(HttpServletRequest request) { + try { + // Authorization 헤더에서 토큰 추출 + String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + throw new BusinessException("UNAUTHORIZED", "인증 토큰이 필요합니다."); + } + + String token = authHeader.substring(7); // "Bearer " 제거 + + // 토큰 유효성 검증 + if (!validateToken(token)) { + throw new BusinessException("UNAUTHORIZED", "유효하지 않은 토큰입니다."); + } + + // 토큰에서 사용자 ID 추출 + String userId = getUserIdFromToken(token); + if (userId == null) { + throw new BusinessException("UNAUTHORIZED", "토큰에서 사용자 정보를 찾을 수 없습니다."); + } + + // 토큰에서 권한 정보 추출 + String roles = getRolesFromToken(token); + if (roles == null || !roles.contains("OWNER")) { + throw new BusinessException("FORBIDDEN", "점주 권한이 필요합니다."); + } + + log.debug("점주 정보 추출 완료: ownerId={}", userId); + return Long.parseLong(userId); + + } catch (NumberFormatException e) { + log.error("사용자 ID 형변환 실패: {}", e.getMessage()); + throw new BusinessException("UNAUTHORIZED", "잘못된 사용자 ID 형식입니다."); + } catch (BusinessException e) { + throw e; // 비즈니스 예외는 그대로 전파 + } catch (Exception e) { + log.error("점주 정보 추출 중 오류 발생: {}", e.getMessage(), e); + throw new BusinessException("UNAUTHORIZED", "인증 처리 중 오류가 발생했습니다."); + } + } + + /** + * HTTP 요청에서 사용자 정보 추출 (일반 사용자용) + */ + public Long extractUserInfo(HttpServletRequest request) { + try { + String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + throw new BusinessException("UNAUTHORIZED", "인증 토큰이 필요합니다."); + } + + String token = authHeader.substring(7); + + if (!validateToken(token)) { + throw new BusinessException("UNAUTHORIZED", "유효하지 않은 토큰입니다."); + } + + String userId = getUserIdFromToken(token); + if (userId == null) { + throw new BusinessException("UNAUTHORIZED", "토큰에서 사용자 정보를 찾을 수 없습니다."); + } + + return Long.parseLong(userId); + + } catch (NumberFormatException e) { + throw new BusinessException("UNAUTHORIZED", "잘못된 사용자 ID 형식입니다."); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + log.error("사용자 정보 추출 중 오류 발생: {}", e.getMessage(), e); + throw new BusinessException("UNAUTHORIZED", "인증 처리 중 오류가 발생했습니다."); + } + } /** * 액세스 토큰 생성 diff --git a/store/src/main/java/com/ktds/hi/store/biz/service/StoreService.java b/store/src/main/java/com/ktds/hi/store/biz/service/StoreService.java index 010f058..980ee3d 100644 --- a/store/src/main/java/com/ktds/hi/store/biz/service/StoreService.java +++ b/store/src/main/java/com/ktds/hi/store/biz/service/StoreService.java @@ -2,10 +2,9 @@ package com.ktds.hi.store.biz.service; import com.ktds.hi.store.biz.usecase.in.StoreUseCase; -import com.ktds.hi.store.biz.usecase.out.*; -import com.ktds.hi.store.biz.domain.Store; -import com.ktds.hi.store.biz.domain.StoreStatus; import com.ktds.hi.store.infra.dto.*; +import com.ktds.hi.store.infra.gateway.entity.StoreEntity; +import com.ktds.hi.store.infra.gateway.repository.StoreJpaRepository; import com.ktds.hi.common.exception.BusinessException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -17,11 +16,7 @@ import java.util.List; import java.util.stream.Collectors; /** - * 매장 서비스 구현체 - * Clean Architecture의 Application Service Layer - * - * @author 하이오더 개발팀 - * @version 1.0.0 + * 매장 서비스 구현체 (간단 버전) */ @Slf4j @Service @@ -29,141 +24,77 @@ import java.util.stream.Collectors; @Transactional(readOnly = true) public class StoreService implements StoreUseCase { - private final StoreRepositoryPort storeRepositoryPort; - private final MenuRepositoryPort menuRepositoryPort; - private final StoreTagRepositoryPort storeTagRepositoryPort; - private final GeocodingPort geocodingPort; - private final CachePort cachePort; - private final EventPort eventPort; + private final StoreJpaRepository storeJpaRepository; @Override @Transactional public StoreCreateResponse createStore(Long ownerId, StoreCreateRequest request) { - log.info("매장 등록 시작: ownerId={}, storeName={}", ownerId, request.getStoreName()); + log.info("매장 등록: ownerId={}, storeName={}", ownerId, request.getStoreName()); - try { - // 1. 입력값 검증 - validateStoreCreateRequest(request); - - // 2. 점주 매장 개수 제한 확인 (예: 최대 10개) - validateOwnerStoreLimit(ownerId); - - // 3. 주소 지오코딩 (좌표 변환) - Coordinates coordinates = geocodingPort.getCoordinates(request.getAddress()); - - // 4. Store 도메인 객체 생성 - Store store = Store.builder() - .ownerId(ownerId) - .storeName(request.getStoreName()) - .address(request.getAddress()) - .latitude(coordinates.getLatitude()) - .longitude(coordinates.getLongitude()) - .description(request.getDescription()) - .phone(request.getPhone()) - .operatingHours(request.getOperatingHours()) - .category(request.getCategory()) - .status(StoreStatus.ACTIVE) - .rating(0.0) - .reviewCount(0) - .createdAt(LocalDateTime.now()) - .updatedAt(LocalDateTime.now()) - .build(); - - // 5. 매장 저장 - Store savedStore = storeRepositoryPort.saveStore(store); - - // 6. 매장 태그 저장 - if (request.getTags() != null && !request.getTags().isEmpty()) { - storeTagRepositoryPort.saveStoreTags(savedStore.getId(), request.getTags()); - } - - // 7. 메뉴 정보 저장 - if (request.getMenus() != null && !request.getMenus().isEmpty()) { - menuRepositoryPort.saveMenus(savedStore.getId(), - request.getMenus().stream() - .map(menuReq -> menuReq.toDomain(savedStore.getId())) - .collect(Collectors.toList())); - } - - // 8. 매장 생성 이벤트 발행 - eventPort.publishStoreCreatedEvent(savedStore); - - // 9. 캐시 무효화 - cachePort.invalidateStoreCache(ownerId); - - log.info("매장 등록 완료: storeId={}", savedStore.getId()); - - return StoreCreateResponse.builder() - .storeId(savedStore.getId()) - .storeName(savedStore.getStoreName()) - .message("매장이 성공적으로 등록되었습니다.") - .build(); - - } catch (Exception e) { - log.error("매장 등록 실패: ownerId={}, error={}", ownerId, e.getMessage(), e); - throw new BusinessException("STORE_CREATE_FAILED", "매장 등록에 실패했습니다: " + e.getMessage()); + // 기본 검증 + if (request.getStoreName() == null || request.getStoreName().trim().isEmpty()) { + throw new BusinessException("INVALID_STORE_NAME", "매장명은 필수입니다."); } + if (request.getAddress() == null || request.getAddress().trim().isEmpty()) { + throw new BusinessException("INVALID_ADDRESS", "주소는 필수입니다."); + } + + // 매장 엔티티 생성 + StoreEntity store = StoreEntity.builder() + .ownerId(ownerId) + .storeName(request.getStoreName()) + .address(request.getAddress()) + .latitude(37.5665) // 기본 좌표 (서울시청) + .longitude(126.9780) + .description(request.getDescription()) + .phone(request.getPhone()) + .operatingHours(request.getOperatingHours()) + .category(request.getCategory()) + .status("ACTIVE") + .rating(0.0) + .reviewCount(0) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build(); + + StoreEntity savedStore = storeJpaRepository.save(store); + + log.info("매장 등록 완료: storeId={}", savedStore.getId()); + + return StoreCreateResponse.builder() + .storeId(savedStore.getId()) + .storeName(savedStore.getStoreName()) + .message("매장이 성공적으로 등록되었습니다.") + .build(); } @Override public List getMyStores(Long ownerId) { log.info("내 매장 목록 조회: ownerId={}", ownerId); - // 1. 캐시 확인 - String cacheKey = "stores:owner:" + ownerId; - List cachedStores = cachePort.getStoreCache(cacheKey); - if (cachedStores != null) { - log.info("캐시에서 매장 목록 반환: ownerId={}, count={}", ownerId, cachedStores.size()); - return cachedStores; - } + List stores = storeJpaRepository.findByOwnerId(ownerId); - // 2. DB에서 매장 목록 조회 - List stores = storeRepositoryPort.findStoresByOwnerId(ownerId); - - // 3. 응답 DTO 변환 - List responses = stores.stream() - .map(store -> { - String status = calculateStoreStatus(store); - return MyStoreListResponse.builder() - .storeId(store.getId()) - .storeName(store.getStoreName()) - .address(store.getAddress()) - .category(store.getCategory()) - .rating(store.getRating()) - .reviewCount(store.getReviewCount()) - .status(status) - .operatingHours(store.getOperatingHours()) - .build(); - }) + return stores.stream() + .map(store -> MyStoreListResponse.builder() + .storeId(store.getId()) + .storeName(store.getStoreName()) + .address(store.getAddress()) + .category(store.getCategory()) + .rating(store.getRating()) + .reviewCount(store.getReviewCount()) + .status("운영중") + .operatingHours(store.getOperatingHours()) + .build()) .collect(Collectors.toList()); - - // 4. 캐시 저장 (1시간) - cachePort.putStoreCache(cacheKey, responses, 3600); - - log.info("내 매장 목록 조회 완료: ownerId={}, count={}", ownerId, responses.size()); - return responses; } @Override public StoreDetailResponse getStoreDetail(Long storeId) { log.info("매장 상세 조회: storeId={}", storeId); - // 1. 매장 기본 정보 조회 - Store store = storeRepositoryPort.findStoreById(storeId) + StoreEntity store = storeJpaRepository.findById(storeId) .orElseThrow(() -> new BusinessException("STORE_NOT_FOUND", "매장을 찾을 수 없습니다.")); - // 2. 매장 태그 조회 - List tags = storeTagRepositoryPort.findTagsByStoreId(storeId); - - // 3. 메뉴 정보 조회 - List menus = menuRepositoryPort.findMenusByStoreId(storeId) - .stream() - .map(MenuResponse::from) - .collect(Collectors.toList()); - - // 4. AI 요약 정보 조회 (외부 서비스) - String aiSummary = getAISummary(storeId); - return StoreDetailResponse.builder() .storeId(store.getId()) .storeName(store.getStoreName()) @@ -176,58 +107,26 @@ public class StoreService implements StoreUseCase { .category(store.getCategory()) .rating(store.getRating()) .reviewCount(store.getReviewCount()) - .status(store.getStatus().name()) - .tags(tags) - .menus(menus) - .aiSummary(aiSummary) + .status(store.getStatus()) .build(); } @Override @Transactional public StoreUpdateResponse updateStore(Long storeId, Long ownerId, StoreUpdateRequest request) { - log.info("매장 정보 수정: storeId={}, ownerId={}", storeId, ownerId); + log.info("매장 수정: storeId={}, ownerId={}", storeId, ownerId); - // 1. 매장 소유권 확인 - Store store = validateStoreOwnership(storeId, ownerId); + StoreEntity store = storeJpaRepository.findByIdAndOwnerId(storeId, ownerId) + .orElseThrow(() -> new BusinessException("STORE_ACCESS_DENIED", "매장에 대한 권한이 없습니다.")); - // 2. 주소 변경 시 지오코딩 - Coordinates coordinates = null; - if (!store.getAddress().equals(request.getAddress())) { - coordinates = geocodingPort.getCoordinates(request.getAddress()); - } + store.updateInfo(request.getStoreName(), request.getAddress(), request.getDescription(), + request.getPhone(), request.getOperatingHours()); - // 3. 매장 정보 업데이트 - store.updateBasicInfo( - request.getStoreName(), - request.getAddress(), - request.getDescription(), - request.getPhone(), - request.getOperatingHours() - ); - - if (coordinates != null) { - store.updateLocation(coordinates); - } - - Store updatedStore = storeRepositoryPort.saveStore(store); - - // 4. 태그 업데이트 - if (request.getTags() != null) { - storeTagRepositoryPort.deleteTagsByStoreId(storeId); - storeTagRepositoryPort.saveStoreTags(storeId, request.getTags()); - } - - // 5. 매장 수정 이벤트 발행 - eventPort.publishStoreUpdatedEvent(updatedStore); - - // 6. 캐시 무효화 - cachePort.invalidateStoreCache(storeId); - cachePort.invalidateStoreCache(ownerId); + storeJpaRepository.save(store); return StoreUpdateResponse.builder() .storeId(storeId) - .message("매장 정보가 성공적으로 수정되었습니다.") + .message("매장 정보가 수정되었습니다.") .build(); } @@ -236,23 +135,15 @@ public class StoreService implements StoreUseCase { public StoreDeleteResponse deleteStore(Long storeId, Long ownerId) { log.info("매장 삭제: storeId={}, ownerId={}", storeId, ownerId); - // 1. 매장 소유권 확인 - Store store = validateStoreOwnership(storeId, ownerId); + StoreEntity store = storeJpaRepository.findByIdAndOwnerId(storeId, ownerId) + .orElseThrow(() -> new BusinessException("STORE_ACCESS_DENIED", "매장에 대한 권한이 없습니다.")); - // 2. 소프트 삭제 (상태 변경) - store.delete(); - storeRepositoryPort.saveStore(store); - - // 3. 매장 삭제 이벤트 발행 - eventPort.publishStoreDeletedEvent(storeId); - - // 4. 캐시 무효화 - cachePort.invalidateStoreCache(storeId); - cachePort.invalidateStoreCache(ownerId); + store.updateStatus("DELETED"); + storeJpaRepository.save(store); return StoreDeleteResponse.builder() .storeId(storeId) - .message("매장이 성공적으로 삭제되었습니다.") + .message("매장이 삭제되었습니다.") .build(); } @@ -260,20 +151,16 @@ public class StoreService implements StoreUseCase { public List searchStores(String keyword, String category, String tags, Double latitude, Double longitude, Integer radius, Integer page, Integer size) { - log.info("매장 검색: keyword={}, category={}, location=({}, {})", keyword, category, latitude, longitude); + log.info("매장 검색: keyword={}, category={}", keyword, category); - StoreSearchCriteria criteria = StoreSearchCriteria.builder() - .keyword(keyword) - .category(category) - .tags(tags) - .latitude(latitude) - .longitude(longitude) - .radius(radius) - .page(page) - .size(size) - .build(); - - List stores = storeRepositoryPort.searchStores(criteria); + List stores; + if (keyword != null && !keyword.trim().isEmpty()) { + stores = storeJpaRepository.findByStoreNameContainingOrAddressContaining(keyword, keyword); + } else if (category != null && !category.trim().isEmpty()) { + stores = storeJpaRepository.findByCategory(category); + } else { + stores = storeJpaRepository.findAll(); + } return stores.stream() .map(store -> StoreSearchResponse.builder() @@ -283,60 +170,8 @@ public class StoreService implements StoreUseCase { .category(store.getCategory()) .rating(store.getRating()) .reviewCount(store.getReviewCount()) - .distance(calculateDistance(latitude, longitude, store.getLatitude(), store.getLongitude())) + .distance(1.5) // 더미 거리 .build()) .collect(Collectors.toList()); } - - // === Private Helper Methods === - - private void validateStoreCreateRequest(StoreCreateRequest request) { - if (request.getStoreName() == null || request.getStoreName().trim().isEmpty()) { - throw new BusinessException("INVALID_STORE_NAME", "매장명은 필수입니다."); - } - if (request.getStoreName().length() > 100) { - throw new BusinessException("INVALID_STORE_NAME", "매장명은 100자를 초과할 수 없습니다."); - } - if (request.getAddress() == null || request.getAddress().trim().isEmpty()) { - throw new BusinessException("INVALID_ADDRESS", "주소는 필수입니다."); - } - if (request.getPhone() != null && !request.getPhone().matches("^\\d{2,3}-\\d{3,4}-\\d{4}$")) { - throw new BusinessException("INVALID_PHONE", "전화번호 형식이 올바르지 않습니다."); - } - } - - private void validateOwnerStoreLimit(Long ownerId) { - Long storeCount = storeRepositoryPort.countStoresByOwnerId(ownerId); - if (storeCount >= 10) { - throw new BusinessException("STORE_LIMIT_EXCEEDED", "매장은 최대 10개까지 등록할 수 있습니다."); - } - } - - private Store validateStoreOwnership(Long storeId, Long ownerId) { - return storeRepositoryPort.findStoreByIdAndOwnerId(storeId, ownerId) - .orElseThrow(() -> new BusinessException("STORE_ACCESS_DENIED", "매장에 대한 권한이 없습니다.")); - } - - private String calculateStoreStatus(Store store) { - if (!store.isActive()) { - return "비활성"; - } - // 운영시간 기반 현재 상태 계산 로직 - return "운영중"; - } - - private String getAISummary(Long storeId) { - // TODO: AI 분석 서비스 연동 구현 - return "AI 요약 정보가 준비 중입니다."; - } - - private Double calculateDistance(Double lat1, Double lon1, Double lat2, Double lon2) { - if (lat1 == null || lon1 == null || lat2 == null || lon2 == null) { - return null; - } - return geocodingPort.calculateDistance( - new Coordinates(lat1, lon1), - new Coordinates(lat2, lon2) - ); - } } diff --git a/store/src/main/java/com/ktds/hi/store/biz/usecase/in/StoreUseCase.java b/store/src/main/java/com/ktds/hi/store/biz/usecase/in/StoreUseCase.java new file mode 100644 index 0000000..c402215 --- /dev/null +++ b/store/src/main/java/com/ktds/hi/store/biz/usecase/in/StoreUseCase.java @@ -0,0 +1,76 @@ +package com.ktds.hi.store.biz.usecase.in; + +import com.ktds.hi.store.infra.dto.*; + +import java.util.List; + +/** + * 매장 관리 유스케이스 인터페이스 + * Clean Architecture의 Input Port + * + * @author 하이오더 개발팀 + * @version 1.0.0 + */ +public interface StoreUseCase { + + /** + * 매장 등록 + * + * @param ownerId 점주 ID + * @param request 매장 등록 요청 정보 + * @return 매장 등록 응답 + */ + StoreCreateResponse createStore(Long ownerId, StoreCreateRequest request); + + /** + * 내 매장 목록 조회 + * + * @param ownerId 점주 ID + * @return 내 매장 목록 + */ + List getMyStores(Long ownerId); + + /** + * 매장 상세 조회 + * + * @param storeId 매장 ID + * @return 매장 상세 정보 + */ + StoreDetailResponse getStoreDetail(Long storeId); + + /** + * 매장 정보 수정 + * + * @param storeId 매장 ID + * @param ownerId 점주 ID + * @param request 매장 수정 요청 정보 + * @return 매장 수정 응답 + */ + StoreUpdateResponse updateStore(Long storeId, Long ownerId, StoreUpdateRequest request); + + /** + * 매장 삭제 + * + * @param storeId 매장 ID + * @param ownerId 점주 ID + * @return 매장 삭제 응답 + */ + StoreDeleteResponse deleteStore(Long storeId, Long ownerId); + + /** + * 매장 검색 + * + * @param keyword 검색 키워드 + * @param category 카테고리 + * @param tags 태그 + * @param latitude 위도 + * @param longitude 경도 + * @param radius 검색 반경(km) + * @param page 페이지 번호 + * @param size 페이지 크기 + * @return 검색된 매장 목록 + */ + List searchStores(String keyword, String category, String tags, + Double latitude, Double longitude, Integer radius, + Integer page, Integer size); +} diff --git a/store/src/main/java/com/ktds/hi/store/domain/Menu.java b/store/src/main/java/com/ktds/hi/store/domain/Menu.java index 131247a..ad92eec 100644 --- a/store/src/main/java/com/ktds/hi/store/domain/Menu.java +++ b/store/src/main/java/com/ktds/hi/store/domain/Menu.java @@ -1,7 +1,8 @@ -package com.ktds.hi.store.biz.domain; +package com.ktds.hi.store.domain; import lombok.Builder; import lombok.Getter; +import java.time.LocalDateTime; /** * 메뉴 도메인 엔티티 @@ -18,6 +19,8 @@ public class Menu { private String category; private String imageUrl; private Boolean available; + private LocalDateTime createdAt; // 추가 + private LocalDateTime updatedAt; // 추가 /** * 메뉴 정보 업데이트 diff --git a/store/src/main/java/com/ktds/hi/store/infra/dto/MenuResponse.java b/store/src/main/java/com/ktds/hi/store/infra/dto/MenuResponse.java index 06ae458..92c1bca 100644 --- a/store/src/main/java/com/ktds/hi/store/infra/dto/MenuResponse.java +++ b/store/src/main/java/com/ktds/hi/store/infra/dto/MenuResponse.java @@ -1,6 +1,6 @@ package com.ktds.hi.store.infra.dto; -import com.ktds.hi.store.biz.domain.Menu; +import com.ktds.hi.store.domain.Menu; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/store/src/main/java/com/ktds/hi/store/infra/gateway/CacheAdapter.java b/store/src/main/java/com/ktds/hi/store/infra/gateway/CacheAdapter.java index 60314d6..b2f80cc 100644 --- a/store/src/main/java/com/ktds/hi/store/infra/gateway/CacheAdapter.java +++ b/store/src/main/java/com/ktds/hi/store/infra/gateway/CacheAdapter.java @@ -9,6 +9,8 @@ import org.springframework.stereotype.Component; import java.time.Duration; import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; /** * 캐시 어댑터 클래스 @@ -32,29 +34,45 @@ public class CacheAdapter implements CachePort { return Optional.empty(); } } - + @Override - public void putStoreCache(String key, Object value, Duration ttl) { + public void putStoreCache(String key, Object value, long ttlSeconds) { try { - redisTemplate.opsForValue().set(key, value, ttl); - log.debug("매장 캐시 저장 완료: key={}, ttl={}분", key, ttl.toMinutes()); + redisTemplate.opsForValue().set(key, value, ttlSeconds, TimeUnit.SECONDS); + log.debug("캐시 저장: key={}, ttl={}초", key, ttlSeconds); } catch (Exception e) { - log.error("매장 캐시 저장 실패: key={}, error={}", key, e.getMessage()); + log.warn("캐시 저장 실패: key={}, error={}", key, e.getMessage()); } } - + @Override - public void invalidateStoreCache(Long storeId) { + public void invalidateStoreCache(Object key) { try { - // 매장 관련 모든 캐시 키 패턴 삭제 - String storeDetailKey = "store_detail:" + storeId; - String myStoresKey = "my_stores:*"; - - redisTemplate.delete(storeDetailKey); - - log.debug("매장 캐시 무효화 완료: storeId={}", storeId); + if (key instanceof Long) { + // 매장 ID로 특정 매장 캐시 삭제 + Long storeId = (Long) key; + String storeDetailKey = "store_detail:" + storeId; + redisTemplate.delete(storeDetailKey); + log.debug("매장 캐시 무효화 완료: storeId={}", storeId); + + } else if (key instanceof String) { + // 패턴으로 캐시 삭제 + String pattern = key.toString(); + Set keys = redisTemplate.keys(pattern); + if (keys != null && !keys.isEmpty()) { + redisTemplate.delete(keys); + } + log.debug("패턴 캐시 무효화 완료: pattern={}", pattern); + + } else { + // 기본적으로 toString()으로 키 생성 + String cacheKey = "stores:" + key.toString(); + redisTemplate.delete(cacheKey); + log.debug("캐시 무효화 완료: key={}", key); + } + } catch (Exception e) { - log.error("매장 캐시 무효화 실패: storeId={}, error={}", storeId, e.getMessage()); + log.error("캐시 무효화 실패: key={}, error={}", key, e.getMessage()); } } } diff --git a/store/src/main/java/com/ktds/hi/store/infra/gateway/MenuRepositoryAdapter.java b/store/src/main/java/com/ktds/hi/store/infra/gateway/MenuRepositoryAdapter.java index b46b76b..c59ebcd 100644 --- a/store/src/main/java/com/ktds/hi/store/infra/gateway/MenuRepositoryAdapter.java +++ b/store/src/main/java/com/ktds/hi/store/infra/gateway/MenuRepositoryAdapter.java @@ -96,7 +96,7 @@ public class MenuRepositoryAdapter implements MenuRepositoryPort { .price(entity.getPrice()) .category(entity.getCategory()) .imageUrl(entity.getImageUrl()) - .isAvailable(entity.getIsAvailable()) + .available(entity.getIsAvailable()) .createdAt(entity.getCreatedAt()) .updatedAt(entity.getUpdatedAt()) .build(); diff --git a/store/src/main/java/com/ktds/hi/store/infra/gateway/entity/StoreEntity.java b/store/src/main/java/com/ktds/hi/store/infra/gateway/entity/StoreEntity.java index ee89ae1..9171ee1 100644 --- a/store/src/main/java/com/ktds/hi/store/infra/gateway/entity/StoreEntity.java +++ b/store/src/main/java/com/ktds/hi/store/infra/gateway/entity/StoreEntity.java @@ -119,6 +119,19 @@ public class StoreEntity { this.reviewCount = reviewCount; } + /** + * 매장 기본 정보 업데이트 + */ + public void updateInfo(String storeName, String address, String description, + String phone, String operatingHours) { + this.storeName = storeName; + this.address = address; + this.description = description; + this.phone = phone; + this.operatingHours = operatingHours; + this.updatedAt = LocalDateTime.now(); + } + /** * 매장 태그 업데이트 */