Преглед на файлове

服务迁移到平台模块

zhangchen преди 2 месеца
родител
ревизия
748065c98b

+ 259 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/PlatformStoreOperationalStatisticsController.java

@@ -0,0 +1,259 @@
+package shop.alien.storeplatform.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreOperationalStatisticsHistory;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsVo;
+import shop.alien.storeplatform.service.PlatformStoreOperationalStatisticsService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 商家经营数据统计Controller
+ *
+ * @author system
+ * @version 1.0
+ * @date 2026/01/05
+ */
+@Slf4j
+@Api(tags = {"商家经营数据统计"})
+@CrossOrigin
+@RestController
+@RequestMapping("/platform/operational/statistics")
+@RequiredArgsConstructor
+public class PlatformStoreOperationalStatisticsController {
+    private final PlatformStoreOperationalStatisticsService platformStoreOperationalStatisticsService;
+
+    @GetMapping("/getPlatformStatistics")
+    public R<StoreOperationalStatisticsVo> getPlatformStatistics(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("startTime") String startTime,
+            @RequestParam("endTime") String endTime) {
+        log.info("StoreOperationalStatisticsController.getStatistics - storeId={}, startTime={}, endTime={}", storeId, startTime, endTime);
+        try {
+            StoreOperationalStatisticsVo statistics = platformStoreOperationalStatisticsService.getPlatformStatisticsInTrackFormat(storeId, startTime, endTime);
+            return R.data(statistics);
+        } catch (Exception e) {
+            log.error("获取商家经营统计数据失败 - storeId={}, startTime={}, endTime={}, error={}",
+                    storeId, startTime, endTime, e.getMessage(), e);
+            return R.fail("获取统计数据失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("获取商家经营统计数据对比")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "storeId",
+                    value = "店铺ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "currentStartTime",
+                    value = "当期开始时间(格式:yyyy-MM-dd)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "currentEndTime",
+                    value = "当期结束时间(格式:yyyy-MM-dd)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "previousStartTime",
+                    value = "上期开始时间(格式:yyyy-MM-dd)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "previousEndTime",
+                    value = "上期结束时间(格式:yyyy-MM-dd)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true
+            )
+    })
+    @GetMapping("/getPlatformStatisticsComparison")
+    public R<StoreOperationalStatisticsComparisonVo> getPlatformStatisticsComparison(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("currentStartTime") String currentStartTime,
+            @RequestParam("currentEndTime") String currentEndTime,
+            @RequestParam("previousStartTime") String previousStartTime,
+            @RequestParam("previousEndTime") String previousEndTime) {
+        log.info("StoreOperationalStatisticsController.getStatisticsComparison - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}",
+                storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
+        StoreOperationalStatisticsComparisonVo comparison = platformStoreOperationalStatisticsService.getPlatformStatisticsComparison(
+                storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
+        return R.data(comparison);
+    }
+
+    @ApiOperation("根据ID查询历史统计记录详情")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "id",
+                    value = "历史记录ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            )
+    })
+    @GetMapping("/history/platformDetail")
+    public R<StoreOperationalStatisticsHistory> getPlatformHistoryById(@RequestParam("id") Integer id) {
+        log.info("StoreOperationalStatisticsController.getPlatformHistoryById - id={}", id);
+        try {
+            if (id == null || id <= 0) {
+                return R.fail("ID不能为空且必须大于0");
+            }
+
+            StoreOperationalStatisticsHistory history = platformStoreOperationalStatisticsService.getPlatformHistoryById(id);
+            return R.data(history);
+        } catch (Exception e) {
+            log.error("查询历史统计记录详情失败 - id={}, error={}", id, e.getMessage(), e);
+            return R.fail("查询失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("查询历史统计记录列表(分页)")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "storeId",
+                    value = "店铺ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "page",
+                    value = "页数(默认1)",
+                    dataType = "int",
+                    paramType = "query"
+            ),
+            @ApiImplicitParam(
+                    name = "size",
+                    value = "页容(默认10)",
+                    dataType = "int",
+                    paramType = "query"
+            ),
+            @ApiImplicitParam(
+                    name = "created_time",
+                    value = "查询日期(yyyy-MM-dd),可为空;传入时仅返回 query_time 为该日期的记录",
+                    dataType = "String",
+                    paramType = "query"
+            )
+    })
+    @GetMapping("/history/platformList")
+    public R<IPage<StoreOperationalStatisticsHistory>> getHistoryList(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam(value = "created_time", required = false) String createdTime) {
+        log.info("StoreOperationalStatisticsController.getHistoryList - storeId={}, page={}, size={}, created_time={}", storeId, page, size, createdTime);
+        try {
+            int pageNum = page > 0 ? page : 1;
+            int pageSize = size > 0 ? size : 10;
+            IPage<StoreOperationalStatisticsHistory> historyPage = platformStoreOperationalStatisticsService.getPlatformHistoryList(storeId, pageNum, pageSize, createdTime);
+            return R.data(historyPage);
+        } catch (Exception e) {
+            log.error("查询历史统计记录列表失败 - storeId={}, page={}, size={}, error={}", storeId, page, size, e.getMessage(), e);
+            return R.fail("查询失败: " + e.getMessage());
+        }
+    }
+
+
+    @ApiOperation("批量删除历史统计记录")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "ids",
+                    value = "历史记录ID列表(逗号分隔)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true,
+                    example = "1,2,3"
+            )
+    })
+    @DeleteMapping("/history/batchPlatformDelete")
+    public R<String> batchPlatformDeleteHistory(@RequestParam("ids") String ids) {
+        log.info("StoreOperationalStatisticsController.batchDeleteHistory - ids={}", ids);
+        try {
+            if (ids == null || ids.trim().isEmpty()) {
+                return R.fail("ID列表不能为空");
+            }
+
+            // 解析ID列表
+            String[] idArray = ids.split(",");
+            List<Integer> idList = new ArrayList<>();
+            for (String idStr : idArray) {
+                try {
+                    int id = Integer.parseInt(idStr.trim());
+                    if (id > 0) {
+                        idList.add(id);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无效的ID格式: {}", idStr);
+                }
+            }
+
+            if (idList.isEmpty()) {
+                return R.fail("没有有效的ID");
+            }
+
+            boolean result = platformStoreOperationalStatisticsService.batchPlatformDeleteHistory(idList);
+            if (result) {
+                return R.success("批量删除成功");
+            } else {
+                return R.fail("批量删除失败");
+            }
+        } catch (Exception e) {
+            log.error("批量删除历史统计记录失败 - ids={}, error={}", ids, e.getMessage(), e);
+            return R.fail("批量删除失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据历史记录ID生成统计数据对比PDF报告并上传到OSS")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "storeId",
+                    value = "店铺ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "historyId",
+                    value = "历史记录ID(从 store_operational_statistics_history 表取 statistics_data 解析为对比数据)",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            )
+    })
+    @GetMapping("/generateStatisticsComparisonPdfByHistoryId")
+    public R<String> generateStatisticsComparisonPdfByHistoryId(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("historyId") Integer historyId) {
+        log.info("StoreOperationalStatisticsController.generateStatisticsComparisonPdfByHistoryId - storeId={}, historyId={}", storeId, historyId);
+        try {
+            String pdfUrl = platformStoreOperationalStatisticsService.generateStatisticsComparisonPdfByHistoryId(storeId, historyId);
+            return R.data(pdfUrl);
+        } catch (Exception e) {
+            log.error("根据历史记录生成统计数据对比PDF失败 - storeId={}, historyId={}, error={}", storeId, historyId, e.getMessage(), e);
+            return R.fail("生成PDF报告失败: " + e.getMessage());
+        }
+    }
+
+}

