|
|
@@ -0,0 +1,1488 @@
|
|
|
+package shop.alien.storeplatform.service.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.cloud.context.config.annotation.RefreshScope;
|
|
|
+import org.springframework.http.*;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+import shop.alien.entity.store.*;
|
|
|
+import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
|
|
|
+import shop.alien.entity.store.vo.StoreOperationalStatisticsVo;
|
|
|
+import shop.alien.mapper.*;
|
|
|
+import shop.alien.storeplatform.service.PlatformStoreOperationalStatisticsService;
|
|
|
+import shop.alien.storeplatform.util.PlatformStatisticsComparisonImageUtil;
|
|
|
+import shop.alien.storeplatform.util.AiAuthTokenUtil;
|
|
|
+import shop.alien.util.ali.AliOSSUtil;
|
|
|
+import shop.alien.util.common.RandomCreateUtil;
|
|
|
+import shop.alien.util.pdf.ImageToPdfUtil;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.text.ParseException;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 商家经营数据统计服务实现类
|
|
|
+ *
|
|
|
+ * @author system
|
|
|
+ * @version 1.0
|
|
|
+ * @date 2026/01/05
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+@RefreshScope
|
|
|
+public class PlatformStoreOperationalStatisticsServiceImpl implements PlatformStoreOperationalStatisticsService {
|
|
|
+
|
|
|
+ private final LifeBrowseRecordMapper lifeBrowseRecordMapper;
|
|
|
+ private final LifeCollectMapper lifeCollectMapper;
|
|
|
+ private final StoreClockInMapper storeClockInMapper;
|
|
|
+ private final LifeUserDynamicsMapper lifeUserDynamicsMapper;
|
|
|
+ private final StoreCommentMapper storeCommentMapper;
|
|
|
+ private final LifeCommentMapper lifeCommentMapper;
|
|
|
+ private final LifeLikeRecordMapper lifeLikeRecordMapper;
|
|
|
+ private final LifeFansMapper lifeFansMapper;
|
|
|
+ private final LifeBlacklistMapper lifeBlacklistMapper;
|
|
|
+ private final LifeDiscountCouponStoreFriendMapper lifeDiscountCouponStoreFriendMapper;
|
|
|
+ private final LifeDiscountCouponUserMapper lifeDiscountCouponUserMapper;
|
|
|
+ private final LifeCouponMapper lifeCouponMapper;
|
|
|
+ private final CommonRatingMapper commonRatingMapper;
|
|
|
+ private final StoreEvaluationMapper storeEvaluationMapper;
|
|
|
+ private final StoreCommentAppealMapper storeCommentAppealMapper;
|
|
|
+ private final StorePriceMapper storePriceMapper;
|
|
|
+ private final StoreCuisineMapper storeCuisineMapper;
|
|
|
+ private final StoreInfoMapper storeInfoMapper;
|
|
|
+ private final StoreUserMapper storeUserMapper;
|
|
|
+ private final StoreOperationalStatisticsHistoryMapper statisticsHistoryMapper;
|
|
|
+ private final StoreTrackStatisticsMapper storeTrackStatisticsMapper;
|
|
|
+ private final AliOSSUtil aliOSSUtil;
|
|
|
+ private final RestTemplate restTemplate;
|
|
|
+ private final AiAuthTokenUtil aiAuthTokenUtil;
|
|
|
+
|
|
|
+
|
|
|
+ @Value("${third-party-ai-store-summary-suggestion.base-url:}")
|
|
|
+ private String aiStatisticsAnalysisUrl;
|
|
|
+
|
|
|
+ private static final String DATE_FORMAT = "yyyy-MM-dd";
|
|
|
+ private static final String STAT_TYPE_DAILY = "DAILY";
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public StoreOperationalStatisticsVo getPlatformStatisticsInTrackFormat(Integer storeId, String startTime, String endTime) {
|
|
|
+ log.info("StoreOperationalStatisticsServiceImpl.getStatisticsInTrackFormat - storeId={}, startTime={}, endTime={}",
|
|
|
+ storeId, startTime, endTime);
|
|
|
+
|
|
|
+ // 计算统计数据
|
|
|
+ StoreOperationalStatisticsVo statistics = calculateStatistics(storeId, startTime, endTime);
|
|
|
+
|
|
|
+ // 不再保存统计数据到历史表,只有对比接口才会保存历史数据
|
|
|
+
|
|
|
+ return statistics;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public StoreOperationalStatisticsComparisonVo getPlatformStatisticsComparison(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));
|
|
|
+
|
|
|
+ // 保存历史记录(不包含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;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public StoreOperationalStatisticsHistory getPlatformHistoryById(Integer id) {
|
|
|
+ log.info("StoreOperationalStatisticsServiceImpl.getHistoryById - id={}", id);
|
|
|
+
|
|
|
+ // 参数校验
|
|
|
+ if (id == null || id <= 0) {
|
|
|
+ throw new IllegalArgumentException("历史记录ID不能为空且必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询历史记录
|
|
|
+ LambdaQueryWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreOperationalStatisticsHistory::getId, id)
|
|
|
+ .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0);
|
|
|
+
|
|
|
+ StoreOperationalStatisticsHistory history = statisticsHistoryMapper.selectOne(wrapper);
|
|
|
+
|
|
|
+ if (history == null) {
|
|
|
+ log.warn("查询历史统计记录详情失败,记录不存在或已删除 - id={}", id);
|
|
|
+ throw new RuntimeException("历史记录不存在或已删除");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("查询历史统计记录详情成功 - id={}", id);
|
|
|
+ return history;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IPage<StoreOperationalStatisticsHistory> getPlatformHistoryList(Integer storeId, Integer page, Integer size, String createdTime) {
|
|
|
+ log.info("StoreOperationalStatisticsServiceImpl.getHistoryList - storeId={}, page={}, size={}, createdTime={}", storeId, page, size, createdTime);
|
|
|
+
|
|
|
+ // 参数校验
|
|
|
+ if (storeId == null || storeId <= 0) {
|
|
|
+ throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
|
|
|
+ }
|
|
|
+ int pageNum = page != null && page > 0 ? page : 1;
|
|
|
+ int pageSize = size != null && size > 0 ? size : 10;
|
|
|
+
|
|
|
+ // 构建分页对象
|
|
|
+ IPage<StoreOperationalStatisticsHistory> pageObj = new Page<>(pageNum, pageSize);
|
|
|
+
|
|
|
+ // 构建查询条件
|
|
|
+ LambdaQueryWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreOperationalStatisticsHistory::getStoreId, storeId)
|
|
|
+ .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0)
|
|
|
+ .orderByDesc(StoreOperationalStatisticsHistory::getQueryTime);
|
|
|
+
|
|
|
+ // created_time 可选:与 query_time 为同一天(query_time 格式 yyyy-MM-dd HH:mm:ss,created_time 格式 yyyy-MM-dd)
|
|
|
+ if (StringUtils.hasText(createdTime)) {
|
|
|
+ wrapper.apply("DATE(query_time) = {0}", createdTime.trim());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 执行分页查询
|
|
|
+ IPage<StoreOperationalStatisticsHistory> result = statisticsHistoryMapper.selectPage(pageObj, wrapper);
|
|
|
+
|
|
|
+ log.info("查询历史统计记录列表成功 - storeId={}, 共{}条记录,当前页{}条", storeId, result.getTotal(), result.getRecords().size());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean batchPlatformDeleteHistory(List<Integer> ids) {
|
|
|
+ log.info("StoreOperationalStatisticsServiceImpl.batchDeleteHistory - ids={}", ids);
|
|
|
+
|
|
|
+ if (ids == null || ids.isEmpty()) {
|
|
|
+ log.warn("批量删除历史统计记录失败,ID列表为空");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 使用逻辑删除
|
|
|
+ LambdaUpdateWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper.in(StoreOperationalStatisticsHistory::getId, ids)
|
|
|
+ .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0)
|
|
|
+ .set(StoreOperationalStatisticsHistory::getDeleteFlag, 1);
|
|
|
+
|
|
|
+ int result = statisticsHistoryMapper.update(null, wrapper);
|
|
|
+ return result > 0;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量删除历史统计记录失败 - ids={}, error={}", ids, e.getMessage(), e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String generateStatisticsComparisonPdfByHistoryId(Integer storeId, Integer historyId) {
|
|
|
+ log.info("StoreOperationalStatisticsServiceImpl.generateStatisticsComparisonPdfByHistoryId - storeId={}, historyId={}", storeId, historyId);
|
|
|
+
|
|
|
+ if (storeId == null || storeId <= 0) {
|
|
|
+ throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
|
|
|
+ }
|
|
|
+ if (historyId == null || historyId <= 0) {
|
|
|
+ throw new IllegalArgumentException("历史记录ID不能为空且必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 根据 historyId 查询历史记录
|
|
|
+ StoreOperationalStatisticsHistory history = getHistoryById(historyId);
|
|
|
+ if (!storeId.equals(history.getStoreId())) {
|
|
|
+ throw new IllegalArgumentException("历史记录与店铺ID不匹配");
|
|
|
+ }
|
|
|
+ String statisticsDataJson = history.getStatisticsData();
|
|
|
+ if (statisticsDataJson == null || statisticsDataJson.trim().isEmpty()) {
|
|
|
+ throw new IllegalArgumentException("该历史记录无统计数据(statistics_data为空)");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 将 statistics_data 的 JSON 转为 StoreOperationalStatisticsComparisonVo
|
|
|
+ StoreOperationalStatisticsComparisonVo comparison = JSON.parseObject(statisticsDataJson.trim(), StoreOperationalStatisticsComparisonVo.class);
|
|
|
+ if (comparison == null) {
|
|
|
+ throw new RuntimeException("解析统计数据对比JSON失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 3. 生成图片
|
|
|
+ byte[] imageBytes = PlatformStatisticsComparisonImageUtil.generateImage(comparison);
|
|
|
+ java.io.ByteArrayInputStream imageInputStream = new java.io.ByteArrayInputStream(imageBytes);
|
|
|
+ byte[] pdfBytes = ImageToPdfUtil.imageToPdfBytes(imageInputStream);
|
|
|
+ if (pdfBytes == null || pdfBytes.length == 0) {
|
|
|
+ throw new RuntimeException("图片转PDF失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 上传PDF到OSS
|
|
|
+ String ossFilePath = "statistics/comparison_" + storeId + "_" + RandomCreateUtil.getRandomNum(8) + ".pdf";
|
|
|
+ java.io.ByteArrayInputStream pdfInputStream = new java.io.ByteArrayInputStream(pdfBytes);
|
|
|
+ String pdfUrl = aliOSSUtil.uploadFile(pdfInputStream, ossFilePath);
|
|
|
+ log.info("根据历史记录生成统计数据对比PDF并上传成功 - storeId={}, historyId={}, pdfUrl={}", storeId, historyId, pdfUrl);
|
|
|
+
|
|
|
+ // 5. 更新该历史记录的 PDF URL
|
|
|
+ updateHistoryPdfUrl(historyId, pdfUrl);
|
|
|
+ return pdfUrl;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("根据历史记录生成统计数据对比PDF失败 - storeId={}, historyId={}, error={}", storeId, historyId, e.getMessage(), e);
|
|
|
+ throw new RuntimeException("生成PDF报告失败: " + e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean updateHistoryPdfUrl(Integer historyId, String pdfUrl) {
|
|
|
+ log.info("StoreOperationalStatisticsServiceImpl.updateHistoryPdfUrl - historyId={}, pdfUrl={}", historyId, pdfUrl);
|
|
|
+
|
|
|
+ if (historyId == null || historyId <= 0) {
|
|
|
+ log.warn("更新历史统计记录PDF URL失败,历史记录ID无效 - historyId={}", historyId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (pdfUrl == null || pdfUrl.trim().isEmpty()) {
|
|
|
+ log.warn("更新历史统计记录PDF URL失败,PDF URL为空 - historyId={}", historyId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 查询历史记录是否存在且未删除
|
|
|
+ StoreOperationalStatisticsHistory history = statisticsHistoryMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<StoreOperationalStatisticsHistory>()
|
|
|
+ .eq(StoreOperationalStatisticsHistory::getId, historyId)
|
|
|
+ .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0));
|
|
|
+
|
|
|
+ if (history == null) {
|
|
|
+ log.warn("更新历史统计记录PDF URL失败,历史记录不存在或已删除 - historyId={}", historyId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新PDF URL
|
|
|
+ LambdaUpdateWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper.eq(StoreOperationalStatisticsHistory::getId, historyId)
|
|
|
+ .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0)
|
|
|
+ .set(StoreOperationalStatisticsHistory::getPdfUrl, pdfUrl.trim());
|
|
|
+
|
|
|
+ int result = statisticsHistoryMapper.update(null, wrapper);
|
|
|
+ if (result > 0) {
|
|
|
+ log.info("更新历史统计记录PDF URL成功 - historyId={}, pdfUrl={}", historyId, pdfUrl);
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ log.warn("更新历史统计记录PDF URL失败,未更新任何记录 - historyId={}", historyId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("更新历史统计记录PDF URL失败 - historyId={}, pdfUrl={}, error={}", historyId, pdfUrl, e.getMessage(), e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public StoreOperationalStatisticsHistory getHistoryById(Integer id) {
|
|
|
+ log.info("StoreOperationalStatisticsServiceImpl.getHistoryById - id={}", id);
|
|
|
+
|
|
|
+ // 参数校验
|
|
|
+ if (id == null || id <= 0) {
|
|
|
+ throw new IllegalArgumentException("历史记录ID不能为空且必须大于0");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询历史记录
|
|
|
+ LambdaQueryWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreOperationalStatisticsHistory::getId, id)
|
|
|
+ .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0);
|
|
|
+
|
|
|
+ StoreOperationalStatisticsHistory history = statisticsHistoryMapper.selectOne(wrapper);
|
|
|
+
|
|
|
+ if (history == null) {
|
|
|
+ log.warn("查询历史统计记录详情失败,记录不存在或已删除 - id={}", id);
|
|
|
+ throw new RuntimeException("历史记录不存在或已删除");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("查询历史统计记录详情成功 - id={}", id);
|
|
|
+ return history;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建流量数据对比
|
|
|
+ */
|
|
|
+ private StoreOperationalStatisticsComparisonVo.TrafficDataComparison buildTrafficDataComparison(
|
|
|
+ StoreOperationalStatisticsVo.TrafficData current, StoreOperationalStatisticsVo.TrafficData previous) {
|
|
|
+ StoreOperationalStatisticsComparisonVo.TrafficDataComparison comparison = new StoreOperationalStatisticsComparisonVo.TrafficDataComparison();
|
|
|
+ // 如果 previous 为 null,创建空对象避免 NullPointerException
|
|
|
+ if (previous == null) {
|
|
|
+ previous = new StoreOperationalStatisticsVo.TrafficData();
|
|
|
+ }
|
|
|
+ if (current == null) {
|
|
|
+ current = new StoreOperationalStatisticsVo.TrafficData();
|
|
|
+ }
|
|
|
+ comparison.setStoreSearchVolume(buildComparisonData(current.getStoreSearchVolume(), previous.getStoreSearchVolume()));
|
|
|
+ comparison.setPageViews(buildComparisonData(current.getPageViews(), previous.getPageViews()));
|
|
|
+ comparison.setVisitors(buildComparisonData(current.getVisitors(), previous.getVisitors()));
|
|
|
+ comparison.setNewVisitors(buildComparisonData(current.getNewVisitors(), previous.getNewVisitors()));
|
|
|
+ comparison.setVisitDuration(buildComparisonData(current.getVisitDuration(), previous.getVisitDuration()));
|
|
|
+ comparison.setAvgVisitDuration(buildComparisonData(current.getAvgVisitDuration(), previous.getAvgVisitDuration()));
|
|
|
+ return comparison;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建互动数据对比
|
|
|
+ */
|
|
|
+ private StoreOperationalStatisticsComparisonVo.InteractionDataComparison buildInteractionDataComparison(
|
|
|
+ StoreOperationalStatisticsVo.InteractionData current, StoreOperationalStatisticsVo.InteractionData previous) {
|
|
|
+ StoreOperationalStatisticsComparisonVo.InteractionDataComparison comparison = new StoreOperationalStatisticsComparisonVo.InteractionDataComparison();
|
|
|
+ // 如果 previous 为 null,创建空对象避免 NullPointerException
|
|
|
+ if (previous == null) {
|
|
|
+ previous = new StoreOperationalStatisticsVo.InteractionData();
|
|
|
+ }
|
|
|
+ if (current == null) {
|
|
|
+ current = new StoreOperationalStatisticsVo.InteractionData();
|
|
|
+ }
|
|
|
+ comparison.setStoreCollectionCount(buildComparisonData(current.getStoreCollectionCount(), previous.getStoreCollectionCount()));
|
|
|
+ comparison.setStoreShareCount(buildComparisonData(current.getStoreShareCount(), previous.getStoreShareCount()));
|
|
|
+ comparison.setStoreCheckInCount(buildComparisonData(current.getStoreCheckInCount(), previous.getStoreCheckInCount()));
|
|
|
+ comparison.setConsultMerchantCount(buildComparisonData(current.getConsultMerchantCount(), previous.getConsultMerchantCount()));
|
|
|
+ comparison.setFriendsCount(buildComparisonData(current.getFriendsCount(), previous.getFriendsCount()));
|
|
|
+ comparison.setFollowCount(buildComparisonData(current.getFollowCount(), previous.getFollowCount()));
|
|
|
+ comparison.setFansCount(buildComparisonData(current.getFansCount(), previous.getFansCount()));
|
|
|
+ comparison.setPostsPublishedCount(buildComparisonData(current.getPostsPublishedCount(), previous.getPostsPublishedCount()));
|
|
|
+ comparison.setPostLikesCount(buildComparisonData(current.getPostLikesCount(), previous.getPostLikesCount()));
|
|
|
+ comparison.setPostCommentsCount(buildComparisonData(current.getPostCommentsCount(), previous.getPostCommentsCount()));
|
|
|
+ comparison.setPostSharesCount(buildComparisonData(current.getPostSharesCount(), previous.getPostSharesCount()));
|
|
|
+ comparison.setReportedCount(buildComparisonData(current.getReportedCount(), previous.getReportedCount()));
|
|
|
+ comparison.setBlockedCount(buildComparisonData(current.getBlockedCount(), previous.getBlockedCount()));
|
|
|
+ return comparison;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建优惠券数据对比
|
|
|
+ */
|
|
|
+ private StoreOperationalStatisticsComparisonVo.CouponDataComparison buildCouponDataComparison(
|
|
|
+ StoreOperationalStatisticsVo.CouponData current, StoreOperationalStatisticsVo.CouponData previous) {
|
|
|
+ StoreOperationalStatisticsComparisonVo.CouponDataComparison comparison = new StoreOperationalStatisticsComparisonVo.CouponDataComparison();
|
|
|
+ // 如果 previous 为 null,创建空对象避免 NullPointerException
|
|
|
+ if (previous == null) {
|
|
|
+ previous = new StoreOperationalStatisticsVo.CouponData();
|
|
|
+ }
|
|
|
+ if (current == null) {
|
|
|
+ current = new StoreOperationalStatisticsVo.CouponData();
|
|
|
+ }
|
|
|
+ comparison.setGiftToFriendsCount(buildComparisonData(current.getGiftToFriendsCount(), previous.getGiftToFriendsCount()));
|
|
|
+ comparison.setGiftToFriendsAmount(buildComparisonData(current.getGiftToFriendsAmount(), previous.getGiftToFriendsAmount()));
|
|
|
+ comparison.setGiftToFriendsUsedCount(buildComparisonData(current.getGiftToFriendsUsedCount(), previous.getGiftToFriendsUsedCount()));
|
|
|
+ comparison.setGiftToFriendsUsedAmount(buildComparisonData(current.getGiftToFriendsUsedAmount(), previous.getGiftToFriendsUsedAmount()));
|
|
|
+ comparison.setGiftToFriendsUsedAmountRatio(buildComparisonData(current.getGiftToFriendsUsedAmountRatio(), previous.getGiftToFriendsUsedAmountRatio()));
|
|
|
+ comparison.setFriendsGiftCount(buildComparisonData(current.getFriendsGiftCount(), previous.getFriendsGiftCount()));
|
|
|
+ comparison.setFriendsGiftAmount(buildComparisonData(current.getFriendsGiftAmount(), previous.getFriendsGiftAmount()));
|
|
|
+ comparison.setFriendsGiftUsedCount(buildComparisonData(current.getFriendsGiftUsedCount(), previous.getFriendsGiftUsedCount()));
|
|
|
+ comparison.setFriendsGiftUsedAmount(buildComparisonData(current.getFriendsGiftUsedAmount(), previous.getFriendsGiftUsedAmount()));
|
|
|
+ comparison.setFriendsGiftUsedAmountRatio(buildComparisonData(current.getFriendsGiftUsedAmountRatio(), previous.getFriendsGiftUsedAmountRatio()));
|
|
|
+ return comparison;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建代金券数据对比
|
|
|
+ */
|
|
|
+ private StoreOperationalStatisticsComparisonVo.VoucherDataComparison buildVoucherDataComparison(
|
|
|
+ StoreOperationalStatisticsVo.VoucherData current, StoreOperationalStatisticsVo.VoucherData previous) {
|
|
|
+ StoreOperationalStatisticsComparisonVo.VoucherDataComparison comparison = new StoreOperationalStatisticsComparisonVo.VoucherDataComparison();
|
|
|
+ // 如果 previous 为 null,创建空对象避免 NullPointerException
|
|
|
+ if (previous == null) {
|
|
|
+ previous = new StoreOperationalStatisticsVo.VoucherData();
|
|
|
+ }
|
|
|
+ if (current == null) {
|
|
|
+ current = new StoreOperationalStatisticsVo.VoucherData();
|
|
|
+ }
|
|
|
+ comparison.setGiftToFriendsCount(buildComparisonData(current.getGiftToFriendsCount(), previous.getGiftToFriendsCount()));
|
|
|
+ comparison.setGiftToFriendsAmount(buildComparisonData(current.getGiftToFriendsAmount(), previous.getGiftToFriendsAmount()));
|
|
|
+ comparison.setGiftToFriendsUsedCount(buildComparisonData(current.getGiftToFriendsUsedCount(), previous.getGiftToFriendsUsedCount()));
|
|
|
+ comparison.setGiftToFriendsUsedAmount(buildComparisonData(current.getGiftToFriendsUsedAmount(), previous.getGiftToFriendsUsedAmount()));
|
|
|
+ comparison.setGiftToFriendsUsedAmountRatio(buildComparisonData(current.getGiftToFriendsUsedAmountRatio(), previous.getGiftToFriendsUsedAmountRatio()));
|
|
|
+ comparison.setFriendsGiftCount(buildComparisonData(current.getFriendsGiftCount(), previous.getFriendsGiftCount()));
|
|
|
+ comparison.setFriendsGiftAmount(buildComparisonData(current.getFriendsGiftAmount(), previous.getFriendsGiftAmount()));
|
|
|
+ comparison.setFriendsGiftUsedCount(buildComparisonData(current.getFriendsGiftUsedCount(), previous.getFriendsGiftUsedCount()));
|
|
|
+ comparison.setFriendsGiftUsedAmount(buildComparisonData(current.getFriendsGiftUsedAmount(), previous.getFriendsGiftUsedAmount()));
|
|
|
+ comparison.setFriendsGiftUsedAmountRatio(buildComparisonData(current.getFriendsGiftUsedAmountRatio(), previous.getFriendsGiftUsedAmountRatio()));
|
|
|
+ return comparison;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建服务质量数据对比
|
|
|
+ */
|
|
|
+ private StoreOperationalStatisticsComparisonVo.ServiceQualityDataComparison buildServiceQualityDataComparison(
|
|
|
+ StoreOperationalStatisticsVo.ServiceQualityData current, StoreOperationalStatisticsVo.ServiceQualityData previous) {
|
|
|
+ StoreOperationalStatisticsComparisonVo.ServiceQualityDataComparison comparison = new StoreOperationalStatisticsComparisonVo.ServiceQualityDataComparison();
|
|
|
+ // 如果 previous 为 null,创建空对象避免 NullPointerException
|
|
|
+ if (previous == null) {
|
|
|
+ previous = new StoreOperationalStatisticsVo.ServiceQualityData();
|
|
|
+ }
|
|
|
+ if (current == null) {
|
|
|
+ current = new StoreOperationalStatisticsVo.ServiceQualityData();
|
|
|
+ }
|
|
|
+ comparison.setStoreRating(buildComparisonData(current.getStoreRating(), previous.getStoreRating()));
|
|
|
+ comparison.setScoreOne(buildComparisonData(current.getScoreOne(), previous.getScoreOne()));
|
|
|
+ comparison.setScoreTwo(buildComparisonData(current.getScoreTwo(), previous.getScoreTwo()));
|
|
|
+ comparison.setScoreThree(buildComparisonData(current.getScoreThree(), previous.getScoreThree()));
|
|
|
+ comparison.setTotalReviews(buildComparisonData(current.getTotalReviews(), previous.getTotalReviews()));
|
|
|
+ comparison.setPositiveReviews(buildComparisonData(current.getPositiveReviews(), previous.getPositiveReviews()));
|
|
|
+ comparison.setNeutralReviews(buildComparisonData(current.getNeutralReviews(), previous.getNeutralReviews()));
|
|
|
+ comparison.setNegativeReviews(buildComparisonData(current.getNegativeReviews(), previous.getNegativeReviews()));
|
|
|
+ comparison.setNegativeReviewRatio(buildComparisonData(current.getNegativeReviewRatio(), previous.getNegativeReviewRatio()));
|
|
|
+ comparison.setNegativeReviewAppealsCount(buildComparisonData(current.getNegativeReviewAppealsCount(), previous.getNegativeReviewAppealsCount()));
|
|
|
+ comparison.setNegativeReviewAppealsSuccessCount(buildComparisonData(current.getNegativeReviewAppealsSuccessCount(), previous.getNegativeReviewAppealsSuccessCount()));
|
|
|
+ comparison.setNegativeReviewAppealsSuccessRatio(buildComparisonData(current.getNegativeReviewAppealsSuccessRatio(), previous.getNegativeReviewAppealsSuccessRatio()));
|
|
|
+ return comparison;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建价目表排名数据对比
|
|
|
+ */
|
|
|
+ private List<StoreOperationalStatisticsComparisonVo.PriceListRankingComparison> buildPriceListRankingComparison(
|
|
|
+ List<StoreOperationalStatisticsVo.PriceListRanking> current, List<StoreOperationalStatisticsVo.PriceListRanking> previous, Integer storeId) {
|
|
|
+ List<StoreOperationalStatisticsComparisonVo.PriceListRankingComparison> result = new ArrayList<>();
|
|
|
+
|
|
|
+ // 如果当期数据为空,返回空列表
|
|
|
+ if (current == null || current.isEmpty()) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果上期数据为空,创建空列表
|
|
|
+ if (previous == null) {
|
|
|
+ previous = new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建上期数据的Map,以priceId为key,方便查找
|
|
|
+ Map<Integer, StoreOperationalStatisticsVo.PriceListRanking> previousMap = new HashMap<>();
|
|
|
+ for (StoreOperationalStatisticsVo.PriceListRanking prev : previous) {
|
|
|
+ if (prev.getPriceId() != null) {
|
|
|
+ previousMap.put(prev.getPriceId(), prev);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 收集需要查询名称的priceId(如果名称为空)
|
|
|
+ List<Integer> needQueryPriceIds = new ArrayList<>();
|
|
|
+ for (StoreOperationalStatisticsVo.PriceListRanking curr : current) {
|
|
|
+ if (curr.getPriceId() != null &&
|
|
|
+ (curr.getPriceListItemName() == null || curr.getPriceListItemName().trim().isEmpty())) {
|
|
|
+ needQueryPriceIds.add(curr.getPriceId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 批量查询价目表名称(根据business_section判断查询美食价目表还是通用价目表)
|
|
|
+ Map<Integer, String> priceNameMap = new HashMap<>();
|
|
|
+ if (!needQueryPriceIds.isEmpty() && storeId != null) {
|
|
|
+ // 查询店铺信息,获取business_section
|
|
|
+ StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
|
|
|
+ if (storeInfo != null && storeInfo.getBusinessSection() != null && storeInfo.getBusinessSection() == 1) {
|
|
|
+ // business_section = 1 表示美食,查询美食价目表
|
|
|
+ LambdaQueryWrapper<StoreCuisine> cuisineWrapper = new LambdaQueryWrapper<>();
|
|
|
+ cuisineWrapper.in(StoreCuisine::getId, needQueryPriceIds)
|
|
|
+ .eq(StoreCuisine::getDeleteFlag, 0);
|
|
|
+ List<StoreCuisine> cuisines = storeCuisineMapper.selectList(cuisineWrapper);
|
|
|
+ for (StoreCuisine cuisine : cuisines) {
|
|
|
+ if (cuisine.getId() != null && cuisine.getName() != null) {
|
|
|
+ priceNameMap.put(cuisine.getId(), cuisine.getName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 其他情况查询通用价目表
|
|
|
+ LambdaQueryWrapper<StorePrice> priceWrapper = new LambdaQueryWrapper<>();
|
|
|
+ priceWrapper.in(StorePrice::getId, needQueryPriceIds)
|
|
|
+ .eq(StorePrice::getDeleteFlag, 0);
|
|
|
+ List<StorePrice> prices = storePriceMapper.selectList(priceWrapper);
|
|
|
+ for (StorePrice price : prices) {
|
|
|
+ if (price.getId() != null && price.getName() != null) {
|
|
|
+ priceNameMap.put(price.getId(), price.getName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 遍历当期数据,构建对比
|
|
|
+ for (StoreOperationalStatisticsVo.PriceListRanking curr : current) {
|
|
|
+ StoreOperationalStatisticsComparisonVo.PriceListRankingComparison comparison =
|
|
|
+ new StoreOperationalStatisticsComparisonVo.PriceListRankingComparison();
|
|
|
+
|
|
|
+ comparison.setPriceId(curr.getPriceId());
|
|
|
+
|
|
|
+ // 设置价目表名称,如果为空则从查询结果中获取
|
|
|
+ String priceListItemName = curr.getPriceListItemName();
|
|
|
+ if (priceListItemName == null || priceListItemName.trim().isEmpty()) {
|
|
|
+ priceListItemName = priceNameMap.get(curr.getPriceId());
|
|
|
+ }
|
|
|
+ comparison.setPriceListItemName(priceListItemName);
|
|
|
+
|
|
|
+ // 获取上期对应的价目表数据
|
|
|
+ StoreOperationalStatisticsVo.PriceListRanking prev = previousMap.get(curr.getPriceId());
|
|
|
+
|
|
|
+ // 构建浏览量对比
|
|
|
+ Long currentPageViews = curr.getPageViews() != null ? curr.getPageViews() : 0L;
|
|
|
+ Long previousPageViews = (prev != null && prev.getPageViews() != null) ? prev.getPageViews() : 0L;
|
|
|
+ comparison.setPageViews(buildComparisonData(currentPageViews, previousPageViews));
|
|
|
+
|
|
|
+ // 构建访客数对比
|
|
|
+ Long currentVisitors = curr.getVisitors() != null ? curr.getVisitors() : 0L;
|
|
|
+ Long previousVisitors = (prev != null && prev.getVisitors() != null) ? prev.getVisitors() : 0L;
|
|
|
+ comparison.setVisitors(buildComparisonData(currentVisitors, previousVisitors));
|
|
|
+
|
|
|
+ // 构建分享数对比
|
|
|
+ Long currentShares = curr.getShares() != null ? curr.getShares() : 0L;
|
|
|
+ Long previousShares = (prev != null && prev.getShares() != null) ? prev.getShares() : 0L;
|
|
|
+ comparison.setShares(buildComparisonData(currentShares, previousShares));
|
|
|
+
|
|
|
+ result.add(comparison);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 异步调用AI接口进行数据分析
|
|
|
+ *
|
|
|
+ * @param historyId 历史记录ID
|
|
|
+ * @param storeId 店铺ID
|
|
|
+ * @param comparison 统计数据对比
|
|
|
+ */
|
|
|
+ private void callAiAnalysisAsync(Integer historyId, Integer storeId, StoreOperationalStatisticsComparisonVo comparison) {
|
|
|
+ // 如果AI接口URL未配置,跳过调用
|
|
|
+ if (!StringUtils.hasText(aiStatisticsAnalysisUrl)) {
|
|
|
+ log.warn("AI统计分析接口URL未配置,跳过AI分析 - historyId={}", historyId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取访问令牌
|
|
|
+ String accessToken = aiAuthTokenUtil.getAccessToken();
|
|
|
+ if (!StringUtils.hasText(accessToken)) {
|
|
|
+ log.error("调用AI分析接口失败,获取accessToken失败 - historyId={}", historyId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建请求体,只发送id
|
|
|
+ Map<String, Object> requestBody = new HashMap<>();
|
|
|
+ requestBody.put("id", historyId);
|
|
|
+
|
|
|
+ // 构建请求头
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ headers.set("Authorization", "Bearer " + accessToken);
|
|
|
+
|
|
|
+ log.info(requestBody.toString());
|
|
|
+
|
|
|
+ HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
|
|
|
+
|
|
|
+ log.info("开始调用AI统计分析接口 - historyId={}, storeId={}", historyId, storeId);
|
|
|
+ ResponseEntity<String> response = restTemplate.postForEntity(
|
|
|
+ aiStatisticsAnalysisUrl != null ? aiStatisticsAnalysisUrl : "", request, String.class);
|
|
|
+
|
|
|
+ if (response != null && response.getStatusCode() == HttpStatus.OK) {
|
|
|
+ String responseBody = response.getBody();
|
|
|
+ log.info("AI统计分析接口调用成功 - historyId={}, response={}", historyId, responseBody);
|
|
|
+
|
|
|
+ // 解析AI返回的结果
|
|
|
+ if (StringUtils.hasText(responseBody)) {
|
|
|
+ try {
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ Map<String, Object> responseMap = (Map<String, Object>) JSON.parseObject(responseBody, Map.class);
|
|
|
+ if (responseMap != null) {
|
|
|
+ String summary = extractStringValue(responseMap, "summary");
|
|
|
+ String optimizationSuggestions = extractStringValue(responseMap, "optimizationSuggestions");
|
|
|
+
|
|
|
+ // 更新历史记录的AI分析结果
|
|
|
+ if (StringUtils.hasText(summary) || StringUtils.hasText(optimizationSuggestions)) {
|
|
|
+ updateHistoryAiAnalysis(historyId, 1, summary, optimizationSuggestions);
|
|
|
+ log.info("更新AI分析结果成功 - historyId={}", historyId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析AI分析结果失败 - historyId={}, responseBody={}, error={}",
|
|
|
+ historyId, responseBody, e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.warn("AI统计分析接口调用失败 - historyId={}, statusCode={}",
|
|
|
+ historyId, response != null ? response.getStatusCode() : null);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("调用AI统计分析接口异常 - historyId={}, error={}", historyId, e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建对比数据
|
|
|
+ */
|
|
|
+ private StoreOperationalStatisticsComparisonVo.BaseComparisonData buildComparisonData(Object current, Object previous) {
|
|
|
+ StoreOperationalStatisticsComparisonVo.BaseComparisonData comparisonData = new StoreOperationalStatisticsComparisonVo.BaseComparisonData();
|
|
|
+ comparisonData.setCurrent(current);
|
|
|
+ comparisonData.setPrevious(previous);
|
|
|
+
|
|
|
+ // 计算变化率
|
|
|
+ BigDecimal changeRate = calculateChangeRate(current, previous);
|
|
|
+ comparisonData.setChangeRate(changeRate);
|
|
|
+
|
|
|
+ return comparisonData;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算变化率(百分比)
|
|
|
+ */
|
|
|
+ private BigDecimal calculateChangeRate(Object current, Object previous) {
|
|
|
+ BigDecimal currentValue = toBigDecimal(current);
|
|
|
+ BigDecimal previousValue = toBigDecimal(previous);
|
|
|
+
|
|
|
+ if (previousValue == null || previousValue.compareTo(BigDecimal.ZERO) == 0) {
|
|
|
+ if (currentValue != null && currentValue.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ return BigDecimal.valueOf(100); // 从0增长,视为100%增长
|
|
|
+ }
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (currentValue == null) {
|
|
|
+ currentValue = BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 变化率 = ((当期 - 上期) / 上期) * 100
|
|
|
+ BigDecimal change = currentValue.subtract(previousValue);
|
|
|
+ BigDecimal rate = change.divide(previousValue, 4, RoundingMode.HALF_UP)
|
|
|
+ .multiply(BigDecimal.valueOf(100));
|
|
|
+ return rate.setScale(2, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将对象转换为BigDecimal
|
|
|
+ */
|
|
|
+ private BigDecimal toBigDecimal(Object value) {
|
|
|
+ if (value == null) {
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ if (value instanceof BigDecimal) {
|
|
|
+ return (BigDecimal) value;
|
|
|
+ }
|
|
|
+ if (value instanceof Number) {
|
|
|
+ return BigDecimal.valueOf(((Number) value).doubleValue());
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return new BigDecimal(value.toString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean updateHistoryAiAnalysis(Integer historyId, Integer aiAnalysisCompleted, String summary, String optimizationSuggestions) {
|
|
|
+ log.info("StoreOperationalStatisticsServiceImpl.updateHistoryAiAnalysis - historyId={}, aiAnalysisCompleted={}", historyId, aiAnalysisCompleted);
|
|
|
+
|
|
|
+ if (historyId == null || historyId <= 0) {
|
|
|
+ log.warn("更新历史统计记录AI分析结果失败,历史记录ID无效 - historyId={}", historyId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 查询历史记录是否存在且未删除
|
|
|
+ StoreOperationalStatisticsHistory history = statisticsHistoryMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<StoreOperationalStatisticsHistory>()
|
|
|
+ .eq(StoreOperationalStatisticsHistory::getId, historyId)
|
|
|
+ .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0));
|
|
|
+
|
|
|
+ if (history == null) {
|
|
|
+ log.warn("更新历史统计记录AI分析结果失败,历史记录不存在或已删除 - historyId={}", historyId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新AI分析相关字段
|
|
|
+ LambdaUpdateWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper.eq(StoreOperationalStatisticsHistory::getId, historyId)
|
|
|
+ .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0);
|
|
|
+
|
|
|
+ if (aiAnalysisCompleted != null) {
|
|
|
+ wrapper.set(StoreOperationalStatisticsHistory::getAiAnalysisCompleted, aiAnalysisCompleted);
|
|
|
+ }
|
|
|
+ if (summary != null) {
|
|
|
+ wrapper.set(StoreOperationalStatisticsHistory::getSummary, summary.trim());
|
|
|
+ }
|
|
|
+ if (optimizationSuggestions != null) {
|
|
|
+ wrapper.set(StoreOperationalStatisticsHistory::getOptimizationSuggestions, optimizationSuggestions.trim());
|
|
|
+ }
|
|
|
+
|
|
|
+ int result = statisticsHistoryMapper.update(null, wrapper);
|
|
|
+ if (result > 0) {
|
|
|
+ log.info("更新历史统计记录AI分析结果成功 - historyId={}, aiAnalysisCompleted={}", historyId, aiAnalysisCompleted);
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ log.warn("更新历史统计记录AI分析结果失败,未更新任何记录 - historyId={}", historyId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("更新历史统计记录AI分析结果失败 - historyId={}, error={}", historyId, e.getMessage(), e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从Map中提取String值
|
|
|
+ */
|
|
|
+ private String extractStringValue(Map<String, Object> map, String key) {
|
|
|
+ if (map == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ Object value = map.get(key);
|
|
|
+ if (value == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return value.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存对比统计数据到历史表
|
|
|
+ * @param storeId 店铺ID
|
|
|
+ * @param currentStartTime 当期开始时间
|
|
|
+ * @param currentEndTime 当期结束时间
|
|
|
+ * @param previousStartTime 上期开始时间
|
|
|
+ * @param previousEndTime 上期结束时间
|
|
|
+ * @param comparison 对比数据
|
|
|
+ * @param pdfUrl PDF文件URL(可选)
|
|
|
+ * @return 历史记录ID,保存失败返回null
|
|
|
+ */
|
|
|
+ private Integer saveStatisticsHistory(Integer storeId, String currentStartTime, String currentEndTime,
|
|
|
+ String previousStartTime, String previousEndTime,
|
|
|
+ StoreOperationalStatisticsComparisonVo comparison, String pdfUrl) {
|
|
|
+ try {
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
|
|
|
+ Date startDate = sdf.parse(currentStartTime);
|
|
|
+ Date endDate = sdf.parse(currentEndTime);
|
|
|
+ Date previousStartDate = sdf.parse(previousStartTime);
|
|
|
+ Date previousEndDate = sdf.parse(previousEndTime);
|
|
|
+
|
|
|
+ StoreOperationalStatisticsHistory history = new StoreOperationalStatisticsHistory();
|
|
|
+ history.setStoreId(storeId);
|
|
|
+ history.setStartTime(startDate);
|
|
|
+ history.setEndTime(endDate);
|
|
|
+ history.setPreviousStartTime(previousStartDate);
|
|
|
+ history.setPreviousEndTime(previousEndDate);
|
|
|
+ history.setQueryTime(new Date());
|
|
|
+
|
|
|
+ // 将对比统计数据序列化为JSON字符串(包含当期、上期和变化率等完整对比数据)
|
|
|
+ String statisticsJson = JSON.toJSONString(comparison);
|
|
|
+ history.setStatisticsData(statisticsJson);
|
|
|
+
|
|
|
+ // 如果提供了PDF URL,则保存
|
|
|
+ if (pdfUrl != null && !pdfUrl.trim().isEmpty()) {
|
|
|
+ history.setPdfUrl(pdfUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ statisticsHistoryMapper.insert(history);
|
|
|
+ log.info("保存对比统计数据到历史表成功 - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}, historyId={}",
|
|
|
+ storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime, history.getId());
|
|
|
+ return history.getId();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("保存对比统计数据到历史表失败 - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}, error={}",
|
|
|
+ storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime, e.getMessage(), e);
|
|
|
+ // 保存失败不影响主流程,只记录日志
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算统计数据(从store_track_statistics表查询并累加)
|
|
|
+ */
|
|
|
+ private StoreOperationalStatisticsVo calculateStatistics(Integer storeId, String startTime, String endTime) {
|
|
|
+ try {
|
|
|
+ // 解析时间范围
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
|
|
|
+ Date startDate = sdf.parse(startTime);
|
|
|
+ Date endDate = sdf.parse(endTime);
|
|
|
+
|
|
|
+ // 查询指定时间范围内的日统计数据
|
|
|
+ LambdaQueryWrapper<StoreTrackStatistics> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreTrackStatistics::getStoreId, storeId)
|
|
|
+ .eq(StoreTrackStatistics::getStatType, STAT_TYPE_DAILY)
|
|
|
+ .ge(StoreTrackStatistics::getStatDate, startDate)
|
|
|
+ .le(StoreTrackStatistics::getStatDate, endDate);
|
|
|
+
|
|
|
+ List<StoreTrackStatistics> statisticsList = storeTrackStatisticsMapper.selectList(wrapper);
|
|
|
+
|
|
|
+ if (statisticsList == null || statisticsList.isEmpty()) {
|
|
|
+ log.warn("未查询到统计数据 - storeId={}, startTime={}, endTime={}", storeId, startTime, endTime);
|
|
|
+ return new StoreOperationalStatisticsVo();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 聚合统计数据(优先取结束日期的数据,只累加新增访客数)
|
|
|
+ return aggregateStatistics(statisticsList, endDate, storeId);
|
|
|
+
|
|
|
+ } catch (ParseException e) {
|
|
|
+ log.error("StoreOperationalStatisticsServiceImpl.calculateStatistics - 时间解析错误", e);
|
|
|
+ throw new RuntimeException("时间格式错误: " + e.getMessage());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算统计数据失败 - storeId={}, startTime={}, endTime={}, error={}",
|
|
|
+ storeId, startTime, endTime, e.getMessage(), e);
|
|
|
+ throw new RuntimeException("计算统计数据失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 聚合统计数据(优先取结束日期的数据,只累加流量数据中的新增访客数,其他数据取结束日期的记录)
|
|
|
+ *
|
|
|
+ * @param statisticsList 统计数据列表
|
|
|
+ * @param endDate 结束日期(用于优先选择该日期的数据)
|
|
|
+ * @param storeId 店铺ID
|
|
|
+ */
|
|
|
+ private StoreOperationalStatisticsVo aggregateStatistics(List<StoreTrackStatistics> statisticsList, Date endDate, Integer storeId) {
|
|
|
+ StoreOperationalStatisticsVo result = new StoreOperationalStatisticsVo();
|
|
|
+
|
|
|
+ // 优先查找结束日期的数据(比较日期部分,忽略时间)
|
|
|
+ StoreTrackStatistics endDateStat = null;
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
|
|
|
+ String endDateStr = sdf.format(endDate);
|
|
|
+ for (StoreTrackStatistics stat : statisticsList) {
|
|
|
+ if (stat.getStatDate() != null) {
|
|
|
+ String statDateStr = sdf.format(stat.getStatDate());
|
|
|
+ if (endDateStr.equals(statDateStr)) {
|
|
|
+ endDateStat = stat;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断结束日期的数据是否有效(不全为0)
|
|
|
+ boolean endDateStatValid = false;
|
|
|
+ if (endDateStat != null) {
|
|
|
+ endDateStatValid = isStatisticsValid(endDateStat);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有结束日期的数据,或者结束日期的数据全是0,则按日期排序,取有数据的最新日期
|
|
|
+ StoreTrackStatistics latestStat = null;
|
|
|
+ if (endDateStat != null && endDateStatValid) {
|
|
|
+ latestStat = endDateStat;
|
|
|
+ } else {
|
|
|
+ // 按日期排序,从最新到最旧
|
|
|
+ statisticsList.sort((a, b) -> {
|
|
|
+ if (a.getStatDate() == null && b.getStatDate() == null) return 0;
|
|
|
+ if (a.getStatDate() == null) return 1;
|
|
|
+ if (b.getStatDate() == null) return -1;
|
|
|
+ return b.getStatDate().compareTo(a.getStatDate());
|
|
|
+ });
|
|
|
+
|
|
|
+ // 查找第一个有数据的记录
|
|
|
+ for (StoreTrackStatistics stat : statisticsList) {
|
|
|
+ if (isStatisticsValid(stat)) {
|
|
|
+ latestStat = stat;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果所有记录都无效,使用最新的记录(即使全是0)
|
|
|
+ if (latestStat == null && !statisticsList.isEmpty()) {
|
|
|
+ latestStat = statisticsList.get(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (latestStat != null) {
|
|
|
+ if (endDateStat != null && !endDateStatValid) {
|
|
|
+ log.info("结束日期{}的数据全是0,使用范围内有数据的最新日期{}的数据",
|
|
|
+ endDateStr,
|
|
|
+ latestStat.getStatDate() != null ? sdf.format(latestStat.getStatDate()) : "未知");
|
|
|
+ } else {
|
|
|
+ log.info("结束日期{}没有统计数据,使用范围内最新日期{}的数据",
|
|
|
+ endDateStr,
|
|
|
+ latestStat.getStatDate() != null ? sdf.format(latestStat.getStatDate()) : "未知");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果找不到有效数据,返回空结果
|
|
|
+ if (latestStat == null) {
|
|
|
+ log.warn("未找到有效的统计数据,返回空结果");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 只累加流量数据中的新增访客数
|
|
|
+ long newVisitorCountSum = 0L;
|
|
|
+ for (StoreTrackStatistics stat : statisticsList) {
|
|
|
+ if (stat.getTrafficData() != null && !stat.getTrafficData().isEmpty()) {
|
|
|
+ try {
|
|
|
+ Map<String, Object> trafficData = (Map<String, Object>) JSON.parseObject(stat.getTrafficData(), Map.class);
|
|
|
+ if (trafficData != null && trafficData.containsKey("newVisitorCount")) {
|
|
|
+ newVisitorCountSum += getLongValue(trafficData, "newVisitorCount");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析流量数据失败: {}", stat.getTrafficData(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用最新记录的数据,但替换新增访客数为累加值
|
|
|
+ if (latestStat.getTrafficData() != null && !latestStat.getTrafficData().isEmpty()) {
|
|
|
+ try {
|
|
|
+ Map<String, Object> latestTrafficData = (Map<String, Object>) JSON.parseObject(latestStat.getTrafficData(), Map.class);
|
|
|
+ if (latestTrafficData != null) {
|
|
|
+ latestTrafficData.put("newVisitorCount", newVisitorCountSum);
|
|
|
+ result.setTrafficData(convertToTrafficDataVoFromMap(latestTrafficData));
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析最新流量数据失败: {}", latestStat.getTrafficData(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他数据直接使用最新记录的值
|
|
|
+ if (latestStat.getInteractionData() != null && !latestStat.getInteractionData().isEmpty()) {
|
|
|
+ result.setInteractionData(convertToInteractionDataVoFromJson(latestStat.getInteractionData()));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (latestStat.getCouponData() != null && !latestStat.getCouponData().isEmpty()) {
|
|
|
+ result.setCouponData(convertToCouponDataVoFromJson(latestStat.getCouponData()));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (latestStat.getVoucherData() != null && !latestStat.getVoucherData().isEmpty()) {
|
|
|
+ result.setVoucherData(convertToVoucherDataVoFromJson(latestStat.getVoucherData()));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (latestStat.getServiceData() != null && !latestStat.getServiceData().isEmpty()) {
|
|
|
+ result.setServiceQualityData(convertToServiceQualityDataVoFromJson(latestStat.getServiceData()));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (latestStat.getPriceRankingData() != null && !latestStat.getPriceRankingData().isEmpty()) {
|
|
|
+ result.setPriceListRanking(convertToPriceListRankingVoFromJson(latestStat.getPriceRankingData(), storeId));
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 工具方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断统计数据是否有效(不全为0)
|
|
|
+ * 检查流量数据、互动数据、优惠券数据、代金券数据、服务质量数据、价目表排名数据中是否至少有一个字段不为0
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private boolean isStatisticsValid(StoreTrackStatistics stat) {
|
|
|
+ if (stat == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 检查流量数据
|
|
|
+ if (stat.getTrafficData() != null && !stat.getTrafficData().isEmpty()) {
|
|
|
+ Map<String, Object> trafficData = (Map<String, Object>) JSON.parseObject(stat.getTrafficData(), Map.class);
|
|
|
+ if (trafficData != null) {
|
|
|
+ long searchCount = getLongValue(trafficData, "searchCount");
|
|
|
+ long viewCount = getLongValue(trafficData, "viewCount");
|
|
|
+ long visitorCount = getLongValue(trafficData, "visitorCount");
|
|
|
+ long newVisitorCount = getLongValue(trafficData, "newVisitorCount");
|
|
|
+ long totalDuration = getLongValue(trafficData, "totalDuration");
|
|
|
+ if (searchCount > 0 || viewCount > 0 || visitorCount > 0 || newVisitorCount > 0 || totalDuration > 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查互动数据
|
|
|
+ if (stat.getInteractionData() != null && !stat.getInteractionData().isEmpty()) {
|
|
|
+ Map<String, Object> interactionData = (Map<String, Object>) JSON.parseObject(stat.getInteractionData(), Map.class);
|
|
|
+ if (interactionData != null) {
|
|
|
+ long collectCount = getLongValue(interactionData, "collectCount");
|
|
|
+ long shareCount = getLongValue(interactionData, "shareCount");
|
|
|
+ long checkinCount = getLongValue(interactionData, "checkinCount");
|
|
|
+ long consultCount = getLongValue(interactionData, "consultCount");
|
|
|
+ long friendCount = getLongValue(interactionData, "friendCount");
|
|
|
+ long followCount = getLongValue(interactionData, "followCount");
|
|
|
+ long fansCount = getLongValue(interactionData, "fansCount");
|
|
|
+ long postCount = getLongValue(interactionData, "postCount");
|
|
|
+ long postLikeCount = getLongValue(interactionData, "postLikeCount");
|
|
|
+ long postCommentCount = getLongValue(interactionData, "postCommentCount");
|
|
|
+ long postRepostCount = getLongValue(interactionData, "postRepostCount");
|
|
|
+ if (collectCount > 0 || shareCount > 0 || checkinCount > 0 || consultCount > 0 ||
|
|
|
+ friendCount > 0 || followCount > 0 || fansCount > 0 || postCount > 0 ||
|
|
|
+ postLikeCount > 0 || postCommentCount > 0 || postRepostCount > 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查优惠券数据
|
|
|
+ if (stat.getCouponData() != null && !stat.getCouponData().isEmpty()) {
|
|
|
+ Map<String, Object> couponData = (Map<String, Object>) JSON.parseObject(stat.getCouponData(), Map.class);
|
|
|
+ if (couponData != null) {
|
|
|
+ long giveToFriendCount = getLongValue(couponData, "giveToFriendCount");
|
|
|
+ BigDecimal giveToFriendAmount = getBigDecimalValue(couponData, "giveToFriendAmount");
|
|
|
+ long friendGiveCount = getLongValue(couponData, "friendGiveCount");
|
|
|
+ BigDecimal friendGiveAmount = getBigDecimalValue(couponData, "friendGiveAmount");
|
|
|
+ if (giveToFriendCount > 0 || giveToFriendAmount.compareTo(BigDecimal.ZERO) > 0 ||
|
|
|
+ friendGiveCount > 0 || friendGiveAmount.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查代金券数据
|
|
|
+ if (stat.getVoucherData() != null && !stat.getVoucherData().isEmpty()) {
|
|
|
+ Map<String, Object> voucherData = (Map<String, Object>) JSON.parseObject(stat.getVoucherData(), Map.class);
|
|
|
+ if (voucherData != null) {
|
|
|
+ long giveToFriendCount = getLongValue(voucherData, "giveToFriendCount");
|
|
|
+ BigDecimal giveToFriendAmount = getBigDecimalValue(voucherData, "giveToFriendAmount");
|
|
|
+ long friendGiveCount = getLongValue(voucherData, "friendGiveCount");
|
|
|
+ BigDecimal friendGiveAmount = getBigDecimalValue(voucherData, "friendGiveAmount");
|
|
|
+ if (giveToFriendCount > 0 || giveToFriendAmount.compareTo(BigDecimal.ZERO) > 0 ||
|
|
|
+ friendGiveCount > 0 || friendGiveAmount.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查服务质量数据
|
|
|
+ if (stat.getServiceData() != null && !stat.getServiceData().isEmpty()) {
|
|
|
+ Map<String, Object> serviceData = (Map<String, Object>) JSON.parseObject(stat.getServiceData(), Map.class);
|
|
|
+ if (serviceData != null) {
|
|
|
+ double storeScore = getBigDecimalValue(serviceData, "storeScore").doubleValue();
|
|
|
+ long ratingCount = getLongValue(serviceData, "ratingCount");
|
|
|
+ long appealCount = getLongValue(serviceData, "appealCount");
|
|
|
+ if (storeScore > 0 || ratingCount > 0 || appealCount > 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查价目表排名数据
|
|
|
+ if (stat.getPriceRankingData() != null && !stat.getPriceRankingData().isEmpty()) {
|
|
|
+ List<Map<String, Object>> priceRankingData = (List<Map<String, Object>>) (List<?>) JSON.parseArray(stat.getPriceRankingData());
|
|
|
+ if (priceRankingData != null && !priceRankingData.isEmpty()) {
|
|
|
+ for (Map<String, Object> item : priceRankingData) {
|
|
|
+ long viewCount = getLongValue(item, "viewCount");
|
|
|
+ long visitorCount = getLongValue(item, "visitorCount");
|
|
|
+ long shareCount = getLongValue(item, "shareCount");
|
|
|
+ if (viewCount > 0 || visitorCount > 0 || shareCount > 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("判断统计数据有效性失败: {}", e.getMessage(), e);
|
|
|
+ // 如果解析失败,认为数据无效
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从Map中获取Long值
|
|
|
+ */
|
|
|
+ private Long getLongValue(Map<String, Object> map, String key) {
|
|
|
+ Object value = map.get(key);
|
|
|
+ if (value == null) return 0L;
|
|
|
+ if (value instanceof Long) return (Long) value;
|
|
|
+ if (value instanceof Number) return ((Number) value).longValue();
|
|
|
+ try {
|
|
|
+ return Long.parseLong(value.toString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ return 0L;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 从JSON/Map直接转换的方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从Map转换为流量数据VO
|
|
|
+ */
|
|
|
+ private StoreOperationalStatisticsVo.TrafficData convertToTrafficDataVoFromMap(Map<String, Object> trafficDataMap) {
|
|
|
+ StoreOperationalStatisticsVo.TrafficData vo = new StoreOperationalStatisticsVo.TrafficData();
|
|
|
+ vo.setStoreSearchVolume(getLongValue(trafficDataMap, "searchCount"));
|
|
|
+ vo.setPageViews(getLongValue(trafficDataMap, "viewCount"));
|
|
|
+ vo.setVisitors(getLongValue(trafficDataMap, "visitorCount"));
|
|
|
+ vo.setNewVisitors(getLongValue(trafficDataMap, "newVisitorCount"));
|
|
|
+ // 访问时长从毫秒转换为秒
|
|
|
+ Long totalDuration = getLongValue(trafficDataMap, "totalDuration");
|
|
|
+ vo.setVisitDuration(totalDuration / 1000);
|
|
|
+ Long avgDuration = getLongValue(trafficDataMap, "avgDuration");
|
|
|
+ vo.setAvgVisitDuration(avgDuration / 1000);
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从JSON字符串转换为互动数据VO
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private StoreOperationalStatisticsVo.InteractionData convertToInteractionDataVoFromJson(String interactionDataJson) {
|
|
|
+ try {
|
|
|
+ Map<String, Object> data = (Map<String, Object>) JSON.parseObject(interactionDataJson, Map.class);
|
|
|
+ if (data == null) return new StoreOperationalStatisticsVo.InteractionData();
|
|
|
+
|
|
|
+ StoreOperationalStatisticsVo.InteractionData vo = new StoreOperationalStatisticsVo.InteractionData();
|
|
|
+ vo.setStoreCollectionCount(getLongValue(data, "collectCount"));
|
|
|
+ vo.setStoreShareCount(getLongValue(data, "shareCount"));
|
|
|
+ vo.setStoreCheckInCount(getLongValue(data, "checkinCount"));
|
|
|
+ vo.setConsultMerchantCount(getLongValue(data, "consultCount"));
|
|
|
+ vo.setFriendsCount(getLongValue(data, "friendCount"));
|
|
|
+ vo.setFollowCount(getLongValue(data, "followCount"));
|
|
|
+ vo.setFansCount(getLongValue(data, "fansCount"));
|
|
|
+ vo.setPostsPublishedCount(getLongValue(data, "postCount"));
|
|
|
+ vo.setPostLikesCount(getLongValue(data, "postLikeCount"));
|
|
|
+ vo.setPostCommentsCount(getLongValue(data, "postCommentCount"));
|
|
|
+ vo.setPostSharesCount(getLongValue(data, "postRepostCount"));
|
|
|
+ vo.setReportedCount(getLongValue(data, "reportCount"));
|
|
|
+ vo.setBlockedCount(getLongValue(data, "blockCount"));
|
|
|
+ return vo;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析互动数据失败: {}", interactionDataJson, e);
|
|
|
+ return new StoreOperationalStatisticsVo.InteractionData();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从JSON字符串转换为优惠券数据VO
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private StoreOperationalStatisticsVo.CouponData convertToCouponDataVoFromJson(String couponDataJson) {
|
|
|
+ try {
|
|
|
+ Map<String, Object> data = (Map<String, Object>) JSON.parseObject(couponDataJson, Map.class);
|
|
|
+ if (data == null) return new StoreOperationalStatisticsVo.CouponData();
|
|
|
+
|
|
|
+ StoreOperationalStatisticsVo.CouponData vo = new StoreOperationalStatisticsVo.CouponData();
|
|
|
+ vo.setGiftToFriendsCount(getLongValue(data, "giveToFriendCount"));
|
|
|
+ vo.setGiftToFriendsAmount(getBigDecimalValue(data, "giveToFriendAmount"));
|
|
|
+ vo.setGiftToFriendsUsedCount(getLongValue(data, "giveToFriendUseCount"));
|
|
|
+ vo.setGiftToFriendsUsedAmount(getBigDecimalValue(data, "giveToFriendUseAmount"));
|
|
|
+
|
|
|
+ // 使用金额占比
|
|
|
+ Object useAmountPercent = data.get("giveToFriendUseAmountPercent");
|
|
|
+ if (useAmountPercent != null) {
|
|
|
+ if (useAmountPercent instanceof Number) {
|
|
|
+ vo.setGiftToFriendsUsedAmountRatio(BigDecimal.valueOf(((Number) useAmountPercent).doubleValue()));
|
|
|
+ } else {
|
|
|
+ vo.setGiftToFriendsUsedAmountRatio(new BigDecimal(useAmountPercent.toString()));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vo.setGiftToFriendsUsedAmountRatio(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ vo.setFriendsGiftCount(getLongValue(data, "friendGiveCount"));
|
|
|
+ vo.setFriendsGiftAmount(getBigDecimalValue(data, "friendGiveAmount"));
|
|
|
+ vo.setFriendsGiftUsedCount(getLongValue(data, "friendGiveUseCount"));
|
|
|
+ vo.setFriendsGiftUsedAmount(getBigDecimalValue(data, "friendGiveUseAmount"));
|
|
|
+
|
|
|
+ // 好友赠送使用金额占比
|
|
|
+ Object friendUseAmountPercent = data.get("friendGiveUseAmountPercent");
|
|
|
+ if (friendUseAmountPercent != null) {
|
|
|
+ if (friendUseAmountPercent instanceof Number) {
|
|
|
+ vo.setFriendsGiftUsedAmountRatio(BigDecimal.valueOf(((Number) friendUseAmountPercent).doubleValue()));
|
|
|
+ } else {
|
|
|
+ vo.setFriendsGiftUsedAmountRatio(new BigDecimal(friendUseAmountPercent.toString()));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vo.setFriendsGiftUsedAmountRatio(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析优惠券数据失败: {}", couponDataJson, e);
|
|
|
+ return new StoreOperationalStatisticsVo.CouponData();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从JSON字符串转换为代金券数据VO
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private StoreOperationalStatisticsVo.VoucherData convertToVoucherDataVoFromJson(String voucherDataJson) {
|
|
|
+ try {
|
|
|
+ Map<String, Object> data = (Map<String, Object>) JSON.parseObject(voucherDataJson, Map.class);
|
|
|
+ if (data == null) return new StoreOperationalStatisticsVo.VoucherData();
|
|
|
+
|
|
|
+ StoreOperationalStatisticsVo.VoucherData vo = new StoreOperationalStatisticsVo.VoucherData();
|
|
|
+ vo.setGiftToFriendsCount(getLongValue(data, "giveToFriendCount"));
|
|
|
+ vo.setGiftToFriendsAmount(getBigDecimalValue(data, "giveToFriendAmount"));
|
|
|
+ vo.setGiftToFriendsUsedCount(getLongValue(data, "giveToFriendUseCount"));
|
|
|
+ vo.setGiftToFriendsUsedAmount(getBigDecimalValue(data, "giveToFriendUseAmount"));
|
|
|
+
|
|
|
+ // 使用金额占比
|
|
|
+ Object useAmountPercent = data.get("giveToFriendUseAmountPercent");
|
|
|
+ if (useAmountPercent != null) {
|
|
|
+ if (useAmountPercent instanceof Number) {
|
|
|
+ vo.setGiftToFriendsUsedAmountRatio(BigDecimal.valueOf(((Number) useAmountPercent).doubleValue()));
|
|
|
+ } else {
|
|
|
+ vo.setGiftToFriendsUsedAmountRatio(new BigDecimal(useAmountPercent.toString()));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vo.setGiftToFriendsUsedAmountRatio(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ vo.setFriendsGiftCount(getLongValue(data, "friendGiveCount"));
|
|
|
+ vo.setFriendsGiftAmount(getBigDecimalValue(data, "friendGiveAmount"));
|
|
|
+ vo.setFriendsGiftUsedCount(getLongValue(data, "friendGiveUseCount"));
|
|
|
+ vo.setFriendsGiftUsedAmount(getBigDecimalValue(data, "friendGiveUseAmount"));
|
|
|
+
|
|
|
+ // 好友赠送使用金额占比
|
|
|
+ Object friendUseAmountPercent = data.get("friendGiveUseAmountPercent");
|
|
|
+ if (friendUseAmountPercent != null) {
|
|
|
+ if (friendUseAmountPercent instanceof Number) {
|
|
|
+ vo.setFriendsGiftUsedAmountRatio(BigDecimal.valueOf(((Number) friendUseAmountPercent).doubleValue()));
|
|
|
+ } else {
|
|
|
+ vo.setFriendsGiftUsedAmountRatio(new BigDecimal(friendUseAmountPercent.toString()));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vo.setFriendsGiftUsedAmountRatio(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析代金券数据失败: {}", voucherDataJson, e);
|
|
|
+ return new StoreOperationalStatisticsVo.VoucherData();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从JSON字符串转换为服务质量数据VO
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private StoreOperationalStatisticsVo.ServiceQualityData convertToServiceQualityDataVoFromJson(String serviceDataJson) {
|
|
|
+ try {
|
|
|
+ Map<String, Object> data = (Map<String, Object>) JSON.parseObject(serviceDataJson, Map.class);
|
|
|
+ if (data == null) return new StoreOperationalStatisticsVo.ServiceQualityData();
|
|
|
+
|
|
|
+ StoreOperationalStatisticsVo.ServiceQualityData vo = new StoreOperationalStatisticsVo.ServiceQualityData();
|
|
|
+
|
|
|
+ // 评分字段
|
|
|
+ Object storeScore = data.get("storeScore");
|
|
|
+ if (storeScore != null) {
|
|
|
+ if (storeScore instanceof Number) {
|
|
|
+ vo.setStoreRating(BigDecimal.valueOf(((Number) storeScore).doubleValue()).setScale(1, RoundingMode.HALF_UP));
|
|
|
+ } else {
|
|
|
+ vo.setStoreRating(new BigDecimal(storeScore.toString()).setScale(1, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vo.setStoreRating(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ Object scoreOne = data.get("scoreOne");
|
|
|
+ if (scoreOne != null) {
|
|
|
+ if (scoreOne instanceof Number) {
|
|
|
+ vo.setScoreOne(BigDecimal.valueOf(((Number) scoreOne).doubleValue()).setScale(1, RoundingMode.HALF_UP));
|
|
|
+ } else {
|
|
|
+ vo.setScoreOne(new BigDecimal(scoreOne.toString()).setScale(1, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vo.setScoreOne(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ Object scoreTwo = data.get("scoreTwo");
|
|
|
+ if (scoreTwo != null) {
|
|
|
+ if (scoreTwo instanceof Number) {
|
|
|
+ vo.setScoreTwo(BigDecimal.valueOf(((Number) scoreTwo).doubleValue()).setScale(1, RoundingMode.HALF_UP));
|
|
|
+ } else {
|
|
|
+ vo.setScoreTwo(new BigDecimal(scoreTwo.toString()).setScale(1, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vo.setScoreTwo(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ Object scoreThree = data.get("scoreThree");
|
|
|
+ if (scoreThree != null) {
|
|
|
+ if (scoreThree instanceof Number) {
|
|
|
+ vo.setScoreThree(BigDecimal.valueOf(((Number) scoreThree).doubleValue()).setScale(1, RoundingMode.HALF_UP));
|
|
|
+ } else {
|
|
|
+ vo.setScoreThree(new BigDecimal(scoreThree.toString()).setScale(1, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vo.setScoreThree(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ vo.setTotalReviews(getLongValue(data, "ratingCount"));
|
|
|
+ vo.setPositiveReviews(getLongValue(data, "goodRatingCount"));
|
|
|
+ vo.setNeutralReviews(getLongValue(data, "midRatingCount"));
|
|
|
+ vo.setNegativeReviews(getLongValue(data, "badRatingCount"));
|
|
|
+
|
|
|
+ // 差评占比
|
|
|
+ Object badRatingPercent = data.get("badRatingPercent");
|
|
|
+ if (badRatingPercent != null) {
|
|
|
+ if (badRatingPercent instanceof Number) {
|
|
|
+ vo.setNegativeReviewRatio(BigDecimal.valueOf(((Number) badRatingPercent).doubleValue()).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ } else {
|
|
|
+ vo.setNegativeReviewRatio(new BigDecimal(badRatingPercent.toString()).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vo.setNegativeReviewRatio(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ vo.setNegativeReviewAppealsCount(getLongValue(data, "appealCount"));
|
|
|
+ vo.setNegativeReviewAppealsSuccessCount(getLongValue(data, "appealSuccessCount"));
|
|
|
+
|
|
|
+ // 申诉成功占比
|
|
|
+ Object appealSuccessPercent = data.get("appealSuccessPercent");
|
|
|
+ if (appealSuccessPercent != null) {
|
|
|
+ if (appealSuccessPercent instanceof Number) {
|
|
|
+ vo.setNegativeReviewAppealsSuccessRatio(BigDecimal.valueOf(((Number) appealSuccessPercent).doubleValue()).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ } else {
|
|
|
+ vo.setNegativeReviewAppealsSuccessRatio(new BigDecimal(appealSuccessPercent.toString()).setScale(2, RoundingMode.HALF_UP));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ vo.setNegativeReviewAppealsSuccessRatio(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析服务质量数据失败: {}", serviceDataJson, e);
|
|
|
+ return new StoreOperationalStatisticsVo.ServiceQualityData();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从JSON字符串转换为价目表排名数据VO
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private List<StoreOperationalStatisticsVo.PriceListRanking> convertToPriceListRankingVoFromJson(String priceRankingDataJson, Integer storeId) {
|
|
|
+ List<StoreOperationalStatisticsVo.PriceListRanking> result = new ArrayList<>();
|
|
|
+
|
|
|
+ try {
|
|
|
+ List<Map<String, Object>> dataList = (List<Map<String, Object>>) (List<?>) JSON.parseArray(priceRankingDataJson);
|
|
|
+ if (dataList == null || dataList.isEmpty()) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 批量查询价目表名称
|
|
|
+ List<Integer> priceIds = new ArrayList<>();
|
|
|
+ for (Map<String, Object> item : dataList) {
|
|
|
+ Integer priceId = getIntegerValue(item, "priceId");
|
|
|
+ if (priceId != null) {
|
|
|
+ priceIds.add(priceId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<Integer, String> priceNameMap = new HashMap<>();
|
|
|
+ if (!priceIds.isEmpty() && storeId != null) {
|
|
|
+ // 查询店铺信息,获取business_section
|
|
|
+ StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
|
|
|
+ if (storeInfo != null && storeInfo.getBusinessSection() != null && storeInfo.getBusinessSection() == 1) {
|
|
|
+ // business_section = 1 表示美食,查询美食价目表
|
|
|
+ LambdaQueryWrapper<StoreCuisine> cuisineWrapper = new LambdaQueryWrapper<>();
|
|
|
+ cuisineWrapper.in(StoreCuisine::getId, priceIds)
|
|
|
+ .eq(StoreCuisine::getDeleteFlag, 0);
|
|
|
+ List<StoreCuisine> cuisines = storeCuisineMapper.selectList(cuisineWrapper);
|
|
|
+ for (StoreCuisine cuisine : cuisines) {
|
|
|
+ if (cuisine.getId() != null && cuisine.getName() != null) {
|
|
|
+ priceNameMap.put(cuisine.getId(), cuisine.getName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 其他情况查询通用价目表
|
|
|
+ LambdaQueryWrapper<StorePrice> priceWrapper = new LambdaQueryWrapper<>();
|
|
|
+ priceWrapper.in(StorePrice::getId, priceIds)
|
|
|
+ .eq(StorePrice::getDeleteFlag, 0);
|
|
|
+ List<StorePrice> prices = storePriceMapper.selectList(priceWrapper);
|
|
|
+ for (StorePrice price : prices) {
|
|
|
+ if (price.getId() != null && price.getName() != null) {
|
|
|
+ priceNameMap.put(price.getId(), price.getName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转换为VO列表
|
|
|
+ for (Map<String, Object> item : dataList) {
|
|
|
+ StoreOperationalStatisticsVo.PriceListRanking ranking = new StoreOperationalStatisticsVo.PriceListRanking();
|
|
|
+ Integer priceId = getIntegerValue(item, "priceId");
|
|
|
+ ranking.setPriceId(priceId);
|
|
|
+ ranking.setPageViews(getLongValue(item, "viewCount"));
|
|
|
+ ranking.setVisitors(getLongValue(item, "visitorCount"));
|
|
|
+ ranking.setShares(getLongValue(item, "shareCount"));
|
|
|
+
|
|
|
+ if (priceId != null) {
|
|
|
+ ranking.setPriceListItemName(priceNameMap.get(priceId));
|
|
|
+ }
|
|
|
+
|
|
|
+ result.add(ranking);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按浏览量降序排序
|
|
|
+ result.sort((a, b) -> Long.compare(b.getPageViews(), a.getPageViews()));
|
|
|
+
|
|
|
+ // 设置排名
|
|
|
+ for (int i = 0; i < result.size(); i++) {
|
|
|
+ result.get(i).setRank(i + 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析价目表排名数据失败: {}", priceRankingDataJson, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从Map中获取BigDecimal值
|
|
|
+ */
|
|
|
+ private BigDecimal getBigDecimalValue(Map<String, Object> map, String key) {
|
|
|
+ Object value = map.get(key);
|
|
|
+ if (value == null) return BigDecimal.ZERO;
|
|
|
+ if (value instanceof BigDecimal) return (BigDecimal) value;
|
|
|
+ if (value instanceof Number) return BigDecimal.valueOf(((Number) value).doubleValue());
|
|
|
+ try {
|
|
|
+ return new BigDecimal(value.toString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从Map中获取Integer值
|
|
|
+ */
|
|
|
+ private Integer getIntegerValue(Map<String, Object> map, String key) {
|
|
|
+ Object value = map.get(key);
|
|
|
+ if (value == null) return null;
|
|
|
+ if (value instanceof Integer) return (Integer) value;
|
|
|
+ if (value instanceof Number) return ((Number) value).intValue();
|
|
|
+ try {
|
|
|
+ return Integer.parseInt(value.toString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|