浏览代码

Merge remote-tracking branch 'origin/sit' into sit

panzhilin 2 月之前
父节点
当前提交
de510d5aa9
共有 22 个文件被更改,包括 3036 次插入355 次删除
  1. 1 0
      alien-entity/pom.xml
  2. 60 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreOperationalStatisticsHistory.java
  3. 268 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOperationalStatisticsComparisonVo.java
  4. 269 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOperationalStatisticsVo.java
  5. 16 0
      alien-entity/src/main/java/shop/alien/mapper/StoreOperationalStatisticsHistoryMapper.java
  6. 137 0
      alien-entity/src/main/java/shop/alien/mapper/StoreTrackEventMapper.java
  7. 28 0
      alien-store/doc/埋点统计数据JSON格式说明.md
  8. 1 0
      alien-store/pom.xml
  9. 385 69
      alien-store/src/main/java/shop/alien/store/aspect/TrackEventAspect.java
  10. 1 1
      alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java
  11. 1 6
      alien-store/src/main/java/shop/alien/store/controller/CommonCommentController.java
  12. 7 6
      alien-store/src/main/java/shop/alien/store/controller/StoreCuisineController.java
  13. 0 6
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  14. 225 0
      alien-store/src/main/java/shop/alien/store/controller/StoreOperationalStatisticsController.java
  15. 7 6
      alien-store/src/main/java/shop/alien/store/controller/StorePriceController.java
  16. 0 6
      alien-store/src/main/java/shop/alien/store/controller/UserStoreController.java
  17. 64 0
      alien-store/src/main/java/shop/alien/store/service/StoreOperationalStatisticsService.java
  18. 663 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalStatisticsServiceImpl.java
  19. 838 203
      alien-store/src/main/java/shop/alien/store/service/impl/TrackEventServiceImpl.java
  20. 1 0
      alien-util/pom.xml
  21. 52 52
      alien-util/src/main/java/shop/alien/util/encryption/StandardAesUtil.java
  22. 12 0
      pom.xml

+ 1 - 0
alien-entity/pom.xml

@@ -103,6 +103,7 @@
                 <configuration>
                     <source>8</source>
                     <target>8</target>
+                    <parameters>true</parameters>
                 </configuration>
             </plugin>
         </plugins>

+ 60 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreOperationalStatisticsHistory.java

@@ -0,0 +1,60 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 商家经营数据统计历史表
+ *
+ * @author auto-generated
+ * @since 2025-01-05
+ */
+@Data
+@JsonInclude
+@TableName("store_operational_statistics_history")
+@ApiModel(value = "StoreOperationalStatisticsHistory对象", description = "商家经营数据统计历史表")
+public class StoreOperationalStatisticsHistory {
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "商铺ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "统计开始时间")
+    @TableField("start_time")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "统计结束时间")
+    @TableField("end_time")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "统计数据(JSON格式,包含所有统计字段)")
+    @TableField("statistics_data")
+    private String statisticsData;
+
+    @ApiModelProperty(value = "查询时间(统计数据的时间)")
+    @TableField("query_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date queryTime;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+}

+ 268 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOperationalStatisticsComparisonVo.java

@@ -0,0 +1,268 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 商家经营统计数据对比VO(包含当期、上期和变化率)
+ *
+ * @author system
+ * @version 1.0
+ * @date 2026/01/05
+ */
+@Data
+@ApiModel(value = "StoreOperationalStatisticsComparisonVo对象", description = "商家经营统计数据对比")
+public class StoreOperationalStatisticsComparisonVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("当期开始时间")
+    private String currentStartTime;
+
+    @ApiModelProperty("当期结束时间")
+    private String currentEndTime;
+
+    @ApiModelProperty("上期开始时间")
+    private String previousStartTime;
+
+    @ApiModelProperty("上期结束时间")
+    private String previousEndTime;
+
+    /**
+     * 流量数据对比
+     */
+    @ApiModelProperty("流量数据对比")
+    private TrafficDataComparison trafficData;
+
+    /**
+     * 互动数据对比
+     */
+    @ApiModelProperty("互动数据对比")
+    private InteractionDataComparison interactionData;
+
+    /**
+     * 优惠券数据对比
+     */
+    @ApiModelProperty("优惠券数据对比")
+    private CouponDataComparison couponData;
+
+    /**
+     * 代金券数据对比
+     */
+    @ApiModelProperty("代金券数据对比")
+    private VoucherDataComparison voucherData;
+
+    /**
+     * 服务质量数据对比
+     */
+    @ApiModelProperty("服务质量数据对比")
+    private ServiceQualityDataComparison serviceQualityData;
+
+    /**
+     * 基础对比数据
+     */
+    @Data
+    @ApiModel("基础对比数据")
+    public static class BaseComparisonData implements Serializable {
+        @ApiModelProperty("当期值")
+        private Object current;
+
+        @ApiModelProperty("上期值")
+        private Object previous;
+
+        @ApiModelProperty("变化率(百分比)")
+        private BigDecimal changeRate;
+    }
+
+    /**
+     * 流量数据对比
+     */
+    @Data
+    @ApiModel("流量数据对比")
+    public static class TrafficDataComparison implements Serializable {
+        @ApiModelProperty("店铺搜索量对比")
+        private BaseComparisonData storeSearchVolume;
+
+        @ApiModelProperty("浏览量对比")
+        private BaseComparisonData pageViews;
+
+        @ApiModelProperty("访客数对比")
+        private BaseComparisonData visitors;
+
+        @ApiModelProperty("新增访客数对比")
+        private BaseComparisonData newVisitors;
+
+        @ApiModelProperty("访问时长对比")
+        private BaseComparisonData visitDuration;
+
+        @ApiModelProperty("平均访问时长对比")
+        private BaseComparisonData avgVisitDuration;
+    }
+
+    /**
+     * 互动数据对比
+     */
+    @Data
+    @ApiModel("互动数据对比")
+    public static class InteractionDataComparison implements Serializable {
+        @ApiModelProperty("店铺收藏次数对比")
+        private BaseComparisonData storeCollectionCount;
+
+        @ApiModelProperty("店铺分享次数对比")
+        private BaseComparisonData storeShareCount;
+
+        @ApiModelProperty("店铺打卡次数对比")
+        private BaseComparisonData storeCheckInCount;
+
+        @ApiModelProperty("咨询商家次数对比")
+        private BaseComparisonData consultMerchantCount;
+
+        @ApiModelProperty("好友数量对比")
+        private BaseComparisonData friendsCount;
+
+        @ApiModelProperty("关注数量对比")
+        private BaseComparisonData followCount;
+
+        @ApiModelProperty("粉丝数量对比")
+        private BaseComparisonData fansCount;
+
+        @ApiModelProperty("发布动态数量对比")
+        private BaseComparisonData postsPublishedCount;
+
+        @ApiModelProperty("动态点赞数量对比")
+        private BaseComparisonData postLikesCount;
+
+        @ApiModelProperty("动态评论数量对比")
+        private BaseComparisonData postCommentsCount;
+
+        @ApiModelProperty("动态转发数量对比")
+        private BaseComparisonData postSharesCount;
+
+        @ApiModelProperty("被举报次数对比")
+        private BaseComparisonData reportedCount;
+
+        @ApiModelProperty("被拉黑次数对比")
+        private BaseComparisonData blockedCount;
+    }
+
+    /**
+     * 优惠券数据对比
+     */
+    @Data
+    @ApiModel("优惠券数据对比")
+    public static class CouponDataComparison implements Serializable {
+        @ApiModelProperty("赠送好友数量对比")
+        private BaseComparisonData giftToFriendsCount;
+
+        @ApiModelProperty("赠送好友金额合计对比")
+        private BaseComparisonData giftToFriendsAmount;
+
+        @ApiModelProperty("赠送好友使用数量对比")
+        private BaseComparisonData giftToFriendsUsedCount;
+
+        @ApiModelProperty("赠送好友使用金额合计对比")
+        private BaseComparisonData giftToFriendsUsedAmount;
+
+        @ApiModelProperty("赠送好友使用金额占比对比")
+        private BaseComparisonData giftToFriendsUsedAmountRatio;
+
+        @ApiModelProperty("好友赠送数量对比")
+        private BaseComparisonData friendsGiftCount;
+
+        @ApiModelProperty("好友赠送金额合计对比")
+        private BaseComparisonData friendsGiftAmount;
+
+        @ApiModelProperty("好友赠送使用数量对比")
+        private BaseComparisonData friendsGiftUsedCount;
+
+        @ApiModelProperty("好友赠送使用金额合计对比")
+        private BaseComparisonData friendsGiftUsedAmount;
+
+        @ApiModelProperty("好友赠送使用金额占比对比")
+        private BaseComparisonData friendsGiftUsedAmountRatio;
+    }
+
+    /**
+     * 代金券数据对比
+     */
+    @Data
+    @ApiModel("代金券数据对比")
+    public static class VoucherDataComparison implements Serializable {
+        @ApiModelProperty("赠送好友数量对比")
+        private BaseComparisonData giftToFriendsCount;
+
+        @ApiModelProperty("赠送好友金额合计对比")
+        private BaseComparisonData giftToFriendsAmount;
+
+        @ApiModelProperty("赠送好友使用数量对比")
+        private BaseComparisonData giftToFriendsUsedCount;
+
+        @ApiModelProperty("赠送好友使用金额合计对比")
+        private BaseComparisonData giftToFriendsUsedAmount;
+
+        @ApiModelProperty("赠送好友使用金额占比对比")
+        private BaseComparisonData giftToFriendsUsedAmountRatio;
+
+        @ApiModelProperty("好友赠送数量对比")
+        private BaseComparisonData friendsGiftCount;
+
+        @ApiModelProperty("好友赠送金额合计对比")
+        private BaseComparisonData friendsGiftAmount;
+
+        @ApiModelProperty("好友赠送使用数量对比")
+        private BaseComparisonData friendsGiftUsedCount;
+
+        @ApiModelProperty("好友赠送使用金额合计对比")
+        private BaseComparisonData friendsGiftUsedAmount;
+
+        @ApiModelProperty("好友赠送使用金额占比对比")
+        private BaseComparisonData friendsGiftUsedAmountRatio;
+    }
+
+    /**
+     * 服务质量数据对比
+     */
+    @Data
+    @ApiModel("服务质量数据对比")
+    public static class ServiceQualityDataComparison implements Serializable {
+        @ApiModelProperty("店铺评分对比")
+        private BaseComparisonData storeRating;
+
+        @ApiModelProperty("口味评分对比")
+        private BaseComparisonData tasteRating;
+
+        @ApiModelProperty("环境评分对比")
+        private BaseComparisonData environmentRating;
+
+        @ApiModelProperty("服务评分对比")
+        private BaseComparisonData serviceRating;
+
+        @ApiModelProperty("评价数量对比")
+        private BaseComparisonData totalReviews;
+
+        @ApiModelProperty("好评数量对比")
+        private BaseComparisonData positiveReviews;
+
+        @ApiModelProperty("中评数量对比")
+        private BaseComparisonData neutralReviews;
+
+        @ApiModelProperty("差评数量对比")
+        private BaseComparisonData negativeReviews;
+
+        @ApiModelProperty("差评占比对比")
+        private BaseComparisonData negativeReviewRatio;
+
+        @ApiModelProperty("差评申诉次数对比")
+        private BaseComparisonData negativeReviewAppealsCount;
+
+        @ApiModelProperty("差评申诉成功次数对比")
+        private BaseComparisonData negativeReviewAppealsSuccessCount;
+
+        @ApiModelProperty("差评申诉成功占比对比")
+        private BaseComparisonData negativeReviewAppealsSuccessRatio;
+    }
+}

+ 269 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOperationalStatisticsVo.java

@@ -0,0 +1,269 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 商家经营统计数据VO
+ *
+ * @author system
+ * @version 1.0
+ * @date 2026/01/05
+ */
+@Data
+@ApiModel(value = "StoreOperationalStatisticsVo对象", description = "商家经营统计数据")
+public class StoreOperationalStatisticsVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 流量数据
+     */
+    @ApiModelProperty("流量数据")
+    private TrafficData trafficData;
+
+    /**
+     * 互动数据
+     */
+    @ApiModelProperty("互动数据")
+    private InteractionData interactionData;
+
+    /**
+     * 优惠券数据
+     */
+    @ApiModelProperty("优惠券数据")
+    private CouponData couponData;
+
+    /**
+     * 代金券数据
+     */
+    @ApiModelProperty("代金券数据")
+    private VoucherData voucherData;
+
+    /**
+     * 服务质量数据
+     */
+    @ApiModelProperty("服务质量数据")
+    private ServiceQualityData serviceQualityData;
+
+    /**
+     * 价目表排名数据
+     */
+    @ApiModelProperty("价目表排名数据")
+    private List<PriceListRanking> priceListRanking;
+
+    /**
+     * 流量数据
+     */
+    @Data
+    @ApiModel("流量数据")
+    public static class TrafficData implements Serializable {
+        @ApiModelProperty("店铺搜索量")
+        private Long storeSearchVolume;
+
+        @ApiModelProperty("浏览量")
+        private Long pageViews;
+
+        @ApiModelProperty("访客数")
+        private Long visitors;
+
+        @ApiModelProperty("新增访客数")
+        private Long newVisitors;
+
+        @ApiModelProperty("访问时长(秒)")
+        private Long visitDuration;
+
+        @ApiModelProperty("平均访问时长(秒)")
+        private Long avgVisitDuration;
+    }
+
+    /**
+     * 互动数据
+     */
+    @Data
+    @ApiModel("互动数据")
+    public static class InteractionData implements Serializable {
+        @ApiModelProperty("店铺收藏次数")
+        private Long storeCollectionCount;
+
+        @ApiModelProperty("店铺分享次数")
+        private Long storeShareCount;
+
+        @ApiModelProperty("店铺打卡次数")
+        private Long storeCheckInCount;
+
+        @ApiModelProperty("咨询商家次数")
+        private Long consultMerchantCount;
+
+        @ApiModelProperty("好友数量")
+        private Long friendsCount;
+
+        @ApiModelProperty("关注数量")
+        private Long followCount;
+
+        @ApiModelProperty("粉丝数量")
+        private Long fansCount;
+
+        @ApiModelProperty("发布动态数量")
+        private Long postsPublishedCount;
+
+        @ApiModelProperty("动态点赞数量")
+        private Long postLikesCount;
+
+        @ApiModelProperty("动态评论数量")
+        private Long postCommentsCount;
+
+        @ApiModelProperty("动态转发数量")
+        private Long postSharesCount;
+
+        @ApiModelProperty("被举报次数")
+        private Long reportedCount;
+
+        @ApiModelProperty("被拉黑次数")
+        private Long blockedCount;
+    }
+
+    /**
+     * 优惠券数据
+     */
+    @Data
+    @ApiModel("优惠券数据")
+    public static class CouponData implements Serializable {
+        @ApiModelProperty("赠送好友数量")
+        private Long giftToFriendsCount;
+
+        @ApiModelProperty("赠送好友金额合计")
+        private BigDecimal giftToFriendsAmount;
+
+        @ApiModelProperty("赠送好友使用数量")
+        private Long giftToFriendsUsedCount;
+
+        @ApiModelProperty("赠送好友使用金额合计")
+        private BigDecimal giftToFriendsUsedAmount;
+
+        @ApiModelProperty("赠送好友使用金额占比")
+        private BigDecimal giftToFriendsUsedAmountRatio;
+
+        @ApiModelProperty("好友赠送数量")
+        private Long friendsGiftCount;
+
+        @ApiModelProperty("好友赠送金额合计")
+        private BigDecimal friendsGiftAmount;
+
+        @ApiModelProperty("好友赠送使用数量")
+        private Long friendsGiftUsedCount;
+
+        @ApiModelProperty("好友赠送使用金额合计")
+        private BigDecimal friendsGiftUsedAmount;
+
+        @ApiModelProperty("好友赠送使用金额占比")
+        private BigDecimal friendsGiftUsedAmountRatio;
+    }
+
+    /**
+     * 代金券数据
+     */
+    @Data
+    @ApiModel("代金券数据")
+    public static class VoucherData implements Serializable {
+        @ApiModelProperty("赠送好友数量")
+        private Long giftToFriendsCount;
+
+        @ApiModelProperty("赠送好友金额合计")
+        private BigDecimal giftToFriendsAmount;
+
+        @ApiModelProperty("赠送好友使用数量")
+        private Long giftToFriendsUsedCount;
+
+        @ApiModelProperty("赠送好友使用金额合计")
+        private BigDecimal giftToFriendsUsedAmount;
+
+        @ApiModelProperty("赠送好友使用金额占比")
+        private BigDecimal giftToFriendsUsedAmountRatio;
+
+        @ApiModelProperty("好友赠送数量")
+        private Long friendsGiftCount;
+
+        @ApiModelProperty("好友赠送金额合计")
+        private BigDecimal friendsGiftAmount;
+
+        @ApiModelProperty("好友赠送使用数量")
+        private Long friendsGiftUsedCount;
+
+        @ApiModelProperty("好友赠送使用金额合计")
+        private BigDecimal friendsGiftUsedAmount;
+
+        @ApiModelProperty("好友赠送使用金额占比")
+        private BigDecimal friendsGiftUsedAmountRatio;
+    }
+
+    /**
+     * 服务质量数据
+     */
+    @Data
+    @ApiModel("服务质量数据")
+    public static class ServiceQualityData implements Serializable {
+        @ApiModelProperty("店铺评分")
+        private BigDecimal storeRating;
+
+        @ApiModelProperty("口味评分")
+        private BigDecimal tasteRating;
+
+        @ApiModelProperty("环境评分")
+        private BigDecimal environmentRating;
+
+        @ApiModelProperty("服务评分")
+        private BigDecimal serviceRating;
+
+        @ApiModelProperty("评价数量")
+        private Long totalReviews;
+
+        @ApiModelProperty("好评数量")
+        private Long positiveReviews;
+
+        @ApiModelProperty("中评数量")
+        private Long neutralReviews;
+
+        @ApiModelProperty("差评数量")
+        private Long negativeReviews;
+
+        @ApiModelProperty("差评占比")
+        private BigDecimal negativeReviewRatio;
+
+        @ApiModelProperty("差评申诉次数")
+        private Long negativeReviewAppealsCount;
+
+        @ApiModelProperty("差评申诉成功次数")
+        private Long negativeReviewAppealsSuccessCount;
+
+        @ApiModelProperty("差评申诉成功占比")
+        private BigDecimal negativeReviewAppealsSuccessRatio;
+    }
+
+    /**
+     * 价目表排名
+     */
+    @Data
+    @ApiModel("价目表排名")
+    public static class PriceListRanking implements Serializable {
+        @ApiModelProperty("排名")
+        private Integer rank;
+
+        @ApiModelProperty("价目表名称")
+        private String priceListItemName;
+
+        @ApiModelProperty("浏览量")
+        private Long pageViews;
+
+        @ApiModelProperty("访客数")
+        private Long visitors;
+
+        @ApiModelProperty("分享数")
+        private Long shares;
+    }
+}

