|
|
@@ -7,13 +7,19 @@ 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.store.service.StoreOperationalStatisticsService;
|
|
|
import shop.alien.store.util.StatisticsComparisonImageUtil;
|
|
|
+import shop.alien.store.util.ai.AiAuthTokenUtil;
|
|
|
import shop.alien.util.ali.AliOSSUtil;
|
|
|
import shop.alien.util.common.RandomCreateUtil;
|
|
|
import shop.alien.util.pdf.ImageToPdfUtil;
|
|
|
@@ -34,6 +40,7 @@ import java.util.*;
|
|
|
@Slf4j
|
|
|
@Service
|
|
|
@RequiredArgsConstructor
|
|
|
+@RefreshScope
|
|
|
public class StoreOperationalStatisticsServiceImpl implements StoreOperationalStatisticsService {
|
|
|
|
|
|
private final LifeBrowseRecordMapper lifeBrowseRecordMapper;
|
|
|
@@ -56,6 +63,11 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
|
|
|
private final StoreOperationalStatisticsHistoryMapper statisticsHistoryMapper;
|
|
|
private final StoreTrackStatisticsMapper storeTrackStatisticsMapper;
|
|
|
private final AliOSSUtil aliOSSUtil;
|
|
|
+ private final AiAuthTokenUtil aiAuthTokenUtil;
|
|
|
+ private final RestTemplate restTemplate;
|
|
|
+
|
|
|
+ @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";
|
|
|
@@ -221,9 +233,19 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
|
|
|
comparison.setPriceListRanking(buildPriceListRankingComparison(currentStatistics.getPriceListRanking(), previousStatistics.getPriceListRanking()));
|
|
|
|
|
|
// 保存历史记录(不包含PDF URL,PDF URL只在 generateStatisticsComparisonPdf 接口中保存)
|
|
|
- saveStatisticsHistory(storeId, currentStartTime, currentEndTime,
|
|
|
+ Integer historyId = saveStatisticsHistory(storeId, currentStartTime, currentEndTime,
|
|
|
previousStartTime, previousEndTime, comparison, null);
|
|
|
|
|
|
+ // 异步调用AI接口进行数据分析
|
|
|
+ if (historyId != null) {
|
|
|
+ try {
|
|
|
+ callAiAnalysisAsync(historyId, storeId, comparison);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("调用AI分析接口失败 - historyId={}, error={}", historyId, e.getMessage(), e);
|
|
|
+ // AI分析失败不影响主流程,只记录日志
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
return comparison;
|
|
|
}
|
|
|
|
|
|
@@ -1035,6 +1057,31 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
public boolean deleteHistory(Integer id) {
|
|
|
log.info("StoreOperationalStatisticsServiceImpl.deleteHistory - id={}", id);
|
|
|
|
|
|
@@ -1738,4 +1785,90 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
|
|
|
return comparison;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 异步调用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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从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();
|
|
|
+ }
|
|
|
+
|
|
|
}
|