+ 79 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/PlatformStoreOperationalStatisticsService.java

@@ -0,0 +1,79 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.store.StoreOperationalStatisticsHistory;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsVo;
+
+import java.util.List;
+
+/**
+ * 商家经营数据统计服务接口
+ *
+ * @author system
+ * @version 1.0
+ * @date 2026/01/05
+ */
+public interface PlatformStoreOperationalStatisticsService {
+
+
+    /**
+     * 获取商家经营统计数据(符合埋点统计数据JSON格式)
+     *
+     * @param storeId   店铺ID
+     * @param startTime 开始时间(格式:yyyy-MM-dd)
+     * @param endTime   结束时间(格式:yyyy-MM-dd)
+     * @return 经营统计数据
+     */
+    StoreOperationalStatisticsVo getPlatformStatisticsInTrackFormat(Integer storeId, String startTime, String endTime);
+
+    /**
+     * 获取商家经营统计数据对比
+     *
+     * @param storeId           店铺ID
+     * @param currentStartTime  当期开始时间(格式:yyyy-MM-dd)
+     * @param currentEndTime    当期结束时间(格式:yyyy-MM-dd)
+     * @param previousStartTime 上期开始时间(格式:yyyy-MM-dd)
+     * @param previousEndTime   上期结束时间(格式:yyyy-MM-dd)
+     * @return 经营统计数据对比
+     */
+    StoreOperationalStatisticsComparisonVo getPlatformStatisticsComparison(Integer storeId, String currentStartTime, String currentEndTime,
+                                                                   String previousStartTime, String previousEndTime);
+
+
+    /**
+     * 根据ID查询历史统计记录详情
+     *
+     * @param id 历史记录ID
+     * @return 历史统计记录详情
+     */
+    StoreOperationalStatisticsHistory getPlatformHistoryById(Integer id);
+
+    /**
+     * 查询历史统计记录列表(分页)
+     *
+     * @param storeId    店铺ID
+     * @param page       页码
+     * @param size       每页大小
+     * @param createdTime 查询日期(yyyy-MM-dd),可为空;传入时仅返回 query_time 为该日期的记录
+     * @return 历史统计记录分页列表
+     */
+    IPage<StoreOperationalStatisticsHistory> getPlatformHistoryList(Integer storeId, Integer page, Integer size, String createdTime);
+
+    /**
+     * 批量删除历史统计记录(逻辑删除)
+     *
+     * @param ids 历史记录ID列表
+     * @return 是否成功
+     */
+    boolean batchPlatformDeleteHistory(List<Integer> ids);
+
+    /**
+     * 根据历史记录ID生成统计数据对比PDF报告并上传到OSS(对比数据从 history 的 statistics_data 解析)
+     *
+     * @param storeId   店铺ID
+     * @param historyId 历史记录ID
+     * @return PDF的OSS URL
+     */
+    String generateStatisticsComparisonPdfByHistoryId(Integer storeId, Integer historyId);
+}

+ 1488 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/PlatformStoreOperationalStatisticsServiceImpl.java

@@ -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;
+        }
+    }
+}

+ 80 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/util/AiAuthTokenUtil.java