+ 16 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreOperationalStatisticsHistoryMapper.java

@@ -0,0 +1,16 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StoreOperationalStatisticsHistory;
+
+/**
+ * 商家经营数据统计历史表 Mapper 接口
+ *
+ * @author auto-generated
+ * @since 2025-01-05
+ */
+@Mapper
+public interface StoreOperationalStatisticsHistoryMapper extends BaseMapper<StoreOperationalStatisticsHistory> {
+
+}

+ 137 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreTrackEventMapper.java

@@ -2,8 +2,14 @@ package shop.alien.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 import shop.alien.entity.store.StoreTrackEvent;
 
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
 /**
  * 埋点事件Mapper接口
  *
@@ -12,4 +18,135 @@ import shop.alien.entity.store.StoreTrackEvent;
  */
 @Mapper
 public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
+    
+    /**
+     * 统计访客数(去重userId)- 使用数据库聚合查询
+     */
+    @Select("SELECT COUNT(DISTINCT user_id) as visitorCount " +
+            "FROM store_track_event " +
+            "WHERE store_id = #{storeId} " +
+            "AND event_time >= #{startDate} " +
+            "AND event_time < #{endDate} " +
+            "AND delete_flag = 0 " +
+            "AND event_category = 'TRAFFIC' " +
+            "AND user_id IS NOT NULL")
+    Long countDistinctVisitors(@Param("storeId") Integer storeId,
+                               @Param("startDate") Date startDate,
+                               @Param("endDate") Date endDate);
+    
+    /**
+     * 统计新增访客数 - 使用数据库聚合查询
+     */
+    @Select("SELECT COUNT(DISTINCT t1.user_id) as newVisitorCount " +
+            "FROM store_track_event t1 " +
+            "WHERE t1.store_id = #{storeId} " +
+            "AND t1.event_time >= #{startDate} " +
+            "AND t1.event_time < #{endDate} " +
+            "AND t1.delete_flag = 0 " +
+            "AND t1.event_category = 'TRAFFIC' " +
+            "AND t1.user_id IS NOT NULL " +
+            "AND NOT EXISTS (" +
+            "    SELECT 1 FROM store_track_event t2 " +
+            "    WHERE t2.store_id = #{storeId} " +
+            "    AND t2.user_id = t1.user_id " +
+            "    AND t2.event_time < #{startDate} " +
+            "    AND t2.delete_flag = 0" +
+            ")")
+    Long countNewVisitors(@Param("storeId") Integer storeId,
+                          @Param("startDate") Date startDate,
+                          @Param("endDate") Date endDate);
+    
+    /**
+     * 统计流量数据:搜索量、浏览量、访问时长 - 使用数据库聚合查询
+     */
+    @Select("SELECT " +
+            "    SUM(CASE WHEN event_type = 'SEARCH' THEN 1 ELSE 0 END) as searchCount, " +
+            "    SUM(CASE WHEN event_type = 'VIEW' THEN 1 ELSE 0 END) as viewCount, " +
+            "    COALESCE(SUM(CASE WHEN event_type = 'VIEW' AND duration IS NOT NULL THEN duration ELSE 0 END), 0) as totalDuration, " +
+            "    COALESCE(AVG(CASE WHEN event_type = 'VIEW' AND duration IS NOT NULL THEN duration END), 0) as avgDuration " +
+            "FROM store_track_event " +
+            "WHERE store_id = #{storeId} " +
+            "AND event_time >= #{startDate} " +
+            "AND event_time < #{endDate} " +
+            "AND delete_flag = 0 " +
+            "AND event_category = 'TRAFFIC'")
+    Map<String, Object> calculateTrafficStatistics(@Param("storeId") Integer storeId,
+                                                    @Param("startDate") Date startDate,
+                                                    @Param("endDate") Date endDate);
+    
+    /**
+     * 按价目表ID统计浏览量、访客数、分享数 - 使用数据库聚合查询
+     */
+    @Select("SELECT " +
+            "    target_id as priceId, " +
+            "    SUM(CASE WHEN event_type = 'PRICE_VIEW' THEN 1 ELSE 0 END) as viewCount, " +
+            "    COUNT(DISTINCT CASE WHEN user_id IS NOT NULL THEN user_id END) as visitorCount, " +
+            "    SUM(CASE WHEN event_type = 'PRICE_SHARE' THEN 1 ELSE 0 END) as shareCount " +
+            "FROM store_track_event " +
+            "WHERE store_id = #{storeId} " +
+            "AND event_time >= #{startDate} " +
+            "AND event_time < #{endDate} " +
+            "AND delete_flag = 0 " +
+            "AND event_category = 'PRICE' " +
+            "AND target_id IS NOT NULL " +
+            "GROUP BY target_id " +
+            "ORDER BY viewCount DESC")
+    List<Map<String, Object>> calculatePriceRanking(@Param("storeId") Integer storeId,
+                                                     @Param("startDate") Date startDate,
+                                                     @Param("endDate") Date endDate);
+    
+    /**
+     * 统计互动数据(按事件类型统计数量)- 使用数据库聚合查询
+     * 从埋点事件表store_track_event中统计互动相关事件
+     */
+    @Select("SELECT " +
+            "    event_type, " +
+            "    COUNT(*) as count " +
+            "FROM store_track_event " +
+            "WHERE store_id = #{storeId} " +
+            "AND store_id IS NOT NULL " +
+            "AND event_time >= #{startDate} " +
+            "AND event_time < #{endDate} " +
+            "AND delete_flag = 0 " +
+            "AND event_category = 'INTERACTION' " +
+            "GROUP BY event_type")
+    List<Map<String, Object>> calculateInteractionStatistics(@Param("storeId") Integer storeId,
+                                                              @Param("startDate") Date startDate,
+                                                              @Param("endDate") Date endDate);
+    
+    /**
+     * 统计代金券数据(按事件类型统计数量和金额)- 使用数据库聚合查询
+     */
+    @Select("SELECT " +
+            "    event_type, " +
+            "    COUNT(*) as count, " +
+            "    COALESCE(SUM(amount), 0) as totalAmount " +
+            "FROM store_track_event " +
+            "WHERE store_id = #{storeId} " +
+            "AND event_time >= #{startDate} " +
+            "AND event_time < #{endDate} " +
+            "AND delete_flag = 0 " +
+            "AND event_category = 'VOUCHER' " +
+            "AND event_type IN ('VOUCHER_GIVE', 'VOUCHER_USE') " +
+            "GROUP BY event_type")
+    List<Map<String, Object>> calculateVoucherStatistics(@Param("storeId") Integer storeId,
+                                                          @Param("startDate") Date startDate,
+                                                          @Param("endDate") Date endDate);
+    
+    /**
+     * 统计服务质量数据(按事件类型统计数量)- 使用数据库聚合查询
+     */
+    @Select("SELECT " +
+            "    event_type, " +
+            "    COUNT(*) as count " +
+            "FROM store_track_event " +
+            "WHERE store_id = #{storeId} " +
+            "AND event_time >= #{startDate} " +
+            "AND event_time < #{endDate} " +
+            "AND delete_flag = 0 " +
+            "AND event_category = 'SERVICE' " +
+            "GROUP BY event_type")
+    List<Map<String, Object>> calculateServiceStatistics(@Param("storeId") Integer storeId,
+                                                          @Param("startDate") Date startDate,
+                                                          @Param("endDate") Date endDate);
 }

+ 28 - 0
alien-store/doc/埋点统计数据JSON格式说明.md

@@ -5,6 +5,34 @@
 `store_track_statistics` 表中的各个数据字段存储的是JSON格式的统计数据。本文档详细说明各个字段的JSON格式。
 
 ````sql
