feat : 주문통계 url 수정
This commit is contained in:
parent
ee8d1a024c
commit
dcfd7e9eaa
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user