|
|
@@ -317,6 +317,72 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
|
|
|
return comparison;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public StoreOperationalStatisticsComparisonVo getStatisticsComparisonNew(Integer storeId, String currentStartTime, String currentEndTime,
|
|
|
+ String previousStartTime, String previousEndTime) {
|
|
|
+
|
|
|
+ log.info("StoreOperationalStatisticsServiceImpl.getStatisticsComparison - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}",
|
|
|
+ storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
|
|
|
+
|
|
|
+ // 参数校验
|
|
|
+ if (storeId == null || storeId <= 0) {
|
|
|
+ throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
|
|
|
+ }
|
|
|
+ if (currentStartTime == null || currentStartTime.trim().isEmpty()) {
|
|
|
+ throw new IllegalArgumentException("当期开始时间不能为空");
|
|
|
+ }
|
|
|
+ if (currentEndTime == null || currentEndTime.trim().isEmpty()) {
|
|
|
+ throw new IllegalArgumentException("当期结束时间不能为空");
|
|
|
+ }
|
|
|
+ if (previousStartTime == null || previousStartTime.trim().isEmpty()) {
|
|
|
+ throw new IllegalArgumentException("上期开始时间不能为空");
|
|
|
+ }
|
|
|
+ if (previousEndTime == null || previousEndTime.trim().isEmpty()) {
|
|
|
+ throw new IllegalArgumentException("上期结束时间不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ StoreOperationalStatisticsComparisonVo comparison = new StoreOperationalStatisticsComparisonVo();
|
|
|
+ comparison.setCurrentStartTime(currentStartTime);
|
|
|
+ comparison.setCurrentEndTime(currentEndTime);
|
|
|
+ comparison.setPreviousStartTime(previousStartTime);
|
|
|
+ comparison.setPreviousEndTime(previousEndTime);
|
|
|
+
|
|
|
+ // 获取当期和上期的统计数据
|
|
|
+ StoreOperationalStatisticsVo currentStatistics = calculateStatistics(storeId, currentStartTime, currentEndTime);
|
|
|
+ StoreOperationalStatisticsVo previousStatistics = calculateStatistics(storeId, previousStartTime, previousEndTime);
|
|
|
+
|
|
|
+ // 构建对比数据
|
|
|
+ comparison.setTrafficData(buildTrafficDataComparison(currentStatistics.getTrafficData(), previousStatistics.getTrafficData()));
|
|
|
+ comparison.setInteractionData(buildInteractionDataComparison(currentStatistics.getInteractionData(), previousStatistics.getInteractionData()));
|
|
|
+ comparison.setCouponData(buildCouponDataComparison(currentStatistics.getCouponData(), previousStatistics.getCouponData()));
|
|
|
+ comparison.setVoucherData(buildVoucherDataComparison(currentStatistics.getVoucherData(), previousStatistics.getVoucherData()));
|
|
|
+ comparison.setServiceQualityData(buildServiceQualityDataComparison(currentStatistics.getServiceQualityData(), previousStatistics.getServiceQualityData()));
|
|
|
+ comparison.setPriceListRanking(buildPriceListRankingComparison(currentStatistics.getPriceListRanking(), previousStatistics.getPriceListRanking(), storeId));
|
|
|
+
|
|
|
+ // 当comparison中所有对比数据的current和previous都为0或为空时,不抛异常,返回 null(由 Controller 返回失败提示)
|
|
|
+ if (!hasComparisonData(comparison)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存历史记录(不包含PDF URL,PDF URL只在 generateStatisticsComparisonPdf 接口中保存)
|
|
|
+ Integer historyId = saveStatisticsHistory(storeId, currentStartTime, currentEndTime,
|
|
|
+ previousStartTime, previousEndTime, comparison, null);
|
|
|
+
|
|
|
+ // 将历史记录ID设置到返回对象中
|
|
|
+ comparison.setHistoryId(historyId);
|
|
|
+
|
|
|
+ // 异步调用AI接口进行数据分析
|
|
|
+ if (historyId != null) {
|
|
|
+ try {
|
|
|
+ callAiAnalysisAsync(historyId, storeId, comparison);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("调用AI分析接口失败 - historyId={}, error={}", historyId, e.getMessage(), e);
|
|
|
+ // AI分析失败不影响主流程,只记录日志
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return comparison;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* 将StoreOperationalStatisticsVo转换为符合埋点统计数据JSON格式
|
|
|
@@ -991,6 +1057,80 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 判断 comparison 中是否至少有一项对比数据有效(current 或 previous 非 0 且非空)。
|
|
|
+ */
|
|
|
+ private boolean hasComparisonData(StoreOperationalStatisticsComparisonVo comparison) {
|
|
|
+ if (comparison == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ boolean hasData = hasAnyMeaningfulData(comparison.getTrafficData())
|
|
|
+ || hasAnyMeaningfulData(comparison.getInteractionData())
|
|
|
+ || hasAnyMeaningfulData(comparison.getCouponData())
|
|
|
+ || hasAnyMeaningfulData(comparison.getVoucherData())
|
|
|
+ || hasAnyMeaningfulData(comparison.getServiceQualityData());
|
|
|
+ if (!hasData && comparison.getPriceListRanking() != null) {
|
|
|
+ for (StoreOperationalStatisticsComparisonVo.PriceListRankingComparison pr : comparison.getPriceListRanking()) {
|
|
|
+ if (pr != null && (hasMeaningfulValue(pr.getPageViews()) || hasMeaningfulValue(pr.getVisitors()) || hasMeaningfulValue(pr.getShares()))) {
|
|
|
+ hasData = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return hasData;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验 comparison 中是否至少有一项对比数据有效(current 或 previous 非 0 且非空)。
|
|
|
+ * 若所有对比数据的 current 和 previous 都为 0 或空,则抛出 IllegalArgumentException("暂无对比数据")。
|
|
|
+ */
|
|
|
+ private void validateComparisonHasData(StoreOperationalStatisticsComparisonVo comparison) {
|
|
|
+ if (!hasComparisonData(comparison)) {
|
|
|
+ throw new IllegalArgumentException("暂无对比数据");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean hasAnyMeaningfulData(Object dataComparison) {
|
|
|
+ if (dataComparison == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ java.lang.reflect.Field[] fields = dataComparison.getClass().getDeclaredFields();
|
|
|
+ for (java.lang.reflect.Field f : fields) {
|
|
|
+ if (StoreOperationalStatisticsComparisonVo.BaseComparisonData.class.isAssignableFrom(f.getType())) {
|
|
|
+ try {
|
|
|
+ f.setAccessible(true);
|
|
|
+ StoreOperationalStatisticsComparisonVo.BaseComparisonData b = (StoreOperationalStatisticsComparisonVo.BaseComparisonData) f.get(dataComparison);
|
|
|
+ if (hasMeaningfulValue(b)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ } catch (Exception ignored) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean hasMeaningfulValue(StoreOperationalStatisticsComparisonVo.BaseComparisonData b) {
|
|
|
+ if (b == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return !isNullOrZero(b.getCurrent()) || !isNullOrZero(b.getPrevious());
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isNullOrZero(Object v) {
|
|
|
+ if (v == null) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (v instanceof Number) {
|
|
|
+ return ((Number) v).doubleValue() == 0;
|
|
|
+ }
|
|
|
+ if (v instanceof CharSequence) {
|
|
|
+ String s = v.toString().trim();
|
|
|
+ return s.isEmpty() || "0".equals(s) || "0.0".equals(s);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* 构建对比数据
|
|
|
*/
|
|
|
private StoreOperationalStatisticsComparisonVo.BaseComparisonData buildComparisonData(Object current, Object previous) {
|