feat : 주문통계 url 수정

This commit is contained in:
lsh9672 2025-06-17 09:25:22 +09:00
parent ee8d1a024c
commit dcfd7e9eaa
3 changed files with 196 additions and 6 deletions

View File

@ -0,0 +1,19 @@
package com.ktds.hi.analytics.infra.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderListResponse {
private Long storeId;
private Integer totalCount;
private List<OrderResponse> orders;
}

View File

@ -0,0 +1,24 @@
package com.ktds.hi.analytics.infra.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderResponse {
private Long id;
private Long storeId;
private Long menuId;
private Integer customerAge;
private String customerGender;
private BigDecimal orderAmount;
private LocalDateTime orderDate;
private LocalDateTime createdAt;
}

View File

@ -2,16 +2,27 @@ package com.ktds.hi.analytics.infra.gateway;
import com.ktds.hi.analytics.biz.domain.OrderStatistics; import com.ktds.hi.analytics.biz.domain.OrderStatistics;
import com.ktds.hi.analytics.biz.usecase.out.OrderDataPort; import com.ktds.hi.analytics.biz.usecase.out.OrderDataPort;
import com.ktds.hi.analytics.infra.dto.OrderListResponse;
import com.ktds.hi.analytics.infra.dto.OrderResponse;
import com.ktds.hi.common.dto.SuccessResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
/** /**
* 주문 데이터 어댑터 클래스 * 주문 데이터 어댑터 클래스
@ -32,13 +43,30 @@ public class OrderDataAdapter implements OrderDataPort {
log.info("주문 통계 조회: storeId={}, period={} ~ {}", storeId, startDate, endDate); log.info("주문 통계 조회: storeId={}, period={} ~ {}", storeId, startDate, endDate);
try { try {
// LocalDate를 LocalDateTime으로 변환 (시작일은 00:00:00, 종료일은 23:59:59)
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDateTime endDateTime = endDate.atTime(23, 59, 59);
String url = String.format("%s/api/stores/orders/store/%d/period?startDate=%s&endDate=%s", String url = String.format("%s/api/stores/orders/store/%d/period?startDate=%s&endDate=%s",
storeServiceUrl, storeId, startDate, endDate); storeServiceUrl, storeId, startDateTime, endDateTime);
OrderStatistics statistics = restTemplate.getForObject(url, OrderStatistics.class); // ParameterizedTypeReference를 사용하여 제네릭 타입 안전하게 파싱
ParameterizedTypeReference<SuccessResponse<OrderListResponse>> responseType =
new ParameterizedTypeReference<SuccessResponse<OrderListResponse>>() {};
ResponseEntity<SuccessResponse<OrderListResponse>> responseEntity =
restTemplate.exchange(url, HttpMethod.GET, null, responseType);
SuccessResponse<OrderListResponse> successResponse = responseEntity.getBody();
if (successResponse != null && successResponse.isSuccess() && successResponse.getData() != null) {
OrderListResponse orderListResponse = successResponse.getData();
// OrderListResponse를 OrderStatistics로 변환
OrderStatistics statistics = convertToOrderStatistics(orderListResponse);
if (statistics != null) {
log.info("주문 통계 조회 완료: storeId={}, totalOrders={}", log.info("주문 통계 조회 완료: storeId={}, totalOrders={}",
storeId, statistics.getTotalOrders()); storeId, statistics.getTotalOrders());
return statistics; return statistics;
@ -52,6 +80,125 @@ public class OrderDataAdapter implements OrderDataPort {
return createDummyOrderStatistics(storeId); return createDummyOrderStatistics(storeId);
} }
/**
* OrderListResponse를 OrderStatistics로 변환
*/
private OrderStatistics convertToOrderStatistics(OrderListResponse orderListResponse) {
List<OrderResponse> orders = orderListResponse.getOrders();
if (orders == null || orders.isEmpty()) {
return createEmptyOrderStatistics();
}
// 주문
int totalOrders = orders.size();
// 매출 계산
BigDecimal totalRevenue = orders.stream()
.map(OrderResponse::getOrderAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 평균 주문 금액 계산
double averageOrderValue = totalRevenue.doubleValue() / totalOrders;
// 시간대별 주문 분석 (피크 시간 계산)
Map<Integer, Long> hourlyOrderCount = orders.stream()
.collect(Collectors.groupingBy(
order -> order.getOrderDate().getHour(),
Collectors.counting()
));
int peakHour = hourlyOrderCount.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse(12); // 기본값 12시
// 연령대별 분포 계산
Map<String, Integer> ageDistribution = calculateCustomerAgeDistribution(orders);
// 인기 메뉴 계산 (메뉴ID별 주문 횟수) - 실제로는 메뉴명을 가져와야 하지만 임시로 메뉴ID 사용
List<String> popularMenus = orders.stream()
.collect(Collectors.groupingBy(
OrderResponse::getMenuId,
Collectors.counting()
))
.entrySet().stream()
.sorted(Map.Entry.<Long, Long>comparingByValue().reversed())
.limit(4)
.map(entry -> "메뉴" + entry.getKey()) // 실제로는 메뉴명으로 변환 필요
.collect(Collectors.toList());
return OrderStatistics.builder()
.totalOrders(totalOrders)
.totalRevenue(totalRevenue.longValue())
.averageOrderValue(averageOrderValue)
.peakHour(peakHour)
.popularMenus(popularMenus)
.customerAgeDistribution(ageDistribution)
.build();
}
/**
* 연령대별 분포 계산
*/
private Map<String, Integer> calculateCustomerAgeDistribution(List<OrderResponse> orders) {
Map<String, Long> ageGroupCount = orders.stream()
.collect(Collectors.groupingBy(
order -> getAgeGroup(order.getCustomerAge()),
Collectors.counting()
));
// Long을 Integer로 변환
Map<String, Integer> result = new HashMap<>();
ageGroupCount.forEach((key, value) -> result.put(key, value.intValue()));
return result;
}
/**
* 연령대별 분포 계산
*/
private Map<String, Integer> calculateAgeDistribution(List<OrderResponse> orders) {
Map<String, Long> ageGroupCount = orders.stream()
.collect(Collectors.groupingBy(
order -> getAgeGroup(order.getCustomerAge()),
Collectors.counting()
));
// Long을 Integer로 변환
Map<String, Integer> result = new HashMap<>();
ageGroupCount.forEach((key, value) -> result.put(key, value.intValue()));
return result;
}
/**
* 나이를 연령대 그룹으로 변환
*/
private String getAgeGroup(Integer age) {
if (age == null) return "미분류";
if (age < 20) return "10대";
if (age < 30) return "20대";
if (age < 40) return "30대";
if (age < 50) return "40대";
if (age < 60) return "50대";
return "60대+";
}
/**
* 주문 통계 생성
*/
private OrderStatistics createEmptyOrderStatistics() {
return OrderStatistics.builder()
.totalOrders(0)
.totalRevenue(0L)
.averageOrderValue(0.0)
.peakHour(12)
.popularMenus(Arrays.asList())
.customerAgeDistribution(new HashMap<>())
.build();
}
@Override @Override
public Integer getCurrentOrderCount(Long storeId) { public Integer getCurrentOrderCount(Long storeId) {
log.info("실시간 주문 현황 조회: storeId={}", storeId); log.info("실시간 주문 현황 조회: storeId={}", storeId);