@@ -0,0 +1,80 @@
+package shop.alien.storeplatform.util;
+
+import com.alibaba.fastjson2.JSONObject;
+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.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * AI 服务鉴权配置
+ */
+@Slf4j
+@Component
+@RefreshScope
+@RequiredArgsConstructor
+public class AiAuthTokenUtil {
+
+    private final RestTemplate restTemplate;
+
+    @Value("${third-party-login.base-url}")
+    private String loginUrl;
+
+    @Value("${third-party-user-name.base-url}")
+    private String userName;
+
+    @Value("${third-party-pass-word.base-url}")
+    private String passWord;
+
+    /**
+     * 登录 AI 服务,获取 token
+     *
+     * @return accessToken
+     */
+    public String getAccessToken() {
+        log.info("登录Ai服务获取token...{}", loginUrl);
+        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("username", userName);
+        formData.add("password", passWord);
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
+
+        ResponseEntity<String> response;
+        try {
+            log.info("请求Ai服务登录接口===================>");
+            response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
+        } catch (Exception e) {
+            log.error("请求AI服务登录接口失败", e);
+            return null;
+        }
+
+        if (response != null && response.getStatusCode() == HttpStatus.OK) {
+            String body = response.getBody();
+            log.info("请求Ai服务登录成功 postForEntity.getBody()\t{}", body);
+            if (StringUtils.hasText(body)) {
+                JSONObject jsonObject = JSONObject.parseObject(body);
+                if (jsonObject != null) {
+                    JSONObject dataJson = jsonObject.getJSONObject("data");
+                    if (dataJson != null) {
+                        return dataJson.getString("access_token");
+                    }
+                }
+            }
+            log.warn("AI服务登录响应解析失败 body: {}", body);
+            return null;
+        }
+
+        log.error("请求AI服务 登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+        return null;
+    }
+}
+
+

+ 831 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/util/PlatformStatisticsComparisonImageUtil.java

@@ -0,0 +1,831 @@
+package shop.alien.storeplatform.util;
+
+import lombok.extern.slf4j.Slf4j;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.nio.file.Files;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 经营统计数据对比图片生成工具类
+ * 将 StoreOperationalStatisticsComparisonVo 数据转换为图片
+ *
+ * @author system
+ * @since 2026-01-05
+ */
+@Slf4j
+public class PlatformStatisticsComparisonImageUtil {
+
+    // 颜色定义
+    private static final Color BACKGROUND_COLOR = Color.WHITE;
+    private static final Color TEXT_COLOR = new Color(51, 51, 51); // #333333
+    private static final Color HEADER_BG_COLOR = new Color(245, 245, 245); // #F5F5F5
+    private static final Color POSITIVE_COLOR = new Color(76, 175, 80); // 绿色 #4CAF50
+    private static final Color NEGATIVE_COLOR = new Color(244, 67, 54); // 红色 #F44336
+    private static final Color SECTION_TITLE_COLOR = new Color(33, 33, 33); // #212121
+    private static final Color BORDER_COLOR = new Color(224, 224, 224); // #E0E0E0
+
+    // 字体:优先 classpath 内 fonts/ 下的字体文件(与 resources/fonts/ 对应),支持 .ttf / .otf
+    private static final String[] FONT_RESOURCE_PATHS = {
+            "fonts/NotoSansSC-Regular.ttf",
+            "fonts/NotoSansCJKsc-Regular.otf",
+            "/fonts/NotoSansSC-Regular.ttf",
+            "/fonts/NotoSansCJKsc-Regular.otf"
+    };
+    private static final String[] FONT_FALLBACK_NAMES = {
+            "Microsoft YaHei",
+            "PingFang SC",
+            "WenQuanYi Zen Hei",
+            "WenQuanYi Micro Hei",
+            "Noto Sans CJK SC",
+            "Noto Sans SC",
+            "SimSun",
+            "STSong",
+            "Source Han Sans SC",
+            "DengXian"
+    };
+    private static volatile Font baseChineseFont;   // 从 TTF 加载的基准字体
+    private static volatile String chineseFontName; // 系统字体名(当未加载 TTF 时)
+
+    private static final int TITLE_FONT_SIZE = 24;
+    private static final int SECTION_TITLE_FONT_SIZE = 18;
+    private static final int DATA_FONT_SIZE = 14;
+    private static final int LABEL_FONT_SIZE = 12;
+
+    // 尺寸定义
+    private static final int IMAGE_WIDTH = 800;
+    private static final int PADDING = 20;
+    private static final int SECTION_SPACING = 30;
+    private static final int ROW_HEIGHT = 35;
+    private static final int HEADER_HEIGHT = 50;
+
+    private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,##0.00");
+    private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("#,##0.00%");
+
+    /** 从 classpath 读取字体资源为字节数组(JAR 内更可靠) */
+    private static byte[] readFontBytes(String path) {
+        ClassLoader cl = PlatformStatisticsComparisonImageUtil.class.getClassLoader();
+        for (String p : new String[]{path, path.startsWith("/") ? path.substring(1) : "/" + path}) {
+            InputStream is = cl.getResourceAsStream(p);
+            if (is == null && Thread.currentThread().getContextClassLoader() != null) {
+                is = Thread.currentThread().getContextClassLoader().getResourceAsStream(p);
+            }
+            if (is == null) continue;
+            try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+                byte[] buf = new byte[8192];
+                int n;
+                while ((n = is.read(buf)) != -1) out.write(buf, 0, n);
+                return out.toByteArray();
+            } catch (Exception ignored) {
+            } finally {
+                try { is.close(); } catch (Exception ignored) {}
+            }
+        }
+        return null;
+    }
+
+    private static Font getChineseFont(int style, int size) {
+        if (baseChineseFont != null) {
+            return baseChineseFont.deriveFont(style, size);
+        }
+        if (chineseFontName != null) {
+            return new Font(chineseFontName, style, size);
+        }
+        synchronized (PlatformStatisticsComparisonImageUtil.class) {
+            if (baseChineseFont != null) return baseChineseFont.deriveFont(style, size);
+            if (chineseFontName != null) return new Font(chineseFontName, style, size);
+            for (String path : FONT_RESOURCE_PATHS) {
+                byte[] bytes = readFontBytes(path);
+                if (bytes == null || bytes.length == 0) continue;
+                try {
+                    baseChineseFont = Font.createFont(Font.TRUETYPE_FONT, new ByteArrayInputStream(bytes)).deriveFont(Font.PLAIN, 14f);
+                    return baseChineseFont.deriveFont(style, size);
+                } catch (Exception e1) {
+                    try {
+                        File tmp = File.createTempFile("noto_", path.endsWith(".otf") ? ".otf" : ".ttf");
+                        Files.write(tmp.toPath(), bytes);
+                        baseChineseFont = Font.createFont(Font.TRUETYPE_FONT, tmp).deriveFont(Font.PLAIN, 14f);
+                        tmp.delete();
+                        return baseChineseFont.deriveFont(style, size);
+                    } catch (Exception e2) {
+                        // skip
+                    }
+                }
+            }
+            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+            String[] names = ge.getAvailableFontFamilyNames();
+            if (names != null) {
+                for (String candidate : FONT_FALLBACK_NAMES) {
+                    for (String name : names) {
+                        if (!candidate.equals(name)) continue;
+                        Font f = new Font(name, Font.PLAIN, 14);
+                        if (f.canDisplayUpTo("经") >= 0) continue;
+                        chineseFontName = name;
+                        return new Font(chineseFontName, style, size);
+                    }
+                }
+                for (String name : names) {
+                    Font f = new Font(name, Font.PLAIN, 14);
+                    if (f.canDisplayUpTo("经") >= 0) continue;
+                    chineseFontName = name;
+                    return new Font(chineseFontName, style, size);
+                }
+            }
+            chineseFontName = Font.SANS_SERIF;
+            return new Font(chineseFontName, style, size);
+        }
+    }
+
+    /**
+     * 将统计数据对比转换为图片字节数组
+     *
+     * @param comparison 统计数据对比对象
+     * @return 图片字节数组
+     */
+    public static byte[] generateImage(StoreOperationalStatisticsComparisonVo comparison) {
+        if (comparison == null) {
+            throw new IllegalArgumentException("统计数据对比对象不能为空");
+        }
+
+        try {
+            // 计算图片高度
+            int totalHeight = calculateImageHeight(comparison);
+            
+            // 创建图片
+            BufferedImage image = new BufferedImage(IMAGE_WIDTH, totalHeight, BufferedImage.TYPE_INT_RGB);
+            Graphics2D g2d = image.createGraphics();
+            
+            // 设置抗锯齿
+            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+            
+            // 填充背景
+            g2d.setColor(BACKGROUND_COLOR);
+            g2d.fillRect(0, 0, IMAGE_WIDTH, totalHeight);
+            
+            int currentY = PADDING;
+            
+            // 绘制标题
+            currentY = drawTitle(g2d, currentY, comparison);
+            
+            // 绘制日期范围
+            currentY = drawDateRange(g2d, currentY, comparison);
+            
+            currentY += SECTION_SPACING;
+            
+            // 绘制流量数据
+            if (comparison.getTrafficData() != null) {
+                currentY = drawSection(g2d, currentY, "流量数据", 
+                    buildTrafficDataRows(comparison.getTrafficData()));
+            }
+            
+            // 绘制互动数据
+            if (comparison.getInteractionData() != null) {
+                currentY = drawSection(g2d, currentY, "互动数据", 
+                    buildInteractionDataRows(comparison.getInteractionData()));
+            }
+            
+            // 绘制优惠券数据
+            if (comparison.getCouponData() != null) {
+                currentY = drawSection(g2d, currentY, "优惠券", 
+                    buildCouponDataRows(comparison.getCouponData()));
+            }
+            
+            // 绘制代金券数据
+            if (comparison.getVoucherData() != null) {
+                currentY = drawSection(g2d, currentY, "代金券", 
+                    buildVoucherDataRows(comparison.getVoucherData()));
+            }
+            
+            // 绘制服务质量数据
+            if (comparison.getServiceQualityData() != null) {
+                currentY = drawSection(g2d, currentY, "服务质量", 
+                    buildServiceQualityDataRows(comparison.getServiceQualityData()));
+            }
+            
+            // 绘制价目表排名数据
+            if (comparison.getPriceListRanking() != null && !comparison.getPriceListRanking().isEmpty()) {
+                currentY = drawPriceListRankingSection(g2d, currentY, "价目表排名", 
+                    comparison.getPriceListRanking());
+            }
+            
+            g2d.dispose();
+            
+            // 转换为字节数组
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ImageIO.write(image, "PNG", baos);
+            return baos.toByteArray();
+            
+        } catch (Exception e) {
+            log.error("生成统计数据对比图片失败", e);
+            throw new RuntimeException("生成图片失败: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 计算图片高度
+     */
+    private static int calculateImageHeight(StoreOperationalStatisticsComparisonVo comparison) {
+        int height = PADDING * 2;
+        height += HEADER_HEIGHT; // 标题
+        height += 40; // 日期范围
+        height += SECTION_SPACING;
+        
+        int rowCount = 0;
+        if (comparison.getTrafficData() != null) {
+            rowCount += 6; // 流量数据行数(6个字段)
+        }
+        if (comparison.getInteractionData() != null) {
+            rowCount += 13; // 互动数据行数(13个字段)
+        }
+        if (comparison.getCouponData() != null) {
+            rowCount += 10; // 优惠券数据行数(10个字段)
+        }
+        if (comparison.getVoucherData() != null) {
+            rowCount += 10; // 代金券数据行数(10个字段)
+        }
+        if (comparison.getServiceQualityData() != null) {
+            rowCount += 12; // 服务质量数据行数(12个字段)
+        }
+        if (comparison.getPriceListRanking() != null && !comparison.getPriceListRanking().isEmpty()) {
+            // 价目表数据:每个价目表3个指标(浏览量、访客数、分享数)
+            rowCount += comparison.getPriceListRanking().size() * 3;
+        }
+        
+        height += rowCount * ROW_HEIGHT;
+        height += (rowCount / 6 + 4) * SECTION_SPACING; // 区块间距
+        
+        return height;
+    }
+
+    /**
+     * 绘制标题
+     */
+    private static int drawTitle(Graphics2D g2d, int y, StoreOperationalStatisticsComparisonVo comparison) {
+        Font titleFont = getChineseFont(Font.BOLD, TITLE_FONT_SIZE);
+        g2d.setFont(titleFont);
+        g2d.setColor(SECTION_TITLE_COLOR);
+        
+        String title = "经营数据";
+        FontMetrics fm = g2d.getFontMetrics();
+        int titleWidth = fm.stringWidth(title);
+        int titleX = (IMAGE_WIDTH - titleWidth) / 2;
+        
+        g2d.drawString(title, titleX, y + TITLE_FONT_SIZE);
+        return y + HEADER_HEIGHT;
+    }
+
+    /**
+     * 绘制日期范围
+     */
+    private static int drawDateRange(Graphics2D g2d, int y, StoreOperationalStatisticsComparisonVo comparison) {
+        Font dateFont = getChineseFont(Font.PLAIN, DATA_FONT_SIZE);
+        g2d.setFont(dateFont);
+        g2d.setColor(TEXT_COLOR);
+        
+        String currentDate = formatDate(comparison.getCurrentStartTime()) + "-" + formatDate(comparison.getCurrentEndTime());
+        String previousDate = formatDate(comparison.getPreviousStartTime()) + "-" + formatDate(comparison.getPreviousEndTime());
+        
+        int dateX = PADDING;
+        g2d.drawString(currentDate, dateX, y);
+        g2d.drawString(previousDate, dateX, y + 20);
+        
+        return y + 40;
+    }
+
+    /**
+     * 格式化日期
+     */
+    private static String formatDate(String date) {
+        if (date == null || date.length() < 10) {
+            return date;
+        }
+        // 将 yyyy-MM-dd 转换为 yyyy/MM/dd
+        return date.replace("-", "/");
+    }
+
+    /**
+     * 绘制数据区块
+     */
+    private static int drawSection(Graphics2D g2d, int y, String sectionTitle, List<DataRow> rows) {
+        if (rows == null || rows.isEmpty()) {
+            return y;
+        }
+        
+        // 绘制区块标题
+        Font sectionFont = getChineseFont(Font.BOLD, SECTION_TITLE_FONT_SIZE);
+        g2d.setFont(sectionFont);
+        g2d.setColor(SECTION_TITLE_COLOR);
+        g2d.drawString(sectionTitle, PADDING, y);
+        y += 30;
+
+        // 绘制表头
+        y = drawTableHeader(g2d, y);
+
+        // 绘制数据行
+        Font dataFont = getChineseFont(Font.PLAIN, DATA_FONT_SIZE);
+        Font labelFont = getChineseFont(Font.PLAIN, LABEL_FONT_SIZE);
+        
+        for (DataRow row : rows) {
+            y = drawDataRow(g2d, y, row, dataFont, labelFont);
+        }
+        
+        return y + SECTION_SPACING;
+    }
+
+    /**
+     * 绘制表头
+     */
+    private static int drawTableHeader(Graphics2D g2d, int y) {
+        // 绘制表头背景
+        g2d.setColor(HEADER_BG_COLOR);
+        g2d.fillRect(PADDING, y, IMAGE_WIDTH - PADDING * 2, ROW_HEIGHT);
+        
+        // 绘制表头文字
+        Font headerFont = getChineseFont(Font.BOLD, LABEL_FONT_SIZE);
+        g2d.setFont(headerFont);
+        g2d.setColor(TEXT_COLOR);
+        
+        int col1X = PADDING + 10;
+        int col2X = IMAGE_WIDTH / 2 - 80;
+        int col3X = IMAGE_WIDTH / 2 + 20;
+        int col4X = IMAGE_WIDTH - PADDING - 100;
+        
+        g2d.drawString("指标", col1X, y + 20);
+        g2d.drawString("当期", col2X, y + 20);
+        g2d.drawString("上期", col3X, y + 20);
+        g2d.drawString("变化率", col4X, y + 20);
+        
+        // 绘制边框
+        g2d.setColor(BORDER_COLOR);
+        g2d.drawRect(PADDING, y, IMAGE_WIDTH - PADDING * 2, ROW_HEIGHT);
+        
+        return y + ROW_HEIGHT;
+    }
+
+    /**
+     * 绘制数据行
+     */
+    private static int drawDataRow(Graphics2D g2d, int y, DataRow row, Font dataFont, Font labelFont) {
+        // 绘制行背景(交替颜色)
+        if ((y / ROW_HEIGHT) % 2 == 0) {
+            g2d.setColor(new Color(250, 250, 250));
+            g2d.fillRect(PADDING, y, IMAGE_WIDTH - PADDING * 2, ROW_HEIGHT);
+        }
+        
+        // 绘制指标名称
+        g2d.setFont(labelFont);
+        g2d.setColor(TEXT_COLOR);
+        g2d.drawString(row.getLabel(), PADDING + 10, y + 22);
+        
+        // 绘制当期值
+        g2d.setFont(dataFont);
+        String currentValue = formatValue(row.getCurrent());
+        g2d.drawString(currentValue, IMAGE_WIDTH / 2 - 80, y + 22);
+        
+        // 绘制上期值
+        String previousValue = formatValue(row.getPrevious());
+        g2d.drawString(previousValue, IMAGE_WIDTH / 2 + 20, y + 22);
+        
+        // 绘制变化率(带颜色)
+        String changeRate = formatChangeRate(row.getChangeRate());
+        Color changeColor = row.getChangeRate() != null && row.getChangeRate().compareTo(BigDecimal.ZERO) >= 0 
+            ? POSITIVE_COLOR : NEGATIVE_COLOR;
+        g2d.setColor(changeColor);
+        g2d.drawString(changeRate, IMAGE_WIDTH - PADDING - 100, y + 22);
+        
+        // 绘制边框
+        g2d.setColor(BORDER_COLOR);
+        g2d.drawLine(PADDING, y + ROW_HEIGHT, IMAGE_WIDTH - PADDING, y + ROW_HEIGHT);
+        
+        return y + ROW_HEIGHT;
+    }
+
+    /**
+     * 格式化数值
+     */
+    private static String formatValue(Object value) {
+        if (value == null) {
+            return "0";
+        }
+        if (value instanceof BigDecimal) {
+            BigDecimal bd = (BigDecimal) value;
+            if (bd.scale() > 0) {
+                return DECIMAL_FORMAT.format(bd);
+            } else {
+                return String.valueOf(bd.longValue());
+            }
+        }
+        if (value instanceof Number) {
+            return DECIMAL_FORMAT.format(((Number) value).doubleValue());
+        }
+        return String.valueOf(value);
+    }
+
+    /**
+     * 格式化变化率
+     */
+    private static String formatChangeRate(BigDecimal changeRate) {
+        if (changeRate == null) {
+            return "0.00%";
+        }
+        String sign = changeRate.compareTo(BigDecimal.ZERO) >= 0 ? "+" : "";
+        return sign + PERCENT_FORMAT.format(changeRate.divide(new BigDecimal(100), 4, BigDecimal.ROUND_HALF_UP));
+    }
+
+    /**
+     * 构建流量数据行(按照实体类字段顺序)
+     */
+    private static List<DataRow> buildTrafficDataRows(StoreOperationalStatisticsComparisonVo.TrafficDataComparison data) {
+        List<DataRow> rows = new ArrayList<>();
+        // 1. 店铺搜索量对比
+        if (data.getStoreSearchVolume() != null) {
+            rows.add(new DataRow("店铺搜索量对比", data.getStoreSearchVolume().getCurrent(),
+                data.getStoreSearchVolume().getPrevious(), data.getStoreSearchVolume().getChangeRate()));
+        }
+        // 2. 浏览量对比
+        if (data.getPageViews() != null) {
+            rows.add(new DataRow("浏览量对比", data.getPageViews().getCurrent(),
+                data.getPageViews().getPrevious(), data.getPageViews().getChangeRate()));
+        }
+        // 3. 访客数对比
+        if (data.getVisitors() != null) {
+            rows.add(new DataRow("访客数对比", data.getVisitors().getCurrent(),
+                data.getVisitors().getPrevious(), data.getVisitors().getChangeRate()));
+        }
+        // 4. 新增访客数对比
+        if (data.getNewVisitors() != null) {
+            rows.add(new DataRow("新增访客数对比", data.getNewVisitors().getCurrent(),
+                data.getNewVisitors().getPrevious(), data.getNewVisitors().getChangeRate()));
+        }
+        // 5. 访问时长对比
+        if (data.getVisitDuration() != null) {
+            rows.add(new DataRow("访问时长对比", data.getVisitDuration().getCurrent(),
+                data.getVisitDuration().getPrevious(), data.getVisitDuration().getChangeRate()));
+        }
+        // 6. 平均访问时长对比
+        if (data.getAvgVisitDuration() != null) {
+            rows.add(new DataRow("平均访问时长对比", data.getAvgVisitDuration().getCurrent(),
+                data.getAvgVisitDuration().getPrevious(), data.getAvgVisitDuration().getChangeRate()));
+        }
+        return rows;
+    }
+
+    /**
+     * 构建互动数据行(按照实体类字段顺序)
+     */
+    private static List<DataRow> buildInteractionDataRows(StoreOperationalStatisticsComparisonVo.InteractionDataComparison data) {
+        List<DataRow> rows = new ArrayList<>();
+        // 1. 店铺收藏次数对比
+        if (data.getStoreCollectionCount() != null) {
+            rows.add(new DataRow("店铺收藏次数对比", data.getStoreCollectionCount().getCurrent(),
+                data.getStoreCollectionCount().getPrevious(), data.getStoreCollectionCount().getChangeRate()));
+        }
+        // 2. 店铺分享次数对比
+        if (data.getStoreShareCount() != null) {
+            rows.add(new DataRow("店铺分享次数对比", data.getStoreShareCount().getCurrent(),
+                data.getStoreShareCount().getPrevious(), data.getStoreShareCount().getChangeRate()));
+        }
+        // 3. 店铺打卡次数对比
+        if (data.getStoreCheckInCount() != null) {
+            rows.add(new DataRow("店铺打卡次数对比", data.getStoreCheckInCount().getCurrent(),
+                data.getStoreCheckInCount().getPrevious(), data.getStoreCheckInCount().getChangeRate()));
+        }
+        // 4. 咨询商家次数对比
+        if (data.getConsultMerchantCount() != null) {
+            rows.add(new DataRow("咨询商家次数对比", data.getConsultMerchantCount().getCurrent(),
+                data.getConsultMerchantCount().getPrevious(), data.getConsultMerchantCount().getChangeRate()));
+        }
+        // 5. 好友数量对比
+        if (data.getFriendsCount() != null) {
+            rows.add(new DataRow("好友数量对比", data.getFriendsCount().getCurrent(),
+                data.getFriendsCount().getPrevious(), data.getFriendsCount().getChangeRate()));
+        }
+        // 6. 关注数量对比
+        if (data.getFollowCount() != null) {
+            rows.add(new DataRow("关注数量对比", data.getFollowCount().getCurrent(),
+                data.getFollowCount().getPrevious(), data.getFollowCount().getChangeRate()));
+        }
+        // 7. 粉丝数量对比
+        if (data.getFansCount() != null) {
+            rows.add(new DataRow("粉丝数量对比", data.getFansCount().getCurrent(),
+                data.getFansCount().getPrevious(), data.getFansCount().getChangeRate()));
+        }
+        // 8. 发布动态数量对比
+        if (data.getPostsPublishedCount() != null) {
+            rows.add(new DataRow("发布动态数量对比", data.getPostsPublishedCount().getCurrent(),
+                data.getPostsPublishedCount().getPrevious(), data.getPostsPublishedCount().getChangeRate()));
+        }
+        // 9. 动态点赞数量对比
+        if (data.getPostLikesCount() != null) {
+            rows.add(new DataRow("动态点赞数量对比", data.getPostLikesCount().getCurrent(),
+                data.getPostLikesCount().getPrevious(), data.getPostLikesCount().getChangeRate()));
+        }
+        // 10. 动态评论数量对比
+        if (data.getPostCommentsCount() != null) {
+            rows.add(new DataRow("动态评论数量对比", data.getPostCommentsCount().getCurrent(),
+                data.getPostCommentsCount().getPrevious(), data.getPostCommentsCount().getChangeRate()));
+        }
+        // 11. 动态转发数量对比
+        if (data.getPostSharesCount() != null) {
+            rows.add(new DataRow("动态转发数量对比", data.getPostSharesCount().getCurrent(),
+                data.getPostSharesCount().getPrevious(), data.getPostSharesCount().getChangeRate()));
+        }
+        // 12. 被举报次数对比
+        if (data.getReportedCount() != null) {
+            rows.add(new DataRow("被举报次数对比", data.getReportedCount().getCurrent(),
+                data.getReportedCount().getPrevious(), data.getReportedCount().getChangeRate()));
+        }
+        // 13. 被拉黑次数对比
+        if (data.getBlockedCount() != null) {
+            rows.add(new DataRow("被拉黑次数对比", data.getBlockedCount().getCurrent(),
+                data.getBlockedCount().getPrevious(), data.getBlockedCount().getChangeRate()));
+        }
+        return rows;
+    }
+
+    /**
+     * 构建优惠券数据行(按照实体类字段顺序)
+     */
+    private static List<DataRow> buildCouponDataRows(StoreOperationalStatisticsComparisonVo.CouponDataComparison data) {
+        List<DataRow> rows = new ArrayList<>();
+        // 1. 赠送好友数量对比
+        if (data.getGiftToFriendsCount() != null) {
+            rows.add(new DataRow("赠送好友数量对比", data.getGiftToFriendsCount().getCurrent(), 
+                data.getGiftToFriendsCount().getPrevious(), data.getGiftToFriendsCount().getChangeRate()));
+        }
+        // 2. 赠送好友金额合计对比
+        if (data.getGiftToFriendsAmount() != null) {
+            rows.add(new DataRow("赠送好友金额合计对比", data.getGiftToFriendsAmount().getCurrent(), 
+                data.getGiftToFriendsAmount().getPrevious(), data.getGiftToFriendsAmount().getChangeRate()));
+        }
+        // 3. 赠送好友使用数量对比
+        if (data.getGiftToFriendsUsedCount() != null) {
+            rows.add(new DataRow("赠送好友使用数量对比", data.getGiftToFriendsUsedCount().getCurrent(), 
+                data.getGiftToFriendsUsedCount().getPrevious(), data.getGiftToFriendsUsedCount().getChangeRate()));
+        }
+        // 4. 赠送好友使用金额合计对比
+        if (data.getGiftToFriendsUsedAmount() != null) {
+            rows.add(new DataRow("赠送好友使用金额合计对比", data.getGiftToFriendsUsedAmount().getCurrent(), 
+                data.getGiftToFriendsUsedAmount().getPrevious(), data.getGiftToFriendsUsedAmount().getChangeRate()));
+        }
+        // 5. 赠送好友使用金额占比对比
+        if (data.getGiftToFriendsUsedAmountRatio() != null) {
+            rows.add(new DataRow("赠送好友使用金额占比对比", data.getGiftToFriendsUsedAmountRatio().getCurrent(), 
+                data.getGiftToFriendsUsedAmountRatio().getPrevious(), data.getGiftToFriendsUsedAmountRatio().getChangeRate()));
+        }
+        // 6. 好友赠送数量对比
+        if (data.getFriendsGiftCount() != null) {
+            rows.add(new DataRow("好友赠送数量对比", data.getFriendsGiftCount().getCurrent(), 
+                data.getFriendsGiftCount().getPrevious(), data.getFriendsGiftCount().getChangeRate()));
+        }
+        // 7. 好友赠送金额合计对比
+        if (data.getFriendsGiftAmount() != null) {
+            rows.add(new DataRow("好友赠送金额合计对比", data.getFriendsGiftAmount().getCurrent(), 
+                data.getFriendsGiftAmount().getPrevious(), data.getFriendsGiftAmount().getChangeRate()));
+        }
+        // 8. 好友赠送使用数量对比
+        if (data.getFriendsGiftUsedCount() != null) {
+            rows.add(new DataRow("好友赠送使用数量对比", data.getFriendsGiftUsedCount().getCurrent(), 
+                data.getFriendsGiftUsedCount().getPrevious(), data.getFriendsGiftUsedCount().getChangeRate()));
+        }
+        // 9. 好友赠送使用金额合计对比
+        if (data.getFriendsGiftUsedAmount() != null) {
+            rows.add(new DataRow("好友赠送使用金额合计对比", data.getFriendsGiftUsedAmount().getCurrent(), 
+                data.getFriendsGiftUsedAmount().getPrevious(), data.getFriendsGiftUsedAmount().getChangeRate()));
+        }
+        // 10. 好友赠送使用金额占比对比
+        if (data.getFriendsGiftUsedAmountRatio() != null) {
+            rows.add(new DataRow("好友赠送使用金额占比对比", data.getFriendsGiftUsedAmountRatio().getCurrent(), 
+                data.getFriendsGiftUsedAmountRatio().getPrevious(), data.getFriendsGiftUsedAmountRatio().getChangeRate()));
+        }
+        return rows;
+    }
+
+    /**
+     * 构建代金券数据行(按照实体类字段顺序)
+     */
+    private static List<DataRow> buildVoucherDataRows(StoreOperationalStatisticsComparisonVo.VoucherDataComparison data) {
+        List<DataRow> rows = new ArrayList<>();
+        // 1. 赠送好友数量对比
+        if (data.getGiftToFriendsCount() != null) {
+            rows.add(new DataRow("赠送好友数量对比", data.getGiftToFriendsCount().getCurrent(), 
+                data.getGiftToFriendsCount().getPrevious(), data.getGiftToFriendsCount().getChangeRate()));
+        }
+        // 2. 赠送好友金额合计对比
+        if (data.getGiftToFriendsAmount() != null) {
+            rows.add(new DataRow("赠送好友金额合计对比", data.getGiftToFriendsAmount().getCurrent(), 
+                data.getGiftToFriendsAmount().getPrevious(), data.getGiftToFriendsAmount().getChangeRate()));
+        }
+        // 3. 赠送好友使用数量对比
+        if (data.getGiftToFriendsUsedCount() != null) {
+            rows.add(new DataRow("赠送好友使用数量对比", data.getGiftToFriendsUsedCount().getCurrent(), 
+                data.getGiftToFriendsUsedCount().getPrevious(), data.getGiftToFriendsUsedCount().getChangeRate()));
+        }
+        // 4. 赠送好友使用金额合计对比
+        if (data.getGiftToFriendsUsedAmount() != null) {
+            rows.add(new DataRow("赠送好友使用金额合计对比", data.getGiftToFriendsUsedAmount().getCurrent(), 
+                data.getGiftToFriendsUsedAmount().getPrevious(), data.getGiftToFriendsUsedAmount().getChangeRate()));
+        }
+        // 5. 赠送好友使用金额占比对比
+        if (data.getGiftToFriendsUsedAmountRatio() != null) {
+            rows.add(new DataRow("赠送好友使用金额占比对比", data.getGiftToFriendsUsedAmountRatio().getCurrent(), 
+                data.getGiftToFriendsUsedAmountRatio().getPrevious(), data.getGiftToFriendsUsedAmountRatio().getChangeRate()));
+        }
+        // 6. 好友赠送数量对比
+        if (data.getFriendsGiftCount() != null) {
+            rows.add(new DataRow("好友赠送数量对比", data.getFriendsGiftCount().getCurrent(), 
+                data.getFriendsGiftCount().getPrevious(), data.getFriendsGiftCount().getChangeRate()));
+        }
+        // 7. 好友赠送金额合计对比
+        if (data.getFriendsGiftAmount() != null) {
+            rows.add(new DataRow("好友赠送金额合计对比", data.getFriendsGiftAmount().getCurrent(), 
+                data.getFriendsGiftAmount().getPrevious(), data.getFriendsGiftAmount().getChangeRate()));
+        }
+        // 8. 好友赠送使用数量对比
+        if (data.getFriendsGiftUsedCount() != null) {
+            rows.add(new DataRow("好友赠送使用数量对比", data.getFriendsGiftUsedCount().getCurrent(), 
+                data.getFriendsGiftUsedCount().getPrevious(), data.getFriendsGiftUsedCount().getChangeRate()));
+        }
+        // 9. 好友赠送使用金额合计对比
+        if (data.getFriendsGiftUsedAmount() != null) {
+            rows.add(new DataRow("好友赠送使用金额合计对比", data.getFriendsGiftUsedAmount().getCurrent(), 
+                data.getFriendsGiftUsedAmount().getPrevious(), data.getFriendsGiftUsedAmount().getChangeRate()));
+        }
+        // 10. 好友赠送使用金额占比对比
+        if (data.getFriendsGiftUsedAmountRatio() != null) {
+            rows.add(new DataRow("好友赠送使用金额占比对比", data.getFriendsGiftUsedAmountRatio().getCurrent(), 
+                data.getFriendsGiftUsedAmountRatio().getPrevious(), data.getFriendsGiftUsedAmountRatio().getChangeRate()));
+        }
+        return rows;
+    }
+
+    /**
+     * 构建服务质量数据行(按照实体类字段顺序)
+     */
+    private static List<DataRow> buildServiceQualityDataRows(StoreOperationalStatisticsComparisonVo.ServiceQualityDataComparison data) {
+        List<DataRow> rows = new ArrayList<>();
+        // 1. 店铺评分对比
+        if (data.getStoreRating() != null) {
+            rows.add(new DataRow("店铺评分对比", data.getStoreRating().getCurrent(), 
+                data.getStoreRating().getPrevious(), data.getStoreRating().getChangeRate()));
+        }
+        // 2. 评分1对比
+        if (data.getScoreOne() != null) {
+            rows.add(new DataRow("评分1对比", data.getScoreOne().getCurrent(), 
+                data.getScoreOne().getPrevious(), data.getScoreOne().getChangeRate()));
+        }
+        // 3. 评分2对比
+        if (data.getScoreTwo() != null) {
+            rows.add(new DataRow("评分2对比", data.getScoreTwo().getCurrent(), 
+                data.getScoreTwo().getPrevious(), data.getScoreTwo().getChangeRate()));
+        }
+        // 4. 评分3对比
+        if (data.getScoreThree() != null) {
+            rows.add(new DataRow("评分3对比", data.getScoreThree().getCurrent(), 
+                data.getScoreThree().getPrevious(), data.getScoreThree().getChangeRate()));
+        }
+        // 5. 评价数量对比
+        if (data.getTotalReviews() != null) {
+            rows.add(new DataRow("评价数量对比", data.getTotalReviews().getCurrent(), 
+                data.getTotalReviews().getPrevious(), data.getTotalReviews().getChangeRate()));
+        }
+        // 6. 好评数量对比
+        if (data.getPositiveReviews() != null) {
+            rows.add(new DataRow("好评数量对比", data.getPositiveReviews().getCurrent(), 
+                data.getPositiveReviews().getPrevious(), data.getPositiveReviews().getChangeRate()));
+        }
+        // 7. 中评数量对比
+        if (data.getNeutralReviews() != null) {
+            rows.add(new DataRow("中评数量对比", data.getNeutralReviews().getCurrent(), 
+                data.getNeutralReviews().getPrevious(), data.getNeutralReviews().getChangeRate()));
+        }
+        // 8. 差评数量对比
+        if (data.getNegativeReviews() != null) {
+            rows.add(new DataRow("差评数量对比", data.getNegativeReviews().getCurrent(), 
+                data.getNegativeReviews().getPrevious(), data.getNegativeReviews().getChangeRate()));
+        }
+        // 9. 差评占比对比
+        if (data.getNegativeReviewRatio() != null) {
+            rows.add(new DataRow("差评占比对比", data.getNegativeReviewRatio().getCurrent(), 
+                data.getNegativeReviewRatio().getPrevious(), data.getNegativeReviewRatio().getChangeRate()));
+        }
+        // 10. 差评申诉次数对比
+        if (data.getNegativeReviewAppealsCount() != null) {
+            rows.add(new DataRow("差评申诉次数对比", data.getNegativeReviewAppealsCount().getCurrent(), 
+                data.getNegativeReviewAppealsCount().getPrevious(), data.getNegativeReviewAppealsCount().getChangeRate()));
+        }
+        // 11. 差评申诉成功次数对比
+        if (data.getNegativeReviewAppealsSuccessCount() != null) {
+            rows.add(new DataRow("差评申诉成功次数对比", data.getNegativeReviewAppealsSuccessCount().getCurrent(), 
+                data.getNegativeReviewAppealsSuccessCount().getPrevious(), data.getNegativeReviewAppealsSuccessCount().getChangeRate()));
+        }
+        // 12. 差评申诉成功占比对比
+        if (data.getNegativeReviewAppealsSuccessRatio() != null) {
+            rows.add(new DataRow("差评申诉成功占比对比", data.getNegativeReviewAppealsSuccessRatio().getCurrent(), 
+                data.getNegativeReviewAppealsSuccessRatio().getPrevious(), data.getNegativeReviewAppealsSuccessRatio().getChangeRate()));
+        }
+        return rows;
+    }
+
+    /**
+     * 绘制价目表排名数据区块(特殊格式,每个价目表显示3个指标)
+     */
+    private static int drawPriceListRankingSection(Graphics2D g2d, int y, String sectionTitle, 
+                                                   List<StoreOperationalStatisticsComparisonVo.PriceListRankingComparison> rankings) {
+        if (rankings == null || rankings.isEmpty()) {
+            return y;
+        }
+        
+        // 绘制区块标题
+        Font sectionFont = getChineseFont(Font.BOLD, SECTION_TITLE_FONT_SIZE);
+        g2d.setFont(sectionFont);
+        g2d.setColor(SECTION_TITLE_COLOR);
+        g2d.drawString(sectionTitle, PADDING, y);
+        y += 30;
+
+        Font dataFont = getChineseFont(Font.PLAIN, DATA_FONT_SIZE);
+        Font labelFont = getChineseFont(Font.PLAIN, LABEL_FONT_SIZE);
+
+        // 遍历每个价目表
+        for (StoreOperationalStatisticsComparisonVo.PriceListRankingComparison ranking : rankings) {
+            // 绘制价目表名称(作为子标题)
+            g2d.setFont(getChineseFont(Font.BOLD, LABEL_FONT_SIZE));
+            g2d.setColor(new Color(66, 66, 66));
+            String priceListName = ranking.getPriceListItemName() != null ? ranking.getPriceListItemName() : 
+                ("价目表ID: " + ranking.getPriceId());
+            g2d.drawString(priceListName, PADDING + 20, y);
+            y += 25;
+            
+            // 绘制表头
+            y = drawTableHeader(g2d, y);
+            
+            // 绘制该价目表的3个指标
+            List<DataRow> rows = new ArrayList<>();
+            if (ranking.getPageViews() != null) {
+                rows.add(new DataRow("浏览量", ranking.getPageViews().getCurrent(), 
+                    ranking.getPageViews().getPrevious(), ranking.getPageViews().getChangeRate()));
+            }
+            if (ranking.getVisitors() != null) {
+                rows.add(new DataRow("访客", ranking.getVisitors().getCurrent(), 
+                    ranking.getVisitors().getPrevious(), ranking.getVisitors().getChangeRate()));
+            }
+            if (ranking.getShares() != null) {
+                rows.add(new DataRow("分享数", ranking.getShares().getCurrent(), 
+                    ranking.getShares().getPrevious(), ranking.getShares().getChangeRate()));
+            }
+            
+            // 绘制数据行
+            for (DataRow row : rows) {
+                y = drawDataRow(g2d, y, row, dataFont, labelFont);
+            }
+            
+            y += SECTION_SPACING / 2; // 价目表之间的间距
+        }
+        
+        return y + SECTION_SPACING;
+    }
+
+    /**
+     * 数据行内部类
+     */
+    private static class DataRow {
+        private String label;
+        private Object current;
+        private Object previous;
+        private BigDecimal changeRate;
+
+        public DataRow(String label, Object current, Object previous, BigDecimal changeRate) {
+            this.label = label;
+            this.current = current;
+            this.previous = previous;
+            this.changeRate = changeRate;
+        }
+
+        public String getLabel() {
+            return label;
+        }
+
+        public Object getCurrent() {
+            return current;
+        }
+
+        public Object getPrevious() {
+            return previous;
+        }
+
+        public BigDecimal getChangeRate() {
+            return changeRate;
+        }
+    }
+}

BIN
alien-store-platform/src/main/resources/fonts/NotoSansCJKsc-Regular.otf