+-- 埋点事件表
+CREATE TABLE `store_track_event` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `event_type` varchar(50) NOT NULL COMMENT '事件类型(VIEW-浏览,SEARCH-搜索,COLLECT-收藏,SHARE-分享,CHECKIN-打卡,CONSULT-咨询,FOLLOW-关注,UNFOLLOW-取消关注,FRIEND_ADD-添加好友,POST_PUBLISH-发布动态,POST_LIKE-动态点赞,POST_COMMENT-动态评论,POST_REPOST-动态转发,REPORT-举报,BLOCK-拉黑,COUPON_GIVE-赠送优惠券,COUPON_USE-使用优惠券,VOUCHER_GIVE-赠送代金券,VOUCHER_USE-使用代金券,PRICE_VIEW-价目表浏览,PRICE_SHARE-价目表分享,RATING-评价,APPEAL-申诉)',
+  `event_category` varchar(50) NOT NULL COMMENT '事件分类(TRAFFIC-流量数据,INTERACTION-互动数据,COUPON-优惠券,VOUCHER-代金券,SERVICE-服务质量,PRICE-价目表)',
+  `user_id` int(11) DEFAULT NULL COMMENT '用户ID',
+  `store_id` int(11) DEFAULT NULL COMMENT '店铺ID',
+  `target_id` int(11) DEFAULT NULL COMMENT '目标对象ID(如价目表ID、动态ID等)',
+  `target_type` varchar(50) DEFAULT NULL COMMENT '目标对象类型(PRICE-价目表,POST-动态,STORE-店铺等)',
+  `event_data` text COMMENT '事件附加数据(JSON格式)',
+  `amount` decimal(10,2) DEFAULT NULL COMMENT '金额(用于优惠券、代金券等)',
+  `duration` bigint(20) DEFAULT NULL COMMENT '时长(毫秒,用于访问时长等)',
+  `ip_address` varchar(50) DEFAULT NULL COMMENT 'IP地址',
+  `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理',
+  `device_type` varchar(20) DEFAULT NULL COMMENT '设备类型(IOS,ANDROID,WEB)',
+  `app_version` varchar(20) DEFAULT NULL COMMENT 'APP版本号',
+  `event_time` datetime NOT NULL COMMENT '事件发生时间',
+  `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `delete_flag` int(1) NOT NULL DEFAULT '0' COMMENT '删除标记(0:未删除,1:已删除)',
+  PRIMARY KEY (`id`),
+  KEY `idx_store_id` (`store_id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_event_type` (`event_type`),
+  KEY `idx_event_category` (`event_category`),
+  KEY `idx_event_time` (`event_time`),
+  KEY `idx_store_event_time` (`store_id`,`event_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='埋点事件表';
+
 -- 埋点统计表
 CREATE TABLE `store_track_statistics` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',

+ 1 - 0
alien-store/pom.xml

@@ -313,6 +313,7 @@
                     <target>1.8</target>
                     <source>1.8</source>
                     <encoding>UTF-8</encoding>
+                    <parameters>true</parameters>
                     <!-- <compilerArguments> <extdirs>lib</extdirs> </compilerArguments> -->
                 </configuration>
             </plugin>

+ 385 - 69
alien-store/src/main/java/shop/alien/store/aspect/TrackEventAspect.java

@@ -1,7 +1,7 @@
 package shop.alien.store.aspect;
 
 import com.alibaba.fastjson.JSONObject;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.alibaba.fastjson2.JSONArray;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -22,7 +22,9 @@ import org.springframework.web.context.request.ServletRequestAttributes;
 import shop.alien.entity.store.*;
 import shop.alien.mapper.*;
 import shop.alien.store.annotation.TrackEvent;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import shop.alien.store.config.BaseRedisService;
+import shop.alien.store.util.UserAgentParserUtil;
 import shop.alien.util.common.JwtUtil;
 
 import javax.servlet.http.HttpServletRequest;
@@ -44,10 +46,12 @@ import java.util.Date;
 public class TrackEventAspect {
 
     private final BaseRedisService baseRedisService;
+    private final ObjectMapper objectMapper;
     private final LifeUserDynamicsMapper lifeUserDynamicsMapper;
     private final CommonRatingMapper commonRatingMapper;
     private final StoreUserMapper storeUserMapper;
-    private final ObjectMapper objectMapper;
+    private final StorePriceMapper storePriceMapper;
+    private final StoreCuisineMapper storeCuisineMapper;
     
     private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
     private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@@ -81,13 +85,22 @@ public class TrackEventAspect {
         Object result = joinPoint.proceed();
 
         try {
-            // 构建埋点事件对象
-            StoreTrackEvent trackEvent = buildTrackEvent(joinPoint, annotation, result);
+            // 特殊处理:搜索接口需要为每个搜索结果中的店铺记录一条埋点
+            String methodName = signature.getMethod().getName();
+            String className = joinPoint.getTarget().getClass().getSimpleName();
             
-            // 异步写入Redis List
-            if (annotation.async()) {
-                String eventJson = objectMapper.writeValueAsString(trackEvent);
-                baseRedisService.setListRight(REDIS_QUEUE_KEY, eventJson);
+            if ("AiSearchController".equals(className) && "search".equals(methodName)) {
+                // 搜索接口:为每个搜索结果中的店铺记录埋点
+                handleSearchEventTracking(joinPoint, annotation, result);
+            } else {
+                // 其他接口:正常处理
+                StoreTrackEvent trackEvent = buildTrackEvent(joinPoint, annotation, result);
+                
+                // 异步写入Redis List
+                if (annotation.async()) {
+                    String eventJson = objectMapper.writeValueAsString(trackEvent);
+                    baseRedisService.setListRight(REDIS_QUEUE_KEY, eventJson);
+                }
             }
         } catch (Exception e) {
             // 埋点失败不应该影响主流程
@@ -113,12 +126,26 @@ public class TrackEventAspect {
         EvaluationContext context = createEvaluationContext(joinPoint, result);
 
         // 解析SpEL表达式获取storeId
-        Integer storeId = parseSpEL(annotation.storeId(), context, Integer.class);
-        if (storeId == null && StringUtils.isBlank(annotation.storeId())) {
-            // 尝试从方法参数中查找storeId
+        Integer storeId = null;
+        if (StringUtils.isNotBlank(annotation.storeId())) {
+            storeId = parseSpEL(annotation.storeId(), context, Integer.class);
+            if (storeId == null) {
+                // SpEL解析失败,记录详细信息用于调试
+                MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+                String methodName = signature.getMethod().getName();
+                String className = joinPoint.getTarget().getClass().getSimpleName();
+                log.warn("SpEL表达式解析失败: className={}, methodName={}, storeId表达式={}, 参数={}", 
+                        className, methodName, annotation.storeId(), 
+                        java.util.Arrays.toString(joinPoint.getArgs()));
+            }
+        }
+        
+        // 如果SpEL解析失败,尝试从方法参数中查找storeId
+        if (storeId == null) {
             storeId = extractStoreIdFromArgs(joinPoint);
         }
-        // 如果storeId仍为空,根据接口类型和参数查询店铺ID
+        
+        // 如果仍然为空,根据接口类型和参数查询店铺ID(业务逻辑查询)
         if (storeId == null) {
             storeId = queryStoreIdByBusinessLogic(joinPoint, annotation, context);
         }
@@ -172,9 +199,38 @@ public class TrackEventAspect {
         Object[] args = joinPoint.getArgs();
         String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
         
-        if (parameterNames != null && args != null) {
-            for (int i = 0; i < parameterNames.length; i++) {
-                context.setVariable(parameterNames[i], args[i]);
+        if (args != null) {
+            if (parameterNames != null && parameterNames.length == args.length) {
+                // 使用真实的参数名
+                for (int i = 0; i < parameterNames.length; i++) {
+                    context.setVariable(parameterNames[i], args[i]);
+                }
+            } else {
+                // 如果参数名获取失败(可能是编译时没有保留参数名),使用类型推断
+                Class<?>[] paramTypes = method.getParameterTypes();
+                for (int i = 0; i < args.length && i < paramTypes.length; i++) {
+                    Object arg = args[i];
+                    if (arg != null) {
+                        // 尝试使用类型名作为变量名(首字母小写)
+                        String typeName = paramTypes[i].getSimpleName();
+                        String varName = Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1);
+                        context.setVariable(varName, arg);
+                        
+                        // 同时尝试使用常见的参数名
+                        if (arg instanceof StoreClockIn) {
+                            context.setVariable("storeClockIn", arg);
+                        } else if (arg instanceof StorePrice) {
+                            context.setVariable("storePrice", arg);
+                        } else if (arg instanceof StoreCuisine) {
+                            context.setVariable("storeCuisine", arg);
+                        }
+                        
+                        // 如果参数名存在,也设置
+                        if (parameterNames != null && i < parameterNames.length) {
+                            context.setVariable(parameterNames[i], arg);
+                        }
+                    }
+                }
             }
         }
         
@@ -188,6 +244,11 @@ public class TrackEventAspect {
 
     /**
      * 解析SpEL表达式
+     * 支持多种格式:
+     * 1. #{#xxx} -> #xxx
+     * 2. #{xxx} -> xxx
+     * 3. #xxx -> #xxx
+     * 4. 嵌套属性访问:#{#obj.field} -> #obj.field
      */
     private <T> T parseSpEL(String expression, EvaluationContext context, Class<T> clazz) {
         if (StringUtils.isBlank(expression)) {
@@ -203,42 +264,71 @@ public class TrackEventAspect {
                 return null;
             }
 
-            // 处理 #{#xxx} 格式,转换为 #xxx
-            String normalizedExpression = expression;
-            if (expression.startsWith("#{#") && expression.endsWith("}")) {
-                normalizedExpression = expression.substring(2, expression.length() - 1);
-            }
+            // 标准化SpEL表达式
+            String normalizedExpression = normalizeSpELExpression(expression);
             
             // 解析SpEL表达式
             Object value = spelExpressionParser.parseExpression(normalizedExpression).getValue(context);
             if (value == null) {
+                log.debug("SpEL表达式解析结果为null: 表达式={}, 标准化后={}", expression, normalizedExpression);
                 return null;
             }
 
-            if (clazz.isInstance(value)) {
-                return clazz.cast(value);
-            }
-            
             // 类型转换
-            if (clazz == Integer.class) {
-                if (value instanceof Number) {
-                    return clazz.cast(((Number) value).intValue());
-                } else if (value instanceof String) {
-                    try {
-                        return clazz.cast(Integer.parseInt((String) value));
-                    } catch (NumberFormatException e) {
-                        log.debug("无法将字符串转换为Integer: {}", value);
-                        return null;
-                    }
-                }
-            }
-            
-            return null;
+            return convertValue(value, clazz, expression);
         } catch (Exception e) {
-            log.debug("解析SpEL表达式失败: {}", expression, e);
+            log.warn("解析SpEL表达式失败: 表达式={}, 错误={}", expression, e.getMessage());
             return null;
         }
     }
+    
+    /**
+     * 标准化SpEL表达式
+     */
+    private String normalizeSpELExpression(String expression) {
+        // 处理 #{#xxx} 格式,转换为 #xxx
+        if (expression.startsWith("#{#") && expression.endsWith("}")) {
+            return expression.substring(2, expression.length() - 1);
+        }
+        // 处理 #{xxx} 格式,转换为 xxx
+        if (expression.startsWith("#{") && expression.endsWith("}")) {
+            return expression.substring(2, expression.length() - 1);
+        }
+        // 如果已经是 #xxx 格式,直接返回
+        return expression;
+    }
+    
+    /**
+     * 类型转换
+     */
+    @SuppressWarnings("unchecked")
+    private <T> T convertValue(Object value, Class<T> clazz, String expression) {
+        if (value == null) {
+            return null;
+        }
+        
+        if (clazz.isInstance(value)) {
+            return (T) value;
+        }
+        
+        // 类型转换
+        if (clazz == Integer.class) {
+            if (value instanceof Number) {
+                return (T) Integer.valueOf(((Number) value).intValue());
+            } else if (value instanceof String) {
+                try {
+                    return (T) Integer.valueOf((String) value);
+                } catch (NumberFormatException e) {
+                    log.debug("无法将字符串转换为Integer: {}", value);
+                    return null;
+                }
+            }
+        } else if (clazz == String.class) {
+            return (T) value.toString();
+        }
+        
+        return null;
+    }
 
     /**
      * 从方法参数中提取storeId
@@ -268,26 +358,6 @@ public class TrackEventAspect {
     }
 
     /**
-     * 从JWT中获取用户ID
-     */
-    private Integer getUserIdFromJWT() {
-        try {
-            JSONObject userInfo = JwtUtil.getCurrentUserInfo();
-            if (userInfo != null && userInfo.get("userId") != null) {
-                Object userIdObj = userInfo.get("userId");
-                if (userIdObj instanceof Integer) {
-                    return (Integer) userIdObj;
-                } else if (userIdObj instanceof String) {
-                    return Integer.parseInt((String) userIdObj);
-                }
-            }
-        } catch (Exception e) {
-            log.debug("从JWT获取用户ID失败", e);
-        }
-        return null;
-    }
-
-    /**
      * 根据业务逻辑查询店铺ID
      */
     private Integer queryStoreIdByBusinessLogic(ProceedingJoinPoint joinPoint, TrackEvent annotation, EvaluationContext context) {
@@ -320,6 +390,16 @@ public class TrackEventAspect {
             if ("LifeUserDynamicsController".equals(className) && "addOrUpdate".equals(methodName)) {
                 return queryStoreIdForAddOrUpdate(context);
             }
+            
+            // 处理 /store/price/getById 接口(查询通用价目详情)
+            if ("StorePriceController".equals(className) && "getById".equals(methodName)) {
+                return queryStoreIdForGetPriceById(context);
+            }
+            
+            // 处理 /store/cuisine/getByCuisineType 接口(查询美食价目详情)
+            if ("StoreCuisineController".equals(className) && "getByCuisineType".equals(methodName)) {
+                return queryStoreIdForGetByCuisineType(context);
+            }
         } catch (Exception e) {
             log.debug("根据业务逻辑查询店铺ID失败", e);
         }
@@ -359,11 +439,6 @@ public class TrackEventAspect {
                     }
                 }
             }
-            // type=1 表示评论,需要查询评论表获取店铺ID
-            else if ("1".equals(type)) {
-                // 这里可以根据评论ID查询评论表,但需要知道评论表的结构
-                // 暂时返回null,后续可以根据实际表结构补充
-            }
         } catch (Exception e) {
             log.debug("查询点赞接口的店铺ID失败", e);
         }
@@ -451,11 +526,6 @@ public class TrackEventAspect {
                     }
                 }
             }
-            // reportContextType=3 表示评论
-            else if ("3".equals(reportContextType) && violation.getCommentId() != null) {
-                // 可以通过评论ID查询评论表,然后获取店铺ID
-                // 暂时返回null,后续可以根据实际表结构补充
-            }
             // reportContextType=0 表示商户
             else if ("0".equals(reportContextType) && violation.getBusinessId() != null) {
                 // businessId就是店铺ID
@@ -522,11 +592,120 @@ public class TrackEventAspect {
                     log.debug("blockerId无法转换为Integer: {}", blacklist.getBlockerId());
                 }
             }
+            
+            // 如果被拉黑方是商户(blockedType=1),通过blockedId查询店铺ID
+            if ("1".equals(blacklist.getBlockedType()) && blacklist.getBlockedId() != null) {
+                try {
+                    Integer blockedId = Integer.parseInt(blacklist.getBlockedId());
+                    StoreUser storeUser = storeUserMapper.selectById(blockedId);
+                    if (storeUser != null && storeUser.getStoreId() != null) {
+                        return storeUser.getStoreId();
+                    }
+                } catch (NumberFormatException e) {
+                    log.debug("blockedId无法转换为Integer: {}", blacklist.getBlockedId());
+                }
+            }
         } catch (Exception e) {
             log.debug("查询拉黑接口的店铺ID失败", e);
         }
         return null;
     }
+    
+    /**
+     * 为查询通用价目详情接口查询店铺ID
+     */
+    private Integer queryStoreIdForGetPriceById(EvaluationContext context) {
+        try {
+            // 获取id参数
+            Object idObj = context.lookupVariable("id");
+            if (idObj == null) {
+                return null;
+            }
+            
+            Integer id = null;
+            if (idObj instanceof Integer) {
+                id = (Integer) idObj;
+            } else if (idObj instanceof Number) {
+                id = ((Number) idObj).intValue();
+            } else if (idObj instanceof String) {
+                try {
+                    id = Integer.parseInt((String) idObj);
+                } catch (NumberFormatException e) {
+                    log.debug("无法将id转换为Integer: {}", idObj);
+                    return null;
+                }
+            }
+            
+            if (id != null) {
+                // 通过id查询StorePrice表获取storeId
+                StorePrice price = storePriceMapper.selectById(id);
+                if (price != null && price.getStoreId() != null) {
+                    return price.getStoreId();
+                }
+            }
+        } catch (Exception e) {
+            log.debug("查询通用价目详情接口的店铺ID失败", e);
+        }
+        return null;
+    }
+    
+    /**
+     * 为查询美食价目详情接口查询店铺ID
+     */
+    private Integer queryStoreIdForGetByCuisineType(EvaluationContext context) {
+        try {
+            // 获取id参数
+            Object idObj = context.lookupVariable("id");
+            if (idObj == null) {
+                return null;
+            }
+            
+            Integer id = null;
+            if (idObj instanceof Integer) {
+                id = (Integer) idObj;
+            } else if (idObj instanceof Number) {
+                id = ((Number) idObj).intValue();
+            } else if (idObj instanceof String) {
+                try {
+                    id = Integer.parseInt((String) idObj);
+                } catch (NumberFormatException e) {
+                    log.debug("无法将id转换为Integer: {}", idObj);
+                    return null;
+                }
+            }
+            
+            if (id != null) {
+                // 通过id查询StoreCuisine表获取storeId
+                StoreCuisine cuisine = storeCuisineMapper.selectById(id);
+                if (cuisine != null && cuisine.getStoreId() != null) {
+                    return cuisine.getStoreId();
+                }
+            }
+        } catch (Exception e) {
+            log.debug("查询美食价目详情接口的店铺ID失败", e);
+        }
+        return null;
+    }
+
+    /**
+     * 从JWT中获取用户ID
+     */
+    private Integer getUserIdFromJWT() {
+        try {
+            JSONObject userInfo = JwtUtil.getCurrentUserInfo();
+            if (userInfo != null && userInfo.get("userId") != null) {
+                Object userIdObj = userInfo.get("userId");
+                if (userIdObj instanceof Integer) {
+                    return (Integer) userIdObj;
+                } else if (userIdObj instanceof String) {
+                    return Integer.parseInt((String) userIdObj);
+                }
+            }
+        } catch (Exception e) {
+            log.debug("从JWT获取用户ID失败", e);
+        }
+        return null;
+    }
 
     /**
      * 获取客户端IP地址
@@ -554,4 +733,141 @@ public class TrackEventAspect {
         }
         return ip;
     }
+    
+    /**
+     * 处理搜索接口的埋点:为每个搜索结果中的店铺记录一条埋点
+     */
+    private void handleSearchEventTracking(ProceedingJoinPoint joinPoint, TrackEvent annotation, Object result) {
+        try {
+            // 获取返回结果
+            if (!(result instanceof shop.alien.entity.result.R)) {
+                log.debug("搜索接口返回结果不是R类型,跳过埋点");
+                return;
+            }
+            
+            @SuppressWarnings("unchecked")
+            shop.alien.entity.result.R<Object> r = (shop.alien.entity.result.R<Object>) result;
+            
+            if (r.getData() == null) {
+                log.debug("搜索接口返回结果data为空,跳过埋点");
+                return;
+            }
+            
+            // 处理 fastjson2 的 JSONObject
+            com.alibaba.fastjson2.JSONObject data;
+            if (r.getData() instanceof com.alibaba.fastjson2.JSONObject) {
+                data = (com.alibaba.fastjson2.JSONObject) r.getData();
+            } else {
+                // 如果不是 JSONObject,尝试转换
+                log.debug("搜索接口返回结果data不是JSONObject类型,跳过埋点");
+                return;
+            }
+            
+            // 提取所有店铺ID
+            java.util.Set<Integer> storeIds = new java.util.HashSet<>();
+            
+            // 从matchedRecords中提取店铺ID
+            JSONArray matchedRecords = data.getJSONArray("matchedRecords");
+            if (matchedRecords != null) {
+                for (int i = 0; i < matchedRecords.size(); i++) {
+                    com.alibaba.fastjson2.JSONObject store = matchedRecords.getJSONObject(i);
+                    if (store != null) {
+                        // id可能是字符串或数字,需要处理
+                        Object idObj = store.get("id");
+                        Integer storeId = parseStoreId(idObj);
+                        if (storeId != null) {
+                            storeIds.add(storeId);
+                        }
+                    }
+                }
+            }
+            
+            // 从relatedRecords中提取店铺ID
+            JSONArray relatedRecords = data.getJSONArray("relatedRecords");
+            if (relatedRecords != null) {
+                for (int i = 0; i < relatedRecords.size(); i++) {
+                    com.alibaba.fastjson2.JSONObject store = relatedRecords.getJSONObject(i);
+                    if (store != null) {
+                        // id可能是字符串或数字,需要处理
+                        Object idObj = store.get("id");
+                        Integer storeId = parseStoreId(idObj);
+                        if (storeId != null) {
+                            storeIds.add(storeId);
+                        }
+                    }
+                }
+            }
+            
+            // 为每个店铺创建一条埋点事件
+            if (storeIds.isEmpty()) {
+                log.debug("搜索接口未找到店铺ID,跳过埋点");
+                return;
+            }
+            
+            // 获取基础信息(用户ID、IP、User-Agent等)
+            EvaluationContext context = createEvaluationContext(joinPoint, result);
+            Integer userId = parseSpEL(annotation.userId(), context, Integer.class);
+            if (userId == null && StringUtils.isBlank(annotation.userId())) {
+                userId = getUserIdFromJWT();
+            }
+            
+            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+            String ipAddress = null;
+            String userAgent = null;
+            if (attributes != null) {
+                HttpServletRequest request = attributes.getRequest();
+                ipAddress = getIpAddress(request);
+                userAgent = request.getHeader("User-Agent");
+            }
+            
+            // 为每个店铺创建埋点事件
+            for (Integer storeId : storeIds) {
+                StoreTrackEvent trackEvent = new StoreTrackEvent();
+                trackEvent.setEventType(annotation.eventType());
+                trackEvent.setEventCategory(annotation.eventCategory());
+                trackEvent.setTargetType(StringUtils.isNotBlank(annotation.targetType()) ? annotation.targetType() : null);
+                trackEvent.setEventTime(new Date());
+                trackEvent.setStoreId(storeId);
+                trackEvent.setUserId(userId);
+                trackEvent.setTargetId(storeId); // 搜索的店铺ID作为targetId
+                trackEvent.setIpAddress(ipAddress);
+                trackEvent.setUserAgent(userAgent);
+                trackEvent.setDeviceType(UserAgentParserUtil.parseDeviceType(userAgent));
+                
+                // 异步写入Redis List
+                if (annotation.async()) {
+                    String eventJson = objectMapper.writeValueAsString(trackEvent);
+                    baseRedisService.setListRight(REDIS_QUEUE_KEY, eventJson);
+                }
+            }
+            
+            log.debug("搜索接口埋点完成,共记录{}个店铺的埋点数据", storeIds.size());
+        } catch (Exception e) {
+            log.error("处理搜索接口埋点失败", e);
+        }
+    }
+    
+    /**
+     * 解析店铺ID(支持字符串和数字类型)
+     */
+    private Integer parseStoreId(Object idObj) {
+        if (idObj == null) {
+            return null;
+        }
+        if (idObj instanceof Integer) {
+            return (Integer) idObj;
+        }
+        if (idObj instanceof Number) {
+            return ((Number) idObj).intValue();
+        }
+        if (idObj instanceof String) {
+            try {
+                return Integer.parseInt((String) idObj);
+            } catch (NumberFormatException e) {
+                log.debug("无法将字符串转换为店铺ID: {}", idObj);
+                return null;
+            }
+        }
+        return null;
+    }
 }

+ 1 - 1
alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java

@@ -62,7 +62,7 @@ public class AiSearchController {
     @TrackEvent(
             eventType = "SEARCH",
             eventCategory = "TRAFFIC",
-            storeId = "#{#storeId}",
+            storeId = "",
             targetType = "STORE"
     )
     @RequestMapping("/search")

+ 1 - 6
alien-store/src/main/java/shop/alien/store/controller/CommonCommentController.java

@@ -58,11 +58,6 @@ public class CommonCommentController {
      //   "merchantId": 10086, 为2商户评论需要填
      }
      */
-    /**
-     * 新增评论
-     * @param commonComment 评论对象
-     * @return 0:成功, 1:失败, 2:文本内容异常, 4:字数超限(超过300字)
-     */
     @TrackEvent(
             eventType = "POST_COMMENT",
             eventCategory = "INTERACTION",
@@ -71,7 +66,7 @@ public class CommonCommentController {
             targetId = "#commonComment.sourceId",
             targetType = "POST"
     )
-    @ApiOperation(value = "新增评论", notes = "0:成功, 1:失败, 2:文本内容异常, 4:字数超限(超过300字)")
+    @ApiOperation(value = "新增评论", notes = "0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常")
     @PostMapping("/addComment")
     public R addComment(@RequestBody CommonComment commonComment) {
         Integer addComment = commonCommentService.addComment(commonComment);

+ 7 - 6
alien-store/src/main/java/shop/alien/store/controller/StoreCuisineController.java

@@ -73,6 +73,13 @@ public class StoreCuisineController {
         return R.data(storeCuisineService.getSingleName());
     }
 
+    @TrackEvent(
+            eventType = "PRICE_VIEW",
+            eventCategory = "PRICE",
+            storeId = "#{result.data.data.storeId}",
+            targetId = "#{#id}",
+            targetType = "PRICE"
+    )
     @ApiOperation("根据id与类型获取美食单品或套餐详情")
     @ApiOperationSupport(order = 4)
     @ApiImplicitParams({
@@ -126,12 +133,6 @@ public class StoreCuisineController {
         return R.fail("操作失败");
     }
 
-    @TrackEvent(
-            eventType = "PRICE_VIEW",
-            eventCategory = "PRICE",
-            storeId = "#{#storeId}",
-            targetType = "PRICE"
-    )
     @ApiOperation("分页查询美食价目/通用价目")
     @ApiOperationSupport(order = 7)
     @ApiImplicitParams({

+ 0 - 6
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -1028,12 +1028,6 @@ public class StoreInfoController {
         return R.data(ocrData);
     }
 
-    @TrackEvent(
-            eventType = "VIEW",
-            eventCategory = "TRAFFIC",
-            storeId = "#{#id}",
-            targetType = "STORE"
-    )
     @ApiOperation(value = "获取店铺详情(用户端)")
     @ApiOperationSupport(order = 17)
     @GetMapping("/getClientStoreDetail")

+ 225 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreOperationalStatisticsController.java

@@ -0,0 +1,225 @@
+package shop.alien.store.controller;
+
+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.store.service.StoreOperationalStatisticsService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 商家经营数据统计Controller
+ *
+ * @author system
+ * @version 1.0
+ * @date 2026/01/05
+ */
+@Slf4j
+@Api(tags = {"商家经营数据统计"})
+@CrossOrigin
+@RestController
+@RequestMapping("/store/operational/statistics")
+@RequiredArgsConstructor
+public class StoreOperationalStatisticsController {
+
+    private final StoreOperationalStatisticsService storeOperationalStatisticsService;
+
+    @ApiOperation("获取商家经营统计数据")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "storeId",
+                    value = "店铺ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "startTime",
+                    value = "开始时间(格式:yyyy-MM-dd)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "endTime",
+                    value = "结束时间(格式:yyyy-MM-dd)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true
+            )
+    })
+    @GetMapping("/getStatistics")
+    public R<StoreOperationalStatisticsVo> getStatistics(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("startTime") String startTime,
+            @RequestParam("endTime") String endTime) {
+        log.info("StoreOperationalStatisticsController.getStatistics - storeId={}, startTime={}, endTime={}", storeId, startTime, endTime);
+        StoreOperationalStatisticsVo statistics = storeOperationalStatisticsService.getStatistics(storeId, startTime, endTime);
+        return R.data(statistics);
+    }
+
+    @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("/getStatisticsComparison")
+    public R<StoreOperationalStatisticsComparisonVo> getStatisticsComparison(
+            @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 = storeOperationalStatisticsService.getStatisticsComparison(
+                storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
+        return R.data(comparison);
+    }
+
+    @ApiOperation("查询历史统计记录列表")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "storeId",
+                    value = "店铺ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            )
+    })
+    @GetMapping("/history/list")
+    public R<List<StoreOperationalStatisticsHistory>> getHistoryList(
+            @RequestParam("storeId") Integer storeId) {
+        log.info("StoreOperationalStatisticsController.getHistoryList - storeId={}", storeId);
+        try {
+            List<StoreOperationalStatisticsHistory> historyList = storeOperationalStatisticsService.getHistoryList(storeId);
+            return R.data(historyList);
+        } catch (Exception e) {
+            log.error("查询历史统计记录列表失败 - storeId={}, error={}", storeId, e.getMessage(), e);
+            return R.fail("查询失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("删除历史统计记录")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "id",
+                    value = "历史记录ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            )
+    })
+    @DeleteMapping("/history/delete")
+    public R<String> deleteHistory(@RequestParam("id") Integer id) {
+        log.info("StoreOperationalStatisticsController.deleteHistory - id={}", id);
+        try {
+            if (id == null || id <= 0) {
+                return R.fail("ID不能为空且必须大于0");
+            }
+            
+            boolean result = storeOperationalStatisticsService.deleteHistory(id);
+            if (result) {
+                return R.success("删除成功");
+            } else {
+                return R.fail("删除失败,记录不存在或已被删除");
+            }
+        } catch (Exception e) {
+            log.error("删除历史统计记录失败 - id={}, error={}", id, 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/batchDelete")
+    public R<String> batchDeleteHistory(@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 = storeOperationalStatisticsService.batchDeleteHistory(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());
+        }
+    }
+}

+ 7 - 6
alien-store/src/main/java/shop/alien/store/controller/StorePriceController.java

@@ -136,6 +136,13 @@ public class StorePriceController {
         return R.fail("修改失败,请检查数据是否正确");
     }
 
+    @TrackEvent(
+            eventType = "PRICE_VIEW",
+            eventCategory = "PRICE",
+            storeId = "#{result.data.storeId}",
+            targetId = "#{#id}",
+            targetType = "PRICE"
+    )
     @ApiOperation("根据ID查询通用价目")
     @ApiOperationSupport(order = 3)
     @ApiImplicitParams({
@@ -184,12 +191,6 @@ public class StorePriceController {
         return R.fail("批量删除失败");
     }
 
-    @TrackEvent(
-            eventType = "PRICE_VIEW",
-            eventCategory = "PRICE",
-            storeId = "#{#storeId}",
-            targetType = "PRICE"
-    )
     @ApiOperation("分页查询通用价目")
     @ApiOperationSupport(order = 6)
     @ApiImplicitParams({

+ 0 - 6
alien-store/src/main/java/shop/alien/store/controller/UserStoreController.java

@@ -96,12 +96,6 @@ public class UserStoreController {
         return R.data(ListToPage.setPage(result, page, size));
     }
 
-    @TrackEvent(
-            eventType = "VIEW",
-            eventCategory = "TRAFFIC",
-            storeId = "#{#storeId}",
-            targetType = "STORE"
-    )
     @ApiOperation("查询商铺详情")
     @ApiOperationSupport(order = 2)
     @ApiImplicitParams({@ApiImplicitParam(name = "storeId", value = "商铺id", dataType = "String", paramType = "query"),

+ 64 - 0
alien-store/src/main/java/shop/alien/store/service/StoreOperationalStatisticsService.java

@@ -0,0 +1,64 @@
+package shop.alien.store.service;
+
+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 StoreOperationalStatisticsService {
+
+    /**
+     * 获取商家经营统计数据
+     *
+     * @param storeId   店铺ID
+     * @param startTime 开始时间(格式:yyyy-MM-dd)
+     * @param endTime   结束时间(格式:yyyy-MM-dd)
+     * @return 经营统计数据
+     */
+    StoreOperationalStatisticsVo getStatistics(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 getStatisticsComparison(Integer storeId, String currentStartTime, String currentEndTime,
+                                                                    String previousStartTime, String previousEndTime);
+
+    /**
+     * 查询历史统计记录列表
+     *
+     * @param storeId 店铺ID
+     * @return 历史统计记录列表
+     */
+    List<StoreOperationalStatisticsHistory> getHistoryList(Integer storeId);
+
+    /**
+     * 删除历史统计记录(逻辑删除)
+     *
+     * @param id 历史记录ID
+     * @return 是否成功
+     */
+    boolean deleteHistory(Integer id);
+
+    /**
+     * 批量删除历史统计记录(逻辑删除)
+     *
+     * @param ids 历史记录ID列表
+     * @return 是否成功
+     */
+    boolean batchDeleteHistory(List<Integer> ids);
+}

+ 663 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalStatisticsServiceImpl.java

@@ -0,0 +1,663 @@
+package shop.alien.store.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.*;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsVo;
+import shop.alien.mapper.*;
+import shop.alien.store.service.StoreOperationalStatisticsService;
+
+import 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
+public class StoreOperationalStatisticsServiceImpl implements StoreOperationalStatisticsService {
+
+    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 StoreUserMapper storeUserMapper;
+    private final StoreOperationalStatisticsHistoryMapper statisticsHistoryMapper;
+
+    private static final String DATE_FORMAT = "yyyy-MM-dd";
+
+    @Override
+    public StoreOperationalStatisticsVo getStatistics(Integer storeId, String startTime, String endTime) {
+        log.info("StoreOperationalStatisticsServiceImpl.getStatistics - storeId={}, startTime={}, endTime={}", storeId, startTime, endTime);
+
+        StoreOperationalStatisticsVo statistics = new StoreOperationalStatisticsVo();
+
+        try {
+            // 解析时间范围
+            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
+            Date startDate = sdf.parse(startTime);
+            Date endDate = sdf.parse(endTime);
+            // 设置为当天的结束时间
+            Calendar endCal = Calendar.getInstance();
+            endCal.setTime(endDate);
+            endCal.set(Calendar.HOUR_OF_DAY, 23);
+            endCal.set(Calendar.MINUTE, 59);
+            endCal.set(Calendar.SECOND, 59);
+            endDate = endCal.getTime();
+
+            // 1. 流量数据统计
+            statistics.setTrafficData(getTrafficData(storeId, startDate, endDate));
+
+            // 2. 互动数据统计
+            statistics.setInteractionData(getInteractionData(storeId, startDate, endDate));
+
+            // 3. 优惠券数据统计
+            statistics.setCouponData(getCouponData(storeId, startDate, endDate));
+
+            // 4. 代金券数据统计(与优惠券类似,根据实际业务区分)
+            statistics.setVoucherData(getVoucherData(storeId, startDate, endDate));
+
+            // 5. 服务质量数据统计
+            statistics.setServiceQualityData(getServiceQualityData(storeId, startDate, endDate));
+
+            // 6. 价目表排名统计
+            statistics.setPriceListRanking(getPriceListRanking(storeId, startDate, endDate));
+
+            // 保存统计数据到历史表
+            saveStatisticsHistory(storeId, startTime, endTime, statistics);
+
+        } catch (ParseException e) {
+            log.error("StoreOperationalStatisticsServiceImpl.getStatistics - 时间解析错误", e);
+            throw new RuntimeException("时间格式错误: " + e.getMessage());
+        }
+
+        return statistics;
+    }
+
+    @Override
+    public StoreOperationalStatisticsComparisonVo getStatisticsComparison(Integer storeId, String currentStartTime, String currentEndTime,
+                                                                          String previousStartTime, String previousEndTime) {
+        log.info("StoreOperationalStatisticsServiceImpl.getStatisticsComparison - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}",
+                storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
+
+        StoreOperationalStatisticsComparisonVo comparison = new StoreOperationalStatisticsComparisonVo();
+        comparison.setCurrentStartTime(currentStartTime);
+        comparison.setCurrentEndTime(currentEndTime);
+        comparison.setPreviousStartTime(previousStartTime);
+        comparison.setPreviousEndTime(previousEndTime);
+
+        // 获取当期和上期的统计数据(getStatistics方法内部已经保存了历史记录)
+        StoreOperationalStatisticsVo currentStatistics = getStatistics(storeId, currentStartTime, currentEndTime);
+        StoreOperationalStatisticsVo previousStatistics = getStatistics(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()));
+
+        return comparison;
+    }
+
+    /**
+     * 保存统计数据到历史表
+     */
+    private void saveStatisticsHistory(Integer storeId, String startTime, String endTime, StoreOperationalStatisticsVo statistics) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
+            Date startDate = sdf.parse(startTime);
+            Date endDate = sdf.parse(endTime);
+
+            StoreOperationalStatisticsHistory history = new StoreOperationalStatisticsHistory();
+            history.setStoreId(storeId);
+            history.setStartTime(startDate);
+            history.setEndTime(endDate);
+            history.setQueryTime(new Date());
+            
+            // 将统计数据序列化为JSON字符串
+            String statisticsJson = JSON.toJSONString(statistics);
+            history.setStatisticsData(statisticsJson);
+
+            statisticsHistoryMapper.insert(history);
+            log.info("保存统计数据到历史表成功 - storeId={}, startTime={}, endTime={}", storeId, startTime, endTime);
+        } catch (Exception e) {
+            log.error("保存统计数据到历史表失败 - storeId={}, startTime={}, endTime={}, error={}", 
+                    storeId, startTime, endTime, e.getMessage(), e);
+            // 保存失败不影响主流程,只记录日志
+        }
+    }
+
+    /**
+     * 获取流量数据
+     */
+    private StoreOperationalStatisticsVo.TrafficData getTrafficData(Integer storeId, Date startDate, Date endDate) {
+        StoreOperationalStatisticsVo.TrafficData trafficData = new StoreOperationalStatisticsVo.TrafficData();
+
+        // TODO: 实现店铺搜索量统计(需要根据实际业务逻辑查询搜索记录表)
+        trafficData.setStoreSearchVolume(0L);
+
+        // 浏览记录统计
+        LambdaQueryWrapper<LifeBrowseRecord> browseWrapper = new LambdaQueryWrapper<>();
+        browseWrapper.eq(LifeBrowseRecord::getStoreId, String.valueOf(storeId))
+                .between(LifeBrowseRecord::getCreatedTime, startDate, endDate)
+                .eq(LifeBrowseRecord::getDeleteFlag, 0);
+        List<LifeBrowseRecord> browseRecords = lifeBrowseRecordMapper.selectList(browseWrapper);
+
+        // 浏览量
+        trafficData.setPageViews((long) browseRecords.size());
+
+        // 访客数(去重用户ID)
+        long visitors = browseRecords.stream()
+                .map(LifeBrowseRecord::getUserId)
+                .distinct()
+                .count();
+        trafficData.setVisitors(visitors);
+
+        // TODO: 新增访客数统计(需要对比历史数据判断是否为新访客)
+        trafficData.setNewVisitors(0L);
+
+        // TODO: 访问时长统计(需要根据实际业务逻辑计算)
+        trafficData.setVisitDuration(0L);
+
+        // TODO: 平均访问时长统计
+        trafficData.setAvgVisitDuration(0L);
+
+        return trafficData;
+    }
+
+    /**
+     * 获取互动数据
+     */
+    private StoreOperationalStatisticsVo.InteractionData getInteractionData(Integer storeId, Date startDate, Date endDate) {
+        StoreOperationalStatisticsVo.InteractionData interactionData = new StoreOperationalStatisticsVo.InteractionData();
+
+        // 收藏次数
+        LambdaQueryWrapper<LifeCollect> collectWrapper = new LambdaQueryWrapper<>();
+        collectWrapper.eq(LifeCollect::getStoreId, String.valueOf(storeId))
+                .between(LifeCollect::getCreatedTime, startDate, endDate)
+                .eq(LifeCollect::getDeleteFlag, 0);
+        long collectionCount = lifeCollectMapper.selectCount(collectWrapper);
+        interactionData.setStoreCollectionCount(collectionCount);
+
+        // TODO: 分享次数统计(需要根据实际业务逻辑查询分享记录表)
+        interactionData.setStoreShareCount(0L);
+
+        // 打卡次数
+        LambdaQueryWrapper<StoreClockIn> clockInWrapper = new LambdaQueryWrapper<>();
+        clockInWrapper.eq(StoreClockIn::getStoreId, storeId)
+                .between(StoreClockIn::getCreatedTime, startDate, endDate);
+        long checkInCount = storeClockInMapper.selectCount(clockInWrapper);
+        interactionData.setStoreCheckInCount(checkInCount);
+
+        // TODO: 咨询商家次数统计(需要根据实际业务逻辑查询咨询记录表)
+        interactionData.setConsultMerchantCount(0L);
+
+        // 好友数量(店铺好友关系)
+        // TODO: 需要根据实际业务逻辑查询好友关系表
+        interactionData.setFriendsCount(0L);
+
+        // 关注数量
+        // TODO: 需要根据实际业务逻辑查询关注关系(需要确认店铺的关注关系表结构)
+        // LambdaQueryWrapper<LifeFans> fansWrapper = new LambdaQueryWrapper<>();
+        interactionData.setFollowCount(0L);
+
+        // 粉丝数量
+        // TODO: 需要根据实际业务逻辑查询粉丝关系
+        interactionData.setFansCount(0L);
+
+        // 动态相关统计(店铺发布的动态)
+        StoreUser storeUser = storeUserMapper.selectOne(
+                new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, storeId).last("LIMIT 1"));
+        if (storeUser != null) {
+            String phoneId = "store_" + storeUser.getPhone();
+
+            // 发布动态数量
+            LambdaQueryWrapper<LifeUserDynamics> dynamicsWrapper = new LambdaQueryWrapper<>();
+            dynamicsWrapper.eq(LifeUserDynamics::getPhoneId, phoneId)
+                    .between(LifeUserDynamics::getCreatedTime, startDate, endDate)
+                    .eq(LifeUserDynamics::getDeleteFlag, 0)
+                    .eq(LifeUserDynamics::getDraft, 0);
+            long postsCount = lifeUserDynamicsMapper.selectCount(dynamicsWrapper);
+            interactionData.setPostsPublishedCount(postsCount);
+
+            // 动态点赞数量
+            List<LifeUserDynamics> dynamicsList = lifeUserDynamicsMapper.selectList(dynamicsWrapper);
+            long likesCount = dynamicsList.stream()
+                    .mapToLong(d -> d.getDianzanCount() != null ? d.getDianzanCount() : 0L)
+                    .sum();
+            interactionData.setPostLikesCount(likesCount);
+
+            // 动态评论数量
+            long commentsCount = 0L;
+            for (LifeUserDynamics dynamics : dynamicsList) {
+                LambdaQueryWrapper<StoreComment> commentWrapper = new LambdaQueryWrapper<>();
+                commentWrapper.eq(StoreComment::getBusinessId, String.valueOf(dynamics.getId()))
+                        .eq(StoreComment::getBusinessType, 2)
+                        .eq(StoreComment::getDeleteFlag, 0);
+                commentsCount += storeCommentMapper.selectCount(commentWrapper);
+            }
+            interactionData.setPostCommentsCount(commentsCount);
+
+            // 动态转发数量
+            long sharesCount = dynamicsList.stream()
+                    .mapToLong(d -> d.getTransferCount() != null ? d.getTransferCount() : 0L)
+                    .sum();
+            interactionData.setPostSharesCount(sharesCount);
+        } else {
+            interactionData.setPostsPublishedCount(0L);
+            interactionData.setPostLikesCount(0L);
+            interactionData.setPostCommentsCount(0L);
+            interactionData.setPostSharesCount(0L);
+        }
+
+        // TODO: 被举报次数统计
+        interactionData.setReportedCount(0L);
+
+        // 被拉黑次数
+        // TODO: 需要根据实际业务逻辑查询拉黑记录
+        interactionData.setBlockedCount(0L);
+
+        return interactionData;
+    }
+
+    /**
+     * 获取优惠券数据
+     */
+    private StoreOperationalStatisticsVo.CouponData getCouponData(Integer storeId, Date startDate, Date endDate) {
+        StoreOperationalStatisticsVo.CouponData couponData = new StoreOperationalStatisticsVo.CouponData();
+
+        // TODO: 实现优惠券相关统计
+        // 1. 赠送好友数量
+        // 2. 赠送好友金额合计
+        // 3. 赠送好友使用数量
+        // 4. 赠送好友使用金额合计
+        // 5. 好友赠送数量
+        // 6. 好友赠送金额合计
+        // 7. 好友赠送使用数量
+        // 8. 好友赠送使用金额合计
+
+        // TODO: 根据实际业务逻辑查询 life_discount_coupon_store_friend 表
+        // 注意:LifeDiscountCouponStoreFriend 没有 storeId 字段,需要通过 storeUserId 关联查询
+        // LambdaQueryWrapper<LifeDiscountCouponStoreFriend> friendCouponWrapper = new LambdaQueryWrapper<>();
+        // friendCouponWrapper.eq(LifeDiscountCouponStoreFriend::getStoreUserId, storeUserId)
+        //         .between(LifeDiscountCouponStoreFriend::getCreatedTime, startDate, endDate);
+        // TODO: 需要根据实际业务逻辑完善统计
+
+        couponData.setGiftToFriendsCount(0L);
+        couponData.setGiftToFriendsAmount(BigDecimal.ZERO);
+        couponData.setGiftToFriendsUsedCount(0L);
+        couponData.setGiftToFriendsUsedAmount(BigDecimal.ZERO);
+        couponData.setGiftToFriendsUsedAmountRatio(BigDecimal.ZERO);
+        couponData.setFriendsGiftCount(0L);
+        couponData.setFriendsGiftAmount(BigDecimal.ZERO);
+        couponData.setFriendsGiftUsedCount(0L);
+        couponData.setFriendsGiftUsedAmount(BigDecimal.ZERO);
+        couponData.setFriendsGiftUsedAmountRatio(BigDecimal.ZERO);
+
+        return couponData;
+    }
+
+    /**
+     * 获取代金券数据(与优惠券类似,根据实际业务区分)
+     */
+    private StoreOperationalStatisticsVo.VoucherData getVoucherData(Integer storeId, Date startDate, Date endDate) {
+        StoreOperationalStatisticsVo.VoucherData voucherData = new StoreOperationalStatisticsVo.VoucherData();
+
+        // TODO: 实现代金券相关统计(与优惠券逻辑类似)
+        voucherData.setGiftToFriendsCount(0L);
+        voucherData.setGiftToFriendsAmount(BigDecimal.ZERO);
+        voucherData.setGiftToFriendsUsedCount(0L);
+        voucherData.setGiftToFriendsUsedAmount(BigDecimal.ZERO);
+        voucherData.setGiftToFriendsUsedAmountRatio(BigDecimal.ZERO);
+        voucherData.setFriendsGiftCount(0L);
+        voucherData.setFriendsGiftAmount(BigDecimal.ZERO);
+        voucherData.setFriendsGiftUsedCount(0L);
+        voucherData.setFriendsGiftUsedAmount(BigDecimal.ZERO);
+        voucherData.setFriendsGiftUsedAmountRatio(BigDecimal.ZERO);
+
+        return voucherData;
+    }
+
+    /**
+     * 获取服务质量数据
+     */
+    private StoreOperationalStatisticsVo.ServiceQualityData getServiceQualityData(Integer storeId, Date startDate, Date endDate) {
+        StoreOperationalStatisticsVo.ServiceQualityData serviceQualityData = new StoreOperationalStatisticsVo.ServiceQualityData();
+
+        // 查询评价数据(StoreEvaluation)
+        LambdaQueryWrapper<StoreEvaluation> evaluationWrapper = new LambdaQueryWrapper<>();
+        evaluationWrapper.eq(StoreEvaluation::getStoreId, storeId)
+                .between(StoreEvaluation::getCreatedTime, startDate, endDate);
+        List<StoreEvaluation> evaluations = storeEvaluationMapper.selectList(evaluationWrapper);
+
+        if (!evaluations.isEmpty()) {
+            // 计算平均评分
+            double avgScore = evaluations.stream()
+                    .mapToDouble(e -> e.getScore() != null ? e.getScore().doubleValue() : 0.0)
+                    .average()
+                    .orElse(0.0);
+            serviceQualityData.setStoreRating(BigDecimal.valueOf(avgScore).setScale(1, RoundingMode.HALF_UP));
+
+            // TODO: 口味评分、环境评分、服务评分需要根据评价详情表查询
+            serviceQualityData.setTasteRating(BigDecimal.ZERO);
+            serviceQualityData.setEnvironmentRating(BigDecimal.ZERO);
+            serviceQualityData.setServiceRating(BigDecimal.ZERO);
+
+            // 评价数量
+            serviceQualityData.setTotalReviews((long) evaluations.size());
+
+            // 好评、中评、差评统计(StoreEvaluation.score 是 Double 类型)
+            long positiveCount = evaluations.stream()
+                    .filter(e -> e.getScore() != null && e.getScore() >= 4.5)
+                    .count();
+            long neutralCount = evaluations.stream()
+                    .filter(e -> e.getScore() != null && e.getScore() >= 3.5 && e.getScore() < 4.5)
+                    .count();
+            long negativeCount = evaluations.stream()
+                    .filter(e -> e.getScore() != null && e.getScore() < 3.5)
+                    .count();
+
+            serviceQualityData.setPositiveReviews(positiveCount);
+            serviceQualityData.setNeutralReviews(neutralCount);
+            serviceQualityData.setNegativeReviews(negativeCount);
+
+            // 差评占比
+            if (evaluations.size() > 0) {
+                BigDecimal ratio = BigDecimal.valueOf(negativeCount)
+                        .divide(BigDecimal.valueOf(evaluations.size()), 4, RoundingMode.HALF_UP)
+                        .multiply(BigDecimal.valueOf(100));
+                serviceQualityData.setNegativeReviewRatio(ratio.setScale(2, RoundingMode.HALF_UP));
+            } else {
+                serviceQualityData.setNegativeReviewRatio(BigDecimal.ZERO);
+            }
+
+            // 差评申诉相关统计
+            // TODO: 需要查询差评申诉表 StoreCommentAppeal
+            serviceQualityData.setNegativeReviewAppealsCount(0L);
+            serviceQualityData.setNegativeReviewAppealsSuccessCount(0L);
+            serviceQualityData.setNegativeReviewAppealsSuccessRatio(BigDecimal.ZERO);
+        } else {
+            serviceQualityData.setStoreRating(BigDecimal.ZERO);
+            serviceQualityData.setTasteRating(BigDecimal.ZERO);
+            serviceQualityData.setEnvironmentRating(BigDecimal.ZERO);
+            serviceQualityData.setServiceRating(BigDecimal.ZERO);
+            serviceQualityData.setTotalReviews(0L);
+            serviceQualityData.setPositiveReviews(0L);
+            serviceQualityData.setNeutralReviews(0L);
+            serviceQualityData.setNegativeReviews(0L);
+            serviceQualityData.setNegativeReviewRatio(BigDecimal.ZERO);
+            serviceQualityData.setNegativeReviewAppealsCount(0L);
+            serviceQualityData.setNegativeReviewAppealsSuccessCount(0L);
+            serviceQualityData.setNegativeReviewAppealsSuccessRatio(BigDecimal.ZERO);
+        }
+
+        return serviceQualityData;
+    }
+
+    /**
+     * 获取价目表排名数据
+     */
+    private List<StoreOperationalStatisticsVo.PriceListRanking> getPriceListRanking(Integer storeId, Date startDate, Date endDate) {
+        List<StoreOperationalStatisticsVo.PriceListRanking> rankings = new ArrayList<>();
+
+        // TODO: 实现价目表排名统计
+        // 需要查询价目表(StorePrice)的浏览量、访客数、分享数等
+
+        LambdaQueryWrapper<StorePrice> priceWrapper = new LambdaQueryWrapper<>();
+        priceWrapper.eq(StorePrice::getStoreId, storeId);
+        List<StorePrice> prices = storePriceMapper.selectList(priceWrapper);
+
+        // TODO: 需要根据浏览记录统计每个价目表的浏览量和访客数
+        // 示例逻辑(需要根据实际业务完善)
+        int rank = 1;
+        for (StorePrice price : prices) {
+            StoreOperationalStatisticsVo.PriceListRanking ranking = new StoreOperationalStatisticsVo.PriceListRanking();
+            ranking.setRank(rank++);
+            ranking.setPriceListItemName(price.getName());
+            // TODO: 统计该价目表的浏览量、访客数、分享数
+            ranking.setPageViews(0L);
+            ranking.setVisitors(0L);
+            ranking.setShares(0L);
+            rankings.add(ranking);
+        }
+
+        return rankings;
+    }
+
+    /**
+     * 构建流量数据对比
+     */
+    private StoreOperationalStatisticsComparisonVo.TrafficDataComparison buildTrafficDataComparison(
+            StoreOperationalStatisticsVo.TrafficData current, StoreOperationalStatisticsVo.TrafficData previous) {
+        StoreOperationalStatisticsComparisonVo.TrafficDataComparison comparison = new StoreOperationalStatisticsComparisonVo.TrafficDataComparison();
+        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();
+        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();
+        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();
+        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();
+        comparison.setStoreRating(buildComparisonData(current.getStoreRating(), previous.getStoreRating()));
+        comparison.setTasteRating(buildComparisonData(current.getTasteRating(), previous.getTasteRating()));
+        comparison.setEnvironmentRating(buildComparisonData(current.getEnvironmentRating(), previous.getEnvironmentRating()));
+        comparison.setServiceRating(buildComparisonData(current.getServiceRating(), previous.getServiceRating()));
+        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 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;
+        }
+    }
+
+    @Override
+    public List<StoreOperationalStatisticsHistory> getHistoryList(Integer storeId) {
+        log.info("StoreOperationalStatisticsServiceImpl.getHistoryList - storeId={}", storeId);
+        
+        LambdaQueryWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreOperationalStatisticsHistory::getStoreId, storeId)
+               .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0)
+               .orderByDesc(StoreOperationalStatisticsHistory::getQueryTime);
+        
+        return statisticsHistoryMapper.selectList(wrapper);
+    }
+
+    @Override
+    public boolean deleteHistory(Integer id) {
+        log.info("StoreOperationalStatisticsServiceImpl.deleteHistory - id={}", id);
+        
+        try {
+            // 使用逻辑删除
+            LambdaUpdateWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaUpdateWrapper<>();
+            wrapper.eq(StoreOperationalStatisticsHistory::getId, id)
+                   .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0)
+                   .set(StoreOperationalStatisticsHistory::getDeleteFlag, 1);
+            
+            int result = statisticsHistoryMapper.update(null, wrapper);
+            return result > 0;
+        } catch (Exception e) {
+            log.error("删除历史统计记录失败 - id={}, error={}", id, e.getMessage(), e);
+            return false;
+        }
+    }
+
+    @Override
+    public boolean batchDeleteHistory(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;
+        }
+    }
+}

+ 838 - 203
alien-store/src/main/java/shop/alien/store/service/impl/TrackEventServiceImpl.java

@@ -1,5 +1,6 @@
 package shop.alien.store.service.impl;
 
+import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -7,16 +8,17 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.*;
 import shop.alien.entity.store.StoreTrackEvent;
 import shop.alien.entity.store.StoreTrackStatistics;
-import shop.alien.mapper.StoreTrackEventMapper;
-import shop.alien.mapper.StoreTrackStatisticsMapper;
+import shop.alien.mapper.*;
 import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.service.TrackEventService;
 
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 埋点事件服务实现类
@@ -34,6 +36,16 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     private final StoreTrackStatisticsMapper trackStatisticsMapper;
     private final ObjectMapper objectMapper;
     
+    // 新增依赖:用于查询其他表数据
+    private final StoreUserMapper storeUserMapper;
+    private final LifeFansMapper lifeFansMapper;
+    private final LifeUserDynamicsMapper lifeUserDynamicsMapper;
+    private final CommonRatingMapper commonRatingMapper;
+    private final StoreCommentAppealMapper storeCommentAppealMapper;
+    private final LifeDiscountCouponUserMapper lifeDiscountCouponUserMapper;
+    private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
+    private final LifeDiscountCouponStoreFriendMapper lifeDiscountCouponStoreFriendMapper;
+    
     private static final String REDIS_QUEUE_KEY = "track:event:queue";
 
     @Override
@@ -68,28 +80,60 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
 
         try {
             // 计算统计日期范围
-            Date startDate = statDate;
-            Date endDate = statDate;
+            java.util.Calendar cal = java.util.Calendar.getInstance();
+            Date startDate;
+            Date endDate;
             
             // 根据统计类型确定日期范围
-            if ("WEEKLY".equals(statType)) {
-                // 周统计:从周一(statDate)到周日
-                java.util.Calendar cal = java.util.Calendar.getInstance();
+            if ("DAILY".equals(statType)) {
+                // 日统计:从当天 00:00:00 到 23:59:59
+                cal.setTime(statDate);
+                cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
+                cal.set(java.util.Calendar.MINUTE, 0);
+                cal.set(java.util.Calendar.SECOND, 0);
+                cal.set(java.util.Calendar.MILLISECOND, 0);
+                startDate = cal.getTime();
+                
+                cal.add(java.util.Calendar.DAY_OF_MONTH, 1);
+                endDate = cal.getTime(); // 下一天的 00:00:00,使用 .lt() 查询
+            } else if ("WEEKLY".equals(statType)) {
+                // 周统计:从周一(statDate)00:00:00 到周日 23:59:59
                 cal.setTime(statDate);
                 cal.set(java.util.Calendar.DAY_OF_WEEK, java.util.Calendar.MONDAY);
+                cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
+                cal.set(java.util.Calendar.MINUTE, 0);
+                cal.set(java.util.Calendar.SECOND, 0);
+                cal.set(java.util.Calendar.MILLISECOND, 0);
                 startDate = cal.getTime();
-                cal.add(java.util.Calendar.DAY_OF_WEEK, 6);
-                endDate = cal.getTime();
+                
+                cal.add(java.util.Calendar.DAY_OF_WEEK, 7);
+                endDate = cal.getTime(); // 下周一的 00:00:00,使用 .lt() 查询
             } else if ("MONTHLY".equals(statType)) {
-                // 月统计:从月初到月末
-                java.util.Calendar cal = java.util.Calendar.getInstance();
+                // 月统计:从月初 00:00:00 到月末 23:59:59
                 cal.setTime(statDate);
                 cal.set(java.util.Calendar.DAY_OF_MONTH, 1);
+                cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
+                cal.set(java.util.Calendar.MINUTE, 0);
+                cal.set(java.util.Calendar.SECOND, 0);
+                cal.set(java.util.Calendar.MILLISECOND, 0);
                 startDate = cal.getTime();
+                
                 cal.add(java.util.Calendar.MONTH, 1);
-                cal.add(java.util.Calendar.DAY_OF_MONTH, -1);
+                endDate = cal.getTime(); // 下个月1号的 00:00:00,使用 .lt() 查询
+            } else {
+                // 默认:日统计
+                cal.setTime(statDate);
+                cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
+                cal.set(java.util.Calendar.MINUTE, 0);
+                cal.set(java.util.Calendar.SECOND, 0);
+                cal.set(java.util.Calendar.MILLISECOND, 0);
+                startDate = cal.getTime();
+                
+                cal.add(java.util.Calendar.DAY_OF_MONTH, 1);
                 endDate = cal.getTime();
             }
+            
+            log.info("统计日期范围: startDate={}, endDate={}", startDate, endDate);
         
         // 查询或创建统计记录
         LambdaQueryWrapper<StoreTrackStatistics> queryWrapper = new LambdaQueryWrapper<>();
@@ -105,22 +149,14 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             statistics.setStatType(statType);
         }
         
-            // 从store_track_event表统计基础数据
-            LambdaQueryWrapper<StoreTrackEvent> eventWrapper = new LambdaQueryWrapper<>();
-            eventWrapper.eq(StoreTrackEvent::getStoreId, storeId)
-                    .ge(StoreTrackEvent::getEventTime, startDate)
-                    .le(StoreTrackEvent::getEventTime, endDate)
-                    .eq(StoreTrackEvent::getDeleteFlag, 0);
-
-            List<StoreTrackEvent> events = this.list(eventWrapper);
-
+            // 优化:直接使用数据库聚合查询,不再加载所有事件到内存
             // 按事件分类统计(符合文档格式)
-            Map<String, Object> trafficData = calculateTrafficData(events, storeId, startDate);
-            Map<String, Object> interactionData = calculateInteractionData(events, storeId);
-            Map<String, Object> couponData = calculateCouponData(events, storeId);
-            Map<String, Object> voucherData = calculateVoucherData(events, storeId);
-            Map<String, Object> serviceData = calculateServiceData(events, storeId);
-            List<Map<String, Object>> priceData = calculatePriceRankingData(events);
+            Map<String, Object> trafficData = calculateTrafficData(storeId, startDate, endDate);
+            Map<String, Object> interactionData = calculateInteractionData(storeId, startDate, endDate);
+            Map<String, Object> couponData = calculateCouponData(storeId, startDate, endDate);
+            Map<String, Object> voucherData = calculateVoucherData(storeId, startDate, endDate);
+            Map<String, Object> serviceData = calculateServiceData(storeId, startDate, endDate);
+            List<Map<String, Object>> priceData = calculatePriceRankingData(storeId, startDate, endDate);
 
             // 设置统计数据(JSON格式)
             statistics.setTrafficData(objectMapper.writeValueAsString(trafficData));
@@ -129,6 +165,13 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             statistics.setVoucherData(objectMapper.writeValueAsString(voucherData));
             statistics.setServiceData(objectMapper.writeValueAsString(serviceData));
             statistics.setPriceRankingData(objectMapper.writeValueAsString(priceData));
+            
+            // 输出统计结果(用于调试)
+            log.info("统计结果 - trafficData: {}", objectMapper.writeValueAsString(trafficData));
+            log.info("统计结果 - interactionData: {}", objectMapper.writeValueAsString(interactionData));
+            log.info("统计结果 - voucherData: {}", objectMapper.writeValueAsString(voucherData));
+            log.info("统计结果 - serviceData: {}", objectMapper.writeValueAsString(serviceData));
+            log.info("统计结果 - priceRankingData: {}", objectMapper.writeValueAsString(priceData));
         
         // 保存统计记录
         if (statistics.getId() == null) {
@@ -145,247 +188,839 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     }
 
     /**
-     * 计算流量数据(符合文档格式)
+     * 计算流量数据- 优化:使用数据库聚合查询
      */
-    private Map<String, Object> calculateTrafficData(List<StoreTrackEvent> events, Integer storeId, Date startDate) {
-        Map<String, Object> result = new java.util.HashMap<>();
+    private Map<String, Object> calculateTrafficData(Integer storeId, Date startDate, Date endDate) {
+        Map<String, Object> result = new HashMap<>();
+        
+        try {
+            StoreTrackEventMapper mapper = this.getBaseMapper();
         
-        List<StoreTrackEvent> trafficEvents = events.stream()
-                .filter(e -> "TRAFFIC".equals(e.getEventCategory()))
-                .collect(java.util.stream.Collectors.toList());
+            // 使用数据库聚合查询统计流量数据
+            Map<String, Object> trafficStats = mapper.calculateTrafficStatistics(storeId, startDate, endDate);
         
         // 搜索量
-        long searchCount = trafficEvents.stream()
-                .filter(e -> "SEARCH".equals(e.getEventType()))
-                .count();
+            Object searchCountObj = trafficStats.get("searchCount");
+            long searchCount = searchCountObj != null ? ((Number) searchCountObj).longValue() : 0L;
         result.put("searchCount", searchCount);
         
         // 浏览量
-        long viewCount = trafficEvents.stream()
-                .filter(e -> "VIEW".equals(e.getEventType()))
-                .count();
+            Object viewCountObj = trafficStats.get("viewCount");
+            long viewCount = viewCountObj != null ? ((Number) viewCountObj).longValue() : 0L;
         result.put("viewCount", viewCount);
-        
-        // 访客数(去重userId)
-        long visitorCount = trafficEvents.stream()
-                .filter(e -> e.getUserId() != null)
-                .map(StoreTrackEvent::getUserId)
-                .distinct()
-                .count();
-        result.put("visitorCount", visitorCount);
-        
-        // 新增访客数(在统计日期之前没有访问记录的用户)
-        // TODO: 需要查询历史数据,暂时返回0
-        result.put("newVisitorCount", 0L);
-        
-        // 总访问时长(所有浏览事件duration字段的总和)
-        long totalDuration = trafficEvents.stream()
-                .filter(e -> "VIEW".equals(e.getEventType()) && e.getDuration() != null)
-                .mapToLong(StoreTrackEvent::getDuration)
-                .sum();
+            
+            // 访客数(去重userId)- 使用数据库聚合查询
+            Long visitorCount = mapper.countDistinctVisitors(storeId, startDate, endDate);
+            result.put("visitorCount", visitorCount != null ? visitorCount : 0L);
+            
+            // 新增访客数 - 使用数据库聚合查询
+            Long newVisitorCount = mapper.countNewVisitors(storeId, startDate, endDate);
+            result.put("newVisitorCount", newVisitorCount != null ? newVisitorCount : 0L);
+            
+            // 总访问时长
+            Object totalDurationObj = trafficStats.get("totalDuration");
+            long totalDuration = totalDurationObj != null ? ((Number) totalDurationObj).longValue() : 0L;
         result.put("totalDuration", totalDuration);
         
         // 平均访问时长
-        long viewEventsWithDuration = trafficEvents.stream()
-                .filter(e -> "VIEW".equals(e.getEventType()) && e.getDuration() != null)
-                .count();
-        long avgDuration = viewEventsWithDuration > 0 ? totalDuration / viewEventsWithDuration : 0L;
+            Object avgDurationObj = trafficStats.get("avgDuration");
+            long avgDuration = avgDurationObj != null ? ((Number) avgDurationObj).longValue() : 0L;
         result.put("avgDuration", avgDuration);
+            
+            log.debug("流量数据统计完成: searchCount={}, viewCount={}, visitorCount={}, newVisitorCount={}", 
+                    searchCount, viewCount, visitorCount, newVisitorCount);
+        } catch (Exception e) {
+            log.error("计算流量数据失败: storeId={}", storeId, e);
+            // 返回默认值,避免统计失败
+            result.put("searchCount", 0L);
+            result.put("viewCount", 0L);
+            result.put("visitorCount", 0L);
+            result.put("newVisitorCount", 0L);
+            result.put("totalDuration", 0L);
+            result.put("avgDuration", 0L);
+        }
         
         return result;
     }
     
     /**
-     * 计算互动数据(符合文档格式)
+     * 计算互动数据- 优化:使用数据库聚合查询
      */
-    private Map<String, Object> calculateInteractionData(List<StoreTrackEvent> events, Integer storeId) {
-        Map<String, Object> result = new java.util.HashMap<>();
+    private Map<String, Object> calculateInteractionData(Integer storeId, Date startDate, Date endDate) {
+        Map<String, Object> result = new HashMap<>();
         
-        List<StoreTrackEvent> interactionEvents = events.stream()
-                .filter(e -> "INTERACTION".equals(e.getEventCategory()))
-                .collect(java.util.stream.Collectors.toList());
-        
-        result.put("collectCount", countByEventType(interactionEvents, "COLLECT"));
-        result.put("shareCount", countByEventType(interactionEvents, "SHARE"));
-        result.put("checkinCount", countByEventType(interactionEvents, "CHECKIN"));
-        result.put("consultCount", countByEventType(interactionEvents, "CONSULT"));
-        result.put("postLikeCount", countByEventType(interactionEvents, "POST_LIKE"));
-        result.put("postCommentCount", countByEventType(interactionEvents, "POST_COMMENT"));
-        result.put("postRepostCount", countByEventType(interactionEvents, "POST_REPOST"));
-        result.put("reportCount", countByEventType(interactionEvents, "REPORT"));
-        result.put("blockCount", countByEventType(interactionEvents, "BLOCK"));
-        
-        // TODO: 需要从其他表查询的数据,暂时返回0
+        try {
+            StoreTrackEventMapper mapper = this.getBaseMapper();
+            
+            // 使用数据库聚合查询统计互动事件(从埋点事件表store_track_event中统计)
+            List<Map<String, Object>> interactionStats = mapper.calculateInteractionStatistics(storeId, startDate, endDate);
+            
+            // 将查询结果转换为Map,便于查找
+            // 注意:SQL返回的字段名是下划线命名(event_type),而非驼峰命名
+            Map<String, Long> eventTypeCountMap = new HashMap<>();
+            for (Map<String, Object> stat : interactionStats) {
+                String eventType = (String) stat.get("event_type");
+                Object countObj = stat.get("count");
+                long count = countObj != null ? ((Number) countObj).longValue() : 0L;
+                if (eventType != null) {
+                    eventTypeCountMap.put(eventType, count);
+                }
+            }
+            
+            // 从聚合查询结果中获取各类型事件数量(从埋点事件表store_track_event中统计)
+            result.put("collectCount", eventTypeCountMap.getOrDefault("COLLECT", 0L));
+            result.put("shareCount", eventTypeCountMap.getOrDefault("SHARE", 0L));
+            result.put("checkinCount", eventTypeCountMap.getOrDefault("CHECKIN", 0L));
+            result.put("consultCount", eventTypeCountMap.getOrDefault("CONSULT", 0L));
+            result.put("postLikeCount", eventTypeCountMap.getOrDefault("POST_LIKE", 0L));
+            result.put("postCommentCount", eventTypeCountMap.getOrDefault("POST_COMMENT", 0L));
+            result.put("postRepostCount", eventTypeCountMap.getOrDefault("POST_REPOST", 0L));
+            result.put("reportCount", eventTypeCountMap.getOrDefault("REPORT", 0L));
+            result.put("blockCount", eventTypeCountMap.getOrDefault("BLOCK", 0L));
+            
+            // 记录调试日志
+            log.debug("互动数据统计结果: storeId={}, 统计到的事件类型={}, 各类型数量={}", 
+                    storeId, eventTypeCountMap.keySet(), eventTypeCountMap);
+            
+            // 从其他表查询的数据(life_fans表使用phoneId关联)
+            String storePhoneId = getStorePhoneId(storeId);
+            log.debug("获取店铺phoneId: storeId={}, storePhoneId={}", storeId, storePhoneId);
+            if (storePhoneId != null) {
+                long friendCount = calculateFriendCount(storePhoneId, startDate, endDate);
+                long followCount = calculateFollowCount(storePhoneId, startDate, endDate);
+                long fansCount = calculateFansCount(storePhoneId, startDate, endDate);
+                result.put("friendCount", friendCount);
+                result.put("followCount", followCount);
+                result.put("fansCount", fansCount);
+                log.debug("好友/关注/粉丝数量: storeId={}, phoneId={}, friendCount={}, followCount={}, fansCount={}", 
+                        storeId, storePhoneId, friendCount, followCount, fansCount);
+            } else {
+                log.warn("无法获取店铺phoneId: storeId={}", storeId);
+                result.put("friendCount", 0L);
+                result.put("followCount", 0L);
+                result.put("fansCount", 0L);
+            }
+            result.put("postCount", calculatePostCount(storeId, startDate, endDate));
+        } catch (Exception e) {
+            log.error("计算互动数据失败: storeId={}", storeId, e);
+            // 返回默认值
+            result.put("collectCount", 0L);
+            result.put("shareCount", 0L);
+            result.put("checkinCount", 0L);
+            result.put("consultCount", 0L);
+            result.put("postLikeCount", 0L);
+            result.put("postCommentCount", 0L);
+            result.put("postRepostCount", 0L);
+            result.put("reportCount", 0L);
+            result.put("blockCount", 0L);
         result.put("friendCount", 0L);
         result.put("followCount", 0L);
         result.put("fansCount", 0L);
         result.put("postCount", 0L);
+        }
         
         return result;
     }
     
     /**
-     * 计算优惠券数据(符合文档格式)
+     * 获取店铺的phoneId(格式:store_手机号)
+     */
+    private String getStorePhoneId(Integer storeId) {
+        try {
+            LambdaQueryWrapper<StoreUser> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(StoreUser::getStoreId, storeId)
+                    .eq(StoreUser::getDeleteFlag, 0)
+                    .last("LIMIT 1");
+            StoreUser storeUser = storeUserMapper.selectOne(wrapper);
+            if (storeUser != null && storeUser.getPhone() != null) {
+                return "store_" + storeUser.getPhone();
+            }
+        } catch (Exception e) {
+            log.error("获取店铺phoneId失败: storeId={}", storeId, e);
+        }
+        return null;
+    }
+    
+    /**
+     * 计算好友数量(互相关注的用户数)
+     * 注意:好友数量是累计数据,统计截止到endDate的所有互相关注关系
+     */
+    private long calculateFriendCount(String storePhoneId, Date startDate, Date endDate) {
+        try {
+            // 查询店铺关注的所有用户(截止到统计日期)
+            LambdaQueryWrapper<LifeFans> followWrapper = new LambdaQueryWrapper<>();
+            followWrapper.eq(LifeFans::getFansId, storePhoneId)
+                    .lt(LifeFans::getCreatedTime, endDate)
+                    .eq(LifeFans::getDeleteFlag, 0);
+            Set<String> followedIds = lifeFansMapper.selectList(followWrapper).stream()
+                    .map(LifeFans::getFollowedId)
+                    .collect(Collectors.toSet());
+            
+            if (followedIds.isEmpty()) {
+                return 0L;
+            }
+            
+            // 查询关注店铺的所有用户(截止到统计日期)
+            LambdaQueryWrapper<LifeFans> fansWrapper = new LambdaQueryWrapper<>();
+            fansWrapper.eq(LifeFans::getFollowedId, storePhoneId)
+                    .lt(LifeFans::getCreatedTime, endDate)
+                    .eq(LifeFans::getDeleteFlag, 0);
+            Set<String> fansIds = lifeFansMapper.selectList(fansWrapper).stream()
+                    .map(LifeFans::getFansId)
+                    .collect(Collectors.toSet());
+            
+            // 互相关注的用户数 = 交集
+            followedIds.retainAll(fansIds);
+            return followedIds.size();
+        } catch (Exception e) {
+            log.error("计算好友数量失败: storePhoneId={}", storePhoneId, e);
+            return 0L;
+        }
+    }
+    
+    /**
+     * 计算关注数量(店铺用户关注的人数)
+     * 注意:关注数量是累计数据,统计截止到endDate的所有关注关系
+     */
+    private long calculateFollowCount(String storePhoneId, Date startDate, Date endDate) {
+        try {
+            LambdaQueryWrapper<LifeFans> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LifeFans::getFansId, storePhoneId)
+                    .lt(LifeFans::getCreatedTime, endDate)
+                    .eq(LifeFans::getDeleteFlag, 0);
+            return lifeFansMapper.selectCount(wrapper);
+        } catch (Exception e) {
+            log.error("计算关注数量失败: storePhoneId={}", storePhoneId, e);
+            return 0L;
+        }
+    }
+    
+    /**
+     * 计算粉丝数量(关注店铺用户的人数)
+     * 注意:粉丝数量是累计数据,统计截止到endDate的所有粉丝关系
      */
-    private Map<String, Object> calculateCouponData(List<StoreTrackEvent> events, Integer storeId) {
-        Map<String, Object> result = new java.util.HashMap<>();
+    private long calculateFansCount(String storePhoneId, Date startDate, Date endDate) {
+        try {
+            LambdaQueryWrapper<LifeFans> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LifeFans::getFollowedId, storePhoneId)
+                    .lt(LifeFans::getCreatedTime, endDate)
+                    .eq(LifeFans::getDeleteFlag, 0);
+            return lifeFansMapper.selectCount(wrapper);
+        } catch (Exception e) {
+            log.error("计算粉丝数量失败: storePhoneId={}", storePhoneId, e);
+            return 0L;
+        }
+    }
+    
+    /**
+     * 计算发布动态数量(从life_user_dynamics表查询,type=2商家社区)
+     */
+    private long calculatePostCount(Integer storeId, Date startDate, Date endDate) {
+        try {
+            String storePhoneId = getStorePhoneId(storeId);
+            if (storePhoneId == null) {
+                return 0L;
+            }
+            
+            LambdaQueryWrapper<LifeUserDynamics> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LifeUserDynamics::getPhoneId, storePhoneId)
+                    .eq(LifeUserDynamics::getType, "2") // 商家社区
+                    .eq(LifeUserDynamics::getDraft, 0) // 非草稿
+                    .ge(LifeUserDynamics::getCreatedTime, startDate)
+                    .lt(LifeUserDynamics::getCreatedTime, endDate)
+                    .eq(LifeUserDynamics::getDeleteFlag, 0);
+            return lifeUserDynamicsMapper.selectCount(wrapper);
+        } catch (Exception e) {
+            log.error("计算发布动态数量失败: storeId={}", storeId, e);
+            return 0L;
+        }
+    }
+    
+    /**
+     * 计算优惠券数据
+     */
+    private Map<String, Object> calculateCouponData(Integer storeId, Date startDate, Date endDate) {
+        Map<String, Object> result = new HashMap<>();
         
-        // TODO: 需要从 life_discount_coupon_user 等表查询,暂时返回0
-        result.put("giveToFriendCount", 0L);
-        result.put("giveToFriendAmount", java.math.BigDecimal.ZERO);
-        result.put("giveToFriendUseCount", 0L);
-        result.put("giveToFriendUseAmount", java.math.BigDecimal.ZERO);
-        result.put("giveToFriendUseAmountPercent", 0.0);
-        result.put("friendGiveCount", 0L);
-        result.put("friendGiveAmount", java.math.BigDecimal.ZERO);
-        result.put("friendGiveUseCount", 0L);
-        result.put("friendGiveUseAmount", java.math.BigDecimal.ZERO);
-        result.put("friendGiveUseAmountPercent", 0.0);
+        try {
+            // 赠送好友相关数据(从life_discount_coupon_user表统计,关联店铺的优惠券,type=1优惠券)
+            String storeIdStr = String.valueOf(storeId);
+            List<LifeDiscountCouponUser> couponUsers = queryCouponUsersByStore(storeIdStr, startDate, endDate);
+            
+            // 赠送好友数量
+            long giveToFriendCount = couponUsers.size();
+            result.put("giveToFriendCount", giveToFriendCount);
+            
+            // 赠送好友金额合计(优惠券面值的总和)
+            BigDecimal giveToFriendAmount = couponUsers.stream()
+                    .map(cu -> {
+                        LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(cu.getCouponId());
+                        return coupon != null && coupon.getNominalValue() != null ? coupon.getNominalValue() : BigDecimal.ZERO;
+                    })
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            result.put("giveToFriendAmount", giveToFriendAmount);
+            
+            // 赠送好友使用数量(status=1已使用的数量)
+            long giveToFriendUseCount = couponUsers.stream()
+                    .filter(cu -> cu.getStatus() != null && cu.getStatus() == 1)
+                    .count();
+            result.put("giveToFriendUseCount", giveToFriendUseCount);
+            
+            // 赠送好友使用金额合计(已使用优惠券的面值总和)
+            BigDecimal giveToFriendUseAmount = couponUsers.stream()
+                    .filter(cu -> cu.getStatus() != null && cu.getStatus() == 1)
+                    .map(cu -> {
+                        LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(cu.getCouponId());
+                        return coupon != null && coupon.getNominalValue() != null ? coupon.getNominalValue() : BigDecimal.ZERO;
+                    })
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            result.put("giveToFriendUseAmount", giveToFriendUseAmount);
+            
+            // 赠送好友使用金额占比
+            double giveToFriendUseAmountPercent = 0.0;
+            if (giveToFriendAmount.compareTo(BigDecimal.ZERO) > 0) {
+                giveToFriendUseAmountPercent = giveToFriendUseAmount
+                        .divide(giveToFriendAmount, 4, RoundingMode.HALF_UP)
+                        .multiply(BigDecimal.valueOf(100))
+                        .doubleValue();
+            }
+            result.put("giveToFriendUseAmountPercent", giveToFriendUseAmountPercent);
+            
+            // 好友赠送相关数据(从life_discount_coupon_store_friend表统计,type=1优惠券)
+            List<LifeDiscountCouponStoreFriend> friendCoupons = queryFriendCouponsByStore(storeId, startDate, endDate, 1);
+            
+            // 好友赠送数量
+            long friendGiveCount = friendCoupons.size();
+            result.put("friendGiveCount", friendGiveCount);
+            
+            // 好友赠送金额合计(好友赠送的优惠券面值总和)
+            BigDecimal friendGiveAmount = friendCoupons.stream()
+                    .map(fc -> {
+                        LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(fc.getCouponId());
+                        return coupon != null && coupon.getNominalValue() != null ? coupon.getNominalValue() : BigDecimal.ZERO;
+                    })
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            result.put("friendGiveAmount", friendGiveAmount);
+            
+            // 好友赠送使用数量(已使用的数量)
+            // 需要通过coupon_id和friend_store_user_id查询life_discount_coupon_user表
+            long friendGiveUseCount = calculateFriendCouponUseCount(friendCoupons, startDate, endDate);
+            result.put("friendGiveUseCount", friendGiveUseCount);
+            
+            // 好友赠送使用金额合计(已使用的优惠券面值总和)
+            BigDecimal friendGiveUseAmount = calculateFriendCouponUseAmount(friendCoupons, startDate, endDate);
+            result.put("friendGiveUseAmount", friendGiveUseAmount);
+            
+            // 好友赠送使用金额占比
+            double friendGiveUseAmountPercent = 0.0;
+            if (friendGiveAmount.compareTo(BigDecimal.ZERO) > 0) {
+                friendGiveUseAmountPercent = friendGiveUseAmount
+                        .divide(friendGiveAmount, 4, RoundingMode.HALF_UP)
+                        .multiply(BigDecimal.valueOf(100))
+                        .doubleValue();
+            }
+            result.put("friendGiveUseAmountPercent", friendGiveUseAmountPercent);
+        } catch (Exception e) {
+            log.error("计算优惠券数据失败: storeId={}", storeId, e);
+            // 返回默认值,避免统计失败
+            result.put("giveToFriendCount", 0L);
+            result.put("giveToFriendAmount", BigDecimal.ZERO);
+            result.put("giveToFriendUseCount", 0L);
+            result.put("giveToFriendUseAmount", BigDecimal.ZERO);
+            result.put("giveToFriendUseAmountPercent", 0.0);
+            result.put("friendGiveCount", 0L);
+            result.put("friendGiveAmount", BigDecimal.ZERO);
+            result.put("friendGiveUseCount", 0L);
+            result.put("friendGiveUseAmount", BigDecimal.ZERO);
+            result.put("friendGiveUseAmountPercent", 0.0);
+        }
         
         return result;
     }
     
     /**
-     * 计算代金券数据(符合文档格式)
+     * 查询店铺关联的优惠券用户数据
      */
-    private Map<String, Object> calculateVoucherData(List<StoreTrackEvent> events, Integer storeId) {
-        Map<String, Object> result = new java.util.HashMap<>();
-        
-        List<StoreTrackEvent> voucherEvents = events.stream()
-                .filter(e -> "VOUCHER".equals(e.getEventCategory()))
-                .collect(java.util.stream.Collectors.toList());
-        
-        // 赠送好友数量
-        long giveToFriendCount = countByEventType(voucherEvents, "VOUCHER_GIVE");
-        result.put("giveToFriendCount", giveToFriendCount);
-        
-        // 赠送好友金额合计
-        java.math.BigDecimal giveToFriendAmount = voucherEvents.stream()
-                .filter(e -> "VOUCHER_GIVE".equals(e.getEventType()) && e.getAmount() != null)
-                .map(StoreTrackEvent::getAmount)
-                .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
-        result.put("giveToFriendAmount", giveToFriendAmount);
-        
-        // 赠送好友使用数量
-        long giveToFriendUseCount = countByEventType(voucherEvents, "VOUCHER_USE");
-        result.put("giveToFriendUseCount", giveToFriendUseCount);
+    private List<LifeDiscountCouponUser> queryCouponUsersByStore(String storeId, Date startDate, Date endDate) {
+        try {
+            // 先查询店铺的优惠券(type=1优惠券)
+            LambdaQueryWrapper<LifeDiscountCoupon> couponWrapper = new LambdaQueryWrapper<>();
+            couponWrapper.eq(LifeDiscountCoupon::getStoreId, storeId)
+                    .eq(LifeDiscountCoupon::getType, 1) // 优惠券
+                    .eq(LifeDiscountCoupon::getDeleteFlag, 0);
+            List<LifeDiscountCoupon> coupons = lifeDiscountCouponMapper.selectList(couponWrapper);
+            
+            if (coupons.isEmpty()) {
+                return Collections.emptyList();
+            }
+            
+            List<Integer> couponIds = coupons.stream()
+                    .map(LifeDiscountCoupon::getId)
+                    .collect(Collectors.toList());
+            
+            // 查询优惠券用户数据
+            LambdaQueryWrapper<LifeDiscountCouponUser> wrapper = new LambdaQueryWrapper<>();
+            wrapper.in(LifeDiscountCouponUser::getCouponId, couponIds)
+                    .ge(LifeDiscountCouponUser::getReceiveTime, startDate)
+                    .lt(LifeDiscountCouponUser::getReceiveTime, endDate)
+                    .eq(LifeDiscountCouponUser::getDeleteFlag, 0);
+            return lifeDiscountCouponUserMapper.selectList(wrapper);
+        } catch (Exception e) {
+            log.error("查询店铺优惠券用户数据失败: storeId={}", storeId, e);
+            return Collections.emptyList();
+        }
+    }
+    
+    /**
+     * 查询好友赠送的优惠券/代金券数据
+     */
+    private List<LifeDiscountCouponStoreFriend> queryFriendCouponsByStore(Integer storeId, Date startDate, Date endDate, Integer type) {
+        try {
+            // 先查询店铺用户
+            LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
+            userWrapper.eq(StoreUser::getStoreId, storeId)
+                    .eq(StoreUser::getDeleteFlag, 0);
+            List<StoreUser> storeUsers = storeUserMapper.selectList(userWrapper);
+            
+            if (storeUsers.isEmpty()) {
+                return Collections.emptyList();
+            }
+            
+            List<Integer> storeUserIds = storeUsers.stream()
+                    .map(StoreUser::getId)
+                    .collect(Collectors.toList());
+            
+            // 查询好友赠送的优惠券/代金券
+            // 需要通过coupon_id关联查询优惠券类型
+            LambdaQueryWrapper<LifeDiscountCouponStoreFriend> wrapper = new LambdaQueryWrapper<>();
+            wrapper.in(LifeDiscountCouponStoreFriend::getFriendStoreUserId, storeUserIds)
+                    .ge(LifeDiscountCouponStoreFriend::getCreatedTime, startDate)
+                    .lt(LifeDiscountCouponStoreFriend::getCreatedTime, endDate)
+                    .eq(LifeDiscountCouponStoreFriend::getDeleteFlag, 0);
+            List<LifeDiscountCouponStoreFriend> storeFriends = lifeDiscountCouponStoreFriendMapper.selectList(wrapper);
+            
+            // 过滤指定类型的优惠券
+            return storeFriends.stream()
+                    .filter(sf -> {
+                        LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(sf.getCouponId());
+                        return coupon != null && coupon.getType() != null && coupon.getType().equals(type);
+                    })
+                    .collect(Collectors.toList());
+        } catch (Exception e) {
+            log.error("查询好友赠送优惠券/代金券数据失败: storeId={}, type={}", storeId, type, e);
+            return Collections.emptyList();
+        }
+    }
+    
+    /**
+     * 计算好友赠送优惠券使用数量
+     */
+    private long calculateFriendCouponUseCount(List<LifeDiscountCouponStoreFriend> friendCoupons, Date startDate, Date endDate) {
+        if (friendCoupons == null || friendCoupons.isEmpty()) {
+            return 0L;
+        }
         
-        // 赠送好友使用金额合计
-        java.math.BigDecimal giveToFriendUseAmount = voucherEvents.stream()
-                .filter(e -> "VOUCHER_USE".equals(e.getEventType()) && e.getAmount() != null)
-                .map(StoreTrackEvent::getAmount)
-                .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
-        result.put("giveToFriendUseAmount", giveToFriendUseAmount);
+        try {
+            List<Integer> couponIds = friendCoupons.stream()
+                    .map(LifeDiscountCouponStoreFriend::getCouponId)
+                    .distinct()
+                    .collect(Collectors.toList());
+            
+            LambdaQueryWrapper<LifeDiscountCouponUser> wrapper = new LambdaQueryWrapper<>();
+            wrapper.in(LifeDiscountCouponUser::getCouponId, couponIds)
+                    .eq(LifeDiscountCouponUser::getStatus, 1) // 已使用
+                    .ge(LifeDiscountCouponUser::getUseTime, startDate)
+                    .lt(LifeDiscountCouponUser::getUseTime, endDate)
+                    .eq(LifeDiscountCouponUser::getDeleteFlag, 0);
+            return lifeDiscountCouponUserMapper.selectCount(wrapper);
+        } catch (Exception e) {
+            log.error("计算好友赠送优惠券使用数量失败", e);
+            return 0L;
+        }
+    }
+    
+    /**
+     * 计算好友赠送优惠券使用金额
+     */
+    private BigDecimal calculateFriendCouponUseAmount(List<LifeDiscountCouponStoreFriend> friendCoupons, Date startDate, Date endDate) {
+        if (friendCoupons == null || friendCoupons.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
         
-        // 赠送好友使用金额占比
-        double giveToFriendUseAmountPercent = 0.0;
-        if (giveToFriendAmount.compareTo(java.math.BigDecimal.ZERO) > 0) {
-            giveToFriendUseAmountPercent = giveToFriendUseAmount.divide(giveToFriendAmount, 4, java.math.RoundingMode.HALF_UP)
-                    .multiply(new java.math.BigDecimal("100")).doubleValue();
+        try {
+            List<Integer> couponIds = friendCoupons.stream()
+                    .map(LifeDiscountCouponStoreFriend::getCouponId)
+                    .distinct()
+                    .collect(Collectors.toList());
+            
+            LambdaQueryWrapper<LifeDiscountCouponUser> wrapper = new LambdaQueryWrapper<>();
+            wrapper.in(LifeDiscountCouponUser::getCouponId, couponIds)
+                    .eq(LifeDiscountCouponUser::getStatus, 1) // 已使用
+                    .ge(LifeDiscountCouponUser::getUseTime, startDate)
+                    .lt(LifeDiscountCouponUser::getUseTime, endDate)
+                    .eq(LifeDiscountCouponUser::getDeleteFlag, 0);
+            List<LifeDiscountCouponUser> usedCoupons = lifeDiscountCouponUserMapper.selectList(wrapper);
+            
+            return usedCoupons.stream()
+                    .map(cu -> {
+                        LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(cu.getCouponId());
+                        return coupon != null && coupon.getNominalValue() != null ? coupon.getNominalValue() : BigDecimal.ZERO;
+                    })
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+        } catch (Exception e) {
+            log.error("计算好友赠送优惠券使用金额失败", e);
+            return BigDecimal.ZERO;
         }
-        result.put("giveToFriendUseAmountPercent", giveToFriendUseAmountPercent);
+    }
+    
+    /**
+     * 计算代金券数据- 优化:使用数据库聚合查询
+     */
+    private Map<String, Object> calculateVoucherData(Integer storeId, Date startDate, Date endDate) {
+        Map<String, Object> result = new HashMap<>();
         
-        // TODO: 需要从 life_discount_coupon_store_friend 表查询的数据,暂时返回0
+        try {
+            StoreTrackEventMapper mapper = this.getBaseMapper();
+            
+            // 使用数据库聚合查询统计代金券事件
+            List<Map<String, Object>> voucherStats = mapper.calculateVoucherStatistics(storeId, startDate, endDate);
+            
+            // 将查询结果转换为Map,便于查找
+            // 注意:SQL返回的字段名是下划线命名(event_type),而非驼峰命名
+            Map<String, Map<String, Object>> eventTypeMap = new HashMap<>();
+            for (Map<String, Object> stat : voucherStats) {
+                String eventType = (String) stat.get("event_type");
+                if (eventType != null) {
+                    eventTypeMap.put(eventType, stat);
+                }
+            }
+            
+            // 赠送好友数量
+            Map<String, Object> giveStat = eventTypeMap.getOrDefault("VOUCHER_GIVE", new HashMap<>());
+            Object giveCountObj = giveStat.get("count");
+            long giveToFriendCount = giveCountObj != null ? ((Number) giveCountObj).longValue() : 0L;
+            result.put("giveToFriendCount", giveToFriendCount);
+            
+            // 赠送好友金额合计
+            Object giveAmountObj = giveStat.get("totalAmount");
+            BigDecimal giveToFriendAmount = giveAmountObj != null ? 
+                    new BigDecimal(giveAmountObj.toString()) : BigDecimal.ZERO;
+            result.put("giveToFriendAmount", giveToFriendAmount);
+            
+            // 赠送好友使用数量
+            Map<String, Object> useStat = eventTypeMap.getOrDefault("VOUCHER_USE", new HashMap<>());
+            Object useCountObj = useStat.get("count");
+            long giveToFriendUseCount = useCountObj != null ? ((Number) useCountObj).longValue() : 0L;
+            result.put("giveToFriendUseCount", giveToFriendUseCount);
+            
+            // 赠送好友使用金额合计
+            Object useAmountObj = useStat.get("totalAmount");
+            BigDecimal giveToFriendUseAmount = useAmountObj != null ? 
+                    new BigDecimal(useAmountObj.toString()) : BigDecimal.ZERO;
+            result.put("giveToFriendUseAmount", giveToFriendUseAmount);
+            
+            // 赠送好友使用金额占比
+            double giveToFriendUseAmountPercent = 0.0;
+            if (giveToFriendAmount.compareTo(BigDecimal.ZERO) > 0) {
+                giveToFriendUseAmountPercent = giveToFriendUseAmount
+                        .divide(giveToFriendAmount, 4, RoundingMode.HALF_UP)
+                        .multiply(BigDecimal.valueOf(100))
+                        .doubleValue();
+            }
+            result.put("giveToFriendUseAmountPercent", giveToFriendUseAmountPercent);
+            
+            // 好友赠送代金券相关数据(从life_discount_coupon_store_friend表统计,type=2代金券)
+            List<LifeDiscountCouponStoreFriend> friendVouchers = queryFriendCouponsByStore(storeId, startDate, endDate, 2); // type=2代金券
+            
+            // 好友赠送数量
+            long friendGiveCount = friendVouchers.size();
+            result.put("friendGiveCount", friendGiveCount);
+            
+            // 好友赠送金额合计(好友赠送的代金券面值总和)
+            BigDecimal friendGiveAmount = friendVouchers.stream()
+                    .map(fv -> {
+                        LifeDiscountCoupon voucher = lifeDiscountCouponMapper.selectById(fv.getCouponId());
+                        return voucher != null && voucher.getNominalValue() != null ? voucher.getNominalValue() : BigDecimal.ZERO;
+                    })
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            result.put("friendGiveAmount", friendGiveAmount);
+            
+            // 好友赠送使用数量(已使用的数量)
+            long friendGiveUseCount = calculateFriendCouponUseCount(friendVouchers, startDate, endDate);
+            result.put("friendGiveUseCount", friendGiveUseCount);
+            
+            // 好友赠送使用金额合计(已使用的代金券面值总和)
+            BigDecimal friendGiveUseAmount = calculateFriendCouponUseAmount(friendVouchers, startDate, endDate);
+            result.put("friendGiveUseAmount", friendGiveUseAmount);
+            
+            // 好友赠送使用金额占比
+            double friendGiveUseAmountPercent = 0.0;
+            if (friendGiveAmount.compareTo(BigDecimal.ZERO) > 0) {
+                friendGiveUseAmountPercent = friendGiveUseAmount
+                        .divide(friendGiveAmount, 4, RoundingMode.HALF_UP)
+                        .multiply(BigDecimal.valueOf(100))
+                        .doubleValue();
+            }
+            result.put("friendGiveUseAmountPercent", friendGiveUseAmountPercent);
+        } catch (Exception e) {
+            log.error("计算代金券数据失败: storeId={}", storeId, e);
+            // 返回默认值
+            result.put("giveToFriendCount", 0L);
+            result.put("giveToFriendAmount", BigDecimal.ZERO);
+            result.put("giveToFriendUseCount", 0L);
+            result.put("giveToFriendUseAmount", BigDecimal.ZERO);
+            result.put("giveToFriendUseAmountPercent", 0.0);
         result.put("friendGiveCount", 0L);
-        result.put("friendGiveAmount", java.math.BigDecimal.ZERO);
+            result.put("friendGiveAmount", BigDecimal.ZERO);
         result.put("friendGiveUseCount", 0L);
-        result.put("friendGiveUseAmount", java.math.BigDecimal.ZERO);
+            result.put("friendGiveUseAmount", BigDecimal.ZERO);
         result.put("friendGiveUseAmountPercent", 0.0);
+        }
         
         return result;
     }
     
     /**
-     * 计算服务质量数据(符合文档格式)
+     * 计算服务质量数据- 优化:使用数据库聚合查询
      */
-    private Map<String, Object> calculateServiceData(List<StoreTrackEvent> events, Integer storeId) {
-        Map<String, Object> result = new java.util.HashMap<>();
-        
-        List<StoreTrackEvent> serviceEvents = events.stream()
-                .filter(e -> "SERVICE".equals(e.getEventCategory()))
-                .collect(java.util.stream.Collectors.toList());
+    private Map<String, Object> calculateServiceData(Integer storeId, Date startDate, Date endDate) {
+        Map<String, Object> result = new HashMap<>();
         
-        // 差评申诉次数
-        long appealCount = countByEventType(serviceEvents, "APPEAL");
-        result.put("appealCount", appealCount);
+        try {
+            StoreTrackEventMapper mapper = this.getBaseMapper();
+            
+            // 使用数据库聚合查询统计服务事件
+            List<Map<String, Object>> serviceStats = mapper.calculateServiceStatistics(storeId, startDate, endDate);
+            
+            // 将查询结果转换为Map,便于查找
+            // 注意:SQL返回的字段名是下划线命名(event_type),而非驼峰命名
+            Map<String, Long> eventTypeCountMap = new HashMap<>();
+            for (Map<String, Object> stat : serviceStats) {
+                String eventType = (String) stat.get("event_type");
+                Object countObj = stat.get("count");
+                long count = countObj != null ? ((Number) countObj).longValue() : 0L;
+                if (eventType != null) {
+                    eventTypeCountMap.put(eventType, count);
+                }
+            }
         
-        // TODO: 需要从 common_rating 和 store_comment_appeal 表查询的数据,暂时返回0或null
-        result.put("storeScore", null);
-        result.put("tasteScore", null);
-        result.put("environmentScore", null);
-        result.put("serviceScore", null);
-        result.put("ratingCount", 0L);
-        result.put("goodRatingCount", 0L);
-        result.put("midRatingCount", 0L);
-        result.put("badRatingCount", 0L);
-        result.put("badRatingPercent", 0.0);
-        result.put("appealSuccessCount", 0L);
-        result.put("appealSuccessPercent", 0.0);
+            // 差评申诉次数
+            long appealCount = eventTypeCountMap.getOrDefault("APPEAL", 0L);
+            result.put("appealCount", appealCount);
+            
+            // 从 common_rating 表查询评价数据
+            List<CommonRating> ratings = queryRatings(storeId, startDate, endDate);
+            
+            // 店铺评分(计算平均分)
+            Double storeScore = calculateStoreScore(ratings);
+            result.put("storeScore", storeScore);
+            
+            // 口味评分、环境评分、服务评分
+            Map<String, Double> otherScores = calculateOtherScores(ratings);
+            result.put("tasteScore", otherScores.getOrDefault("口味", null));
+            result.put("environmentScore", otherScores.getOrDefault("环境", null));
+            result.put("serviceScore", otherScores.getOrDefault("服务", null));
+            
+            // 评价数量
+            long ratingCount = ratings.size();
+            result.put("ratingCount", ratingCount);
+            
+            // 好评数量(score >= 4.5)
+            long goodRatingCount = ratings.stream()
+                    .filter(r -> r.getScore() != null && r.getScore() >= 4.5)
+                    .count();
+            result.put("goodRatingCount", goodRatingCount);
+            
+            // 中评数量(3.0 <= score <= 4.0)
+            long midRatingCount = ratings.stream()
+                    .filter(r -> r.getScore() != null && r.getScore() >= 3.0 && r.getScore() <= 4.0)
+                    .count();
+            result.put("midRatingCount", midRatingCount);
+            
+            // 差评数量(0.5 <= score <= 2.5)
+            long badRatingCount = ratings.stream()
+                    .filter(r -> r.getScore() != null && r.getScore() >= 0.5 && r.getScore() <= 2.5)
+                    .count();
+            result.put("badRatingCount", badRatingCount);
+            
+            // 差评占比
+            double badRatingPercent = 0.0;
+            if (ratingCount > 0) {
+                badRatingPercent = (double) badRatingCount / ratingCount * 100;
+                badRatingPercent = BigDecimal.valueOf(badRatingPercent)
+                        .setScale(2, RoundingMode.HALF_UP)
+                        .doubleValue();
+            }
+            result.put("badRatingPercent", badRatingPercent);
+            
+            // 差评申诉成功次数
+            long appealSuccessCount = calculateAppealSuccessCount(storeId, startDate, endDate);
+            result.put("appealSuccessCount", appealSuccessCount);
+            
+            // 差评申诉成功占比
+            double appealSuccessPercent = 0.0;
+            if (appealCount > 0) {
+                appealSuccessPercent = (double) appealSuccessCount / appealCount * 100;
+                appealSuccessPercent = BigDecimal.valueOf(appealSuccessPercent)
+                        .setScale(2, RoundingMode.HALF_UP)
+                        .doubleValue();
+            }
+            result.put("appealSuccessPercent", appealSuccessPercent);
+        } catch (Exception e) {
+            log.error("计算服务质量数据失败: storeId={}", storeId, e);
+            // 返回默认值,避免统计失败
+            result.put("storeScore", null);
+            result.put("tasteScore", null);
+            result.put("environmentScore", null);
+            result.put("serviceScore", null);
+            result.put("ratingCount", 0L);
+            result.put("goodRatingCount", 0L);
+            result.put("midRatingCount", 0L);
+            result.put("badRatingCount", 0L);
+            result.put("badRatingPercent", 0.0);
+            result.put("appealCount", 0L);
+            result.put("appealSuccessCount", 0L);
+            result.put("appealSuccessPercent", 0.0);
+        }
         
         return result;
     }
     
     /**
-     * 计算价目表排名数据(符合文档格式)
+     * 查询评价数据
      */
-    private List<Map<String, Object>> calculatePriceRankingData(List<StoreTrackEvent> events) {
-        List<StoreTrackEvent> priceEvents = events.stream()
-                .filter(e -> "PRICE".equals(e.getEventCategory()))
-                .collect(java.util.stream.Collectors.toList());
+    private List<CommonRating> queryRatings(Integer storeId, Date startDate, Date endDate) {
+        try {
+            LambdaQueryWrapper<CommonRating> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(CommonRating::getBusinessId, storeId)
+                    .eq(CommonRating::getBusinessType, 1) // 商铺评价
+                    .ge(CommonRating::getCreatedTime, startDate)
+                    .lt(CommonRating::getCreatedTime, endDate)
+                    .eq(CommonRating::getDeleteFlag, 0);
+            return commonRatingMapper.selectList(wrapper);
+        } catch (Exception e) {
+            log.error("查询评价数据失败: storeId={}", storeId, e);
+            return Collections.emptyList();
+        }
+    }
+    
+    /**
+     * 计算店铺评分(平均分)
+     */
+    private Double calculateStoreScore(List<CommonRating> ratings) {
+        if (ratings == null || ratings.isEmpty()) {
+            return null;
+        }
         
-        // 按 targetId (priceId) 分组统计
-        Map<Integer, List<StoreTrackEvent>> eventsByPriceId = priceEvents.stream()
-                .filter(e -> e.getTargetId() != null)
-                .collect(java.util.stream.Collectors.groupingBy(StoreTrackEvent::getTargetId));
+        double sum = ratings.stream()
+                .filter(r -> r.getScore() != null)
+                .mapToDouble(CommonRating::getScore)
+                .sum();
+        long count = ratings.stream()
+                .filter(r -> r.getScore() != null)
+                .count();
         
-        List<Map<String, Object>> result = new java.util.ArrayList<>();
+        if (count == 0) {
+            return null;
+        }
         
-        for (Map.Entry<Integer, List<StoreTrackEvent>> entry : eventsByPriceId.entrySet()) {
-            Integer priceId = entry.getKey();
-            List<StoreTrackEvent> priceIdEvents = entry.getValue();
-            
-            Map<String, Object> priceData = new java.util.HashMap<>();
-            priceData.put("priceId", priceId);
-            
-            // 浏览量(PRICE_VIEW事件数量)
-            long viewCount = priceIdEvents.stream()
-                    .filter(e -> "PRICE_VIEW".equals(e.getEventType()))
-                    .count();
-            priceData.put("viewCount", (int) viewCount);
-            
-            // 访客数(去重后的用户ID数量)
-            long visitorCount = priceIdEvents.stream()
-                    .filter(e -> e.getUserId() != null)
-                    .map(StoreTrackEvent::getUserId)
-                    .distinct()
-                    .count();
-            priceData.put("visitorCount", (int) visitorCount);
-            
-            // 分享数(PRICE_SHARE事件数量)
-            long shareCount = priceIdEvents.stream()
-                    .filter(e -> "PRICE_SHARE".equals(e.getEventType()))
-                    .count();
-            priceData.put("shareCount", (int) shareCount);
+        double avg = sum / count;
+        return BigDecimal.valueOf(avg)
+                .setScale(1, RoundingMode.HALF_UP)
+                .doubleValue();
+    }
+    
+    /**
+     * 计算多维度评分(口味、环境、服务)
+     */
+    private Map<String, Double> calculateOtherScores(List<CommonRating> ratings) {
+        Map<String, Double> result = new HashMap<>();
+        Map<String, List<Double>> scoresMap = new HashMap<>();
+        
+        for (CommonRating rating : ratings) {
+            if (rating.getOtherScore() == null || rating.getOtherScore().trim().isEmpty()) {
+                continue;
+            }
             
-            result.add(priceData);
+            try {
+                JSONObject otherScoreJson = JSONObject.parseObject(rating.getOtherScore());
+                for (String key : otherScoreJson.keySet()) {
+                    Object value = otherScoreJson.get(key);
+                    if (value instanceof Number) {
+                        scoresMap.computeIfAbsent(key, k -> new ArrayList<>())
+                                .add(((Number) value).doubleValue());
+                    }
+                }
+            } catch (Exception e) {
+                log.debug("解析otherScore失败: {}", rating.getOtherScore(), e);
+            }
         }
         
-        // 按 viewCount 降序排列
-        result.sort((a, b) -> {
-            Integer viewCountA = (Integer) a.get("viewCount");
-            Integer viewCountB = (Integer) b.get("viewCount");
-            return viewCountB.compareTo(viewCountA);
-        });
+        // 计算每个维度的平均分
+        for (Map.Entry<String, List<Double>> entry : scoresMap.entrySet()) {
+            List<Double> scores = entry.getValue();
+            if (!scores.isEmpty()) {
+                double avg = scores.stream().mapToDouble(Double::doubleValue).sum() / scores.size();
+                result.put(entry.getKey(), BigDecimal.valueOf(avg)
+                        .setScale(1, RoundingMode.HALF_UP)
+                        .doubleValue());
+            }
+        }
         
         return result;
     }
     
     /**
-     * 统计指定事件类型的数量
+     * 计算差评申诉成功次数
      */
-    private long countByEventType(List<StoreTrackEvent> events, String eventType) {
-        return events.stream()
-                .filter(e -> eventType.equals(e.getEventType()))
-                .count();
+    private long calculateAppealSuccessCount(Integer storeId, Date startDate, Date endDate) {
+        try {
+            LambdaQueryWrapper<StoreCommentAppeal> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(StoreCommentAppeal::getStoreId, storeId)
+                    .eq(StoreCommentAppeal::getAppealStatus, 2) // 已同意
+                    .ge(StoreCommentAppeal::getCreatedTime, startDate)
+                    .lt(StoreCommentAppeal::getCreatedTime, endDate)
+                    .eq(StoreCommentAppeal::getDeleteFlag, 0);
+            return storeCommentAppealMapper.selectCount(wrapper);
+        } catch (Exception e) {
+            log.error("计算差评申诉成功次数失败: storeId={}", storeId, e);
+            return 0L;
+        }
+    }
+    
+    /**
+     * 计算价目表排名数据- 优化:使用数据库聚合查询
+     */
+    private List<Map<String, Object>> calculatePriceRankingData(Integer storeId, Date startDate, Date endDate) {
+        try {
+            StoreTrackEventMapper mapper = this.getBaseMapper();
+            
+            // 使用数据库聚合查询统计价目表排名数据(已按viewCount降序排列)
+            List<Map<String, Object>> result = mapper.calculatePriceRanking(storeId, startDate, endDate);
+            
+            // 转换数据类型,确保与文档格式一致
+            for (Map<String, Object> priceData : result) {
+                // priceId 保持原样
+                // viewCount, visitorCount, shareCount 转换为 Integer
+                Object viewCountObj = priceData.get("viewCount");
+                if (viewCountObj instanceof Number) {
+                    priceData.put("viewCount", ((Number) viewCountObj).intValue());
+                }
+                Object visitorCountObj = priceData.get("visitorCount");
+                if (visitorCountObj instanceof Number) {
+                    priceData.put("visitorCount", ((Number) visitorCountObj).intValue());
+                }
+                Object shareCountObj = priceData.get("shareCount");
+                if (shareCountObj instanceof Number) {
+                    priceData.put("shareCount", ((Number) shareCountObj).intValue());
+                }
+            }
+            
+            return result;
+        } catch (Exception e) {
+            log.error("计算价目表排名数据失败: storeId={}", storeId, e);
+            return new ArrayList<>();
+        }
     }
+
 }

+ 1 - 0
alien-util/pom.xml

@@ -381,6 +381,7 @@
                 <configuration>
                     <source>8</source>
                     <target>8</target>
+                    <parameters>true</parameters>
                 </configuration>
             </plugin>
         </plugins>

+ 52 - 52
alien-util/src/main/java/shop/alien/util/encryption/StandardAesUtil.java

@@ -1,52 +1,52 @@
-package shop.alien.util.encryption;
-
-import org.apache.commons.codec.binary.Base64;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.crypto.Cipher;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import java.nio.charset.StandardCharsets;
-
-/**
- * 标准 AES 加解密工具类
- * 模式:AES/CBC/PKCS5Padding
- * 编码:UTF-8
- */
-public class StandardAesUtil {
-
-    private static final Logger log = LoggerFactory.getLogger(StandardAesUtil.class);
-    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
-
-    public static String encrypt(String data, String key, String iv) {
-        try {
-            Cipher cipher = Cipher.getInstance(ALGORITHM);
-            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
-            IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
-            
-            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
-            byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
-            return Base64.encodeBase64String(encrypted);
-        } catch (Exception e) {
-            log.error("AES加密失败: {}", e.getMessage());
-            return null;
-        }
-    }
-
-    public static String decrypt(String base64Data, String key, String iv) {
-        try {
-            Cipher cipher = Cipher.getInstance(ALGORITHM);
-            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
-            IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
-            
-            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
-            byte[] decoded = Base64.decodeBase64(base64Data);
-            byte[] original = cipher.doFinal(decoded);
-            return new String(original, StandardCharsets.UTF_8);
-        } catch (Exception e) {
-            log.error("AES解密失败: {}", e.getMessage());
-            return null;
-        }
-    }
-}
+package shop.alien.util.encryption;
+
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 标准 AES 加解密工具类
+ * 模式:AES/CBC/PKCS5Padding
+ * 编码:UTF-8
+ */
+public class StandardAesUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(StandardAesUtil.class);
+    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
+
+    public static String encrypt(String data, String key, String iv) {
+        try {
+            Cipher cipher = Cipher.getInstance(ALGORITHM);
+            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
+            IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
+            
+            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
+            byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
+            return Base64.encodeBase64String(encrypted);
+        } catch (Exception e) {
+            log.error("AES加密失败: {}", e.getMessage());
+            return null;
+        }
+    }
+
+    public static String decrypt(String base64Data, String key, String iv) {
+        try {
+            Cipher cipher = Cipher.getInstance(ALGORITHM);
+            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
+            IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
+            
+            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
+            byte[] decoded = Base64.decodeBase64(base64Data);
+            byte[] original = cipher.doFinal(decoded);
+            return new String(original, StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            log.error("AES解密失败: {}", e.getMessage());
+            return null;
+        }
+    }
+}

+ 12 - 0
pom.xml

@@ -424,6 +424,18 @@
                     <artifactId>spring-boot-maven-plugin</artifactId>
                     <version>2.3.2.RELEASE</version>
                 </plugin>
+                <!-- Maven Compiler Plugin - 保留方法参数名,用于SpEL表达式解析 -->
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>3.8.1</version>
+                    <configuration>
+                        <source>1.8</source>
+                        <target>1.8</target>
+                        <encoding>UTF-8</encoding>
+                        <parameters>true</parameters>
+                    </configuration>
+                </plugin>
             </plugins>
         </pluginManagement>
     </build>