Explorar el Código

fix:websocket咨询、分享埋点添加优化,完善数据统计逻辑

penghao hace 2 meses
padre
commit
fb923f6f1c

+ 7 - 19
alien-entity/src/main/java/shop/alien/mapper/StoreTrackEventMapper.java

@@ -20,22 +20,20 @@ import java.util.Map;
 public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
     
     /**
-     * 统计访客数(去重userId)- 使用数据库聚合查询
+     * 统计访客数(去重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 " +
@@ -57,7 +55,7 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
                           @Param("endDate") Date endDate);
     
     /**
-     * 统计流量数据:搜索量、浏览量、访问时长 - 使用数据库聚合查询
+     * 统计流量数据:搜索量、浏览量、访问时长
      */
     @Select("SELECT " +
             "    SUM(CASE WHEN event_type = 'SEARCH' THEN 1 ELSE 0 END) as searchCount, " +
@@ -66,16 +64,14 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
             "    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统计浏览量、访客数、分享数 - 使用数据库聚合查询
+     * 按价目表ID统计浏览量、访客数、分享数
      */
     @Select("SELECT " +
             "    target_id as priceId, " +
@@ -84,7 +80,6 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
             "    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' " +
@@ -92,11 +87,10 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
             "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 " +
@@ -105,17 +99,15 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
             "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, " +
@@ -123,30 +115,26 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
             "    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);
 }

+ 29 - 21
alien-store/doc/埋点接口清单.md

@@ -7,53 +7,61 @@
 | 序号 | 接口路径 | HTTP方法 | 方法名 | 所在类 | 事件类型 |
 |------|---------|---------|--------|--------|---------|
 | 1 | `/aiSearch/search` | POST | `search` | `AiSearchController` | SEARCH |
-| 2 | `/store/info/getClientStoreDetail` | GET | `getClientStoreDetail` | `StoreInfoController` | VIEW |
-| 3 | `/userStore/getStoreDetailById` | GET | `getStoreDetailById` | `UserStoreController` | VIEW |
 
 ## 二、互动数据(INTERACTION)
 
 | 序号 | 接口路径 | HTTP方法 | 方法名 | 所在类 | 事件类型 |
 |------|---------|---------|--------|--------|---------|
-| 4 | `/collect/addCollect` | POST | `addCollect` | `LifeCollectController` | COLLECT |
-| 5 | `/storeClockIn/addStoreClockIn` | POST | `addStoreClockIn` | `StoreClockInController` | CHECKIN |
-| 6 | `/renovation/requirement/consultRequirement` | POST | `consultRequirement` | `StoreRenovationRequirementController` | CONSULT |
-| 7 | `/userDynamics/addOrUpdate` | POST | `addOrUpdate` | `LifeUserDynamicsController` | POST_PUBLISH |
-| 8 | `/comment/like` | POST | `like` | `LifeCommentController` | POST_LIKE |
-| 9 | `/commonComment/addComment` | POST | `addComment` | `CommonCommentController` | POST_COMMENT |
-| 10 | `/user-violation/reporting` | POST | `reporting` | `UserViolationController` | REPORT |
-| 11 | `/life-blacklist/blackList` | POST | `blackList` | `LifeBlacklistController` | BLOCK |
+| 2 | `/collect/addCollect` | POST | `addCollect` | `LifeCollectController` | COLLECT |
+| 3 | `/storeClockIn/addStoreClockIn` | POST | `addStoreClockIn` | `StoreClockInController` | CHECKIN |
+| 4 | `/renovation/requirement/consultRequirement` | POST | `consultRequirement` | `StoreRenovationRequirementController` | CONSULT |
+| 5 | `/userDynamics/addOrUpdate` | POST | `addOrUpdate` | `LifeUserDynamicsController` | POST_PUBLISH |
+| 6 | `/userDynamics/addTransferCount` | GET | `addTransferCount` | `LifeUserDynamicsController` | POST_REPOST |
+| 7 | `/comment/like` | POST | `like` | `LifeCommentController` | POST_LIKE |
+| 8 | `/commonComment/addComment` | POST | `addComment` | `CommonCommentController` | POST_COMMENT |
+| 9 | `/user-violation/reporting` | POST | `reporting` | `UserViolationController` | REPORT |
+| 10 | `/life-blacklist/blackList` | POST | `blackList` | `LifeBlacklistController` | BLOCK |
 
 ## 三、服务质量(SERVICE)
 
 | 序号 | 接口路径 | HTTP方法 | 方法名 | 所在类 | 事件类型 |
 |------|---------|---------|--------|--------|---------|
-| 12 | `/commonRating/addRating` | POST | `add` | `CommonRatingController` | RATING_ADD |
-| 13 | `/commentAppeal/submit` | POST | `submitAppeal` | `CommentAppealController` | APPEAL |
+| 11 | `/commonRating/addRating` | POST | `add` | `CommonRatingController` | RATING_ADD |
+| 12 | `/commentAppeal/submit` | POST | `submitAppeal` | `CommentAppealController` | APPEAL |
 
 ## 四、价目表(PRICE)
 
 | 序号 | 接口路径 | HTTP方法 | 方法名 | 所在类 | 事件类型 |
 |------|---------|---------|--------|--------|---------|
-| 14 | `/cuisine/getPage` | GET | `getPage` | `StoreCuisineController` | PRICE_VIEW |
-| 15 | `/price/getPage` | GET | `getPage` | `StorePriceController` | PRICE_VIEW |
+| 13 | `/store/price/getById` | GET | `getById` | `StorePriceController` | PRICE_VIEW |
+| 14 | `/store/cuisine/getByCuisineType` | GET | `getByCuisineType` | `StoreCuisineController` | PRICE_VIEW |
 
 ## 五、优惠券(COUPON)
 
 | 序号 | 接口路径 | HTTP方法 | 方法名 | 所在类 | 事件类型 |
 |------|---------|---------|--------|--------|---------|
-| 16 | `/life-discount-coupon-store-friend/setFriendCoupon` | POST | `setFriendCoupon` | `LifeDiscountCouponStoreFriendController` | COUPON_GIVE |
-| 17 | `/coupon/verify` | GET | `verify` | `LifeCouponController` | COUPON_USE |
+| 15 | `/life-discount-coupon-store-friend/setFriendCoupon` | POST | `setFriendCoupon` | `LifeDiscountCouponStoreFriendController` | COUPON_GIVE |
+| 16 | `/coupon/verify` | GET | `verify` | `LifeCouponController` | COUPON_USE |
+
+## 六、WebSocket埋点(非HTTP接口)
+
+| 序号 | 触发场景 | 消息类型 | 处理类 | 事件类型 | 说明 |
+|------|---------|---------|--------|---------|------|
+| 17 | 用户咨询店铺 | WebSocket消息 | `WebSocketProcess` | CONSULT | 当接收者是店铺(receiverId以store_开头)且是普通消息时触发 |
+| 18 | 用户分享店铺 | WebSocket消息(type="3"或"14") | `WebSocketProcess` | SHARE | 当消息类型为链接分享(type="3")或人员分享(type="14")时触发,从消息内容中解析storeId |
 
 ## 统计汇总
 
-- **总接口数**:17个
-- **流量数据**:3个
-- **互动数据**:8个
+- **总埋点数**:18个
+- **HTTP接口总数**:16个
+- **WebSocket埋点**:2个
+- **流量数据**:1个
+- **互动数据**:9个(包含2个WebSocket埋点)
 - **服务质量**:2个
 - **价目表**:2个
 - **优惠券**:2个
 
 ---
 
-**文档版本**:v1.1  
-**最后更新**:2026-01-14
+**文档版本**:v1.2  
+**最后更新**:2026-01-22

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

@@ -73,8 +73,8 @@ CREATE TABLE `store_track_statistics` (
 ```
 
 **字段说明**:
-- `searchCount`: 店铺搜索次数
-- `viewCount`: 店铺浏览次数
+- `searchCount`: 店铺搜索次数(埋点时间SEARCH该店铺的次数)
+- `viewCount`: 店铺浏览次数(埋点时间VIEW该店铺的次数)
 - `visitorCount`: 访客数(去重后的用户ID数量)
 - `newVisitorCount`: 新增访客数(在统计日期之前没有访问记录的用户)
 - `totalDuration`: 总访问时长(所有浏览事件duration字段的总和,单位:毫秒)
@@ -198,9 +198,9 @@ CREATE TABLE `store_track_statistics` (
 ```json
 {
   "storeScore": 4.5,              // 店铺评分(Double类型,0-5分)
-  "tasteScore": 4.3,              // 口味评分(Double类型,0-5分)
-  "environmentScore": 4.2,        // 环境评分(Double类型,0-5分)
-  "serviceScore": 4.4,            // 服务评分(Double类型,0-5分)
+  "scoreOne": 4.3,                // 评分1(Double类型,0-5分)
+  "scoreTwo": 4.2,                // 评分2(Double类型,0-5分)
+  "scoreThree": 4.4,              // 评分3(Double类型,0-5分)
   "ratingCount": 100,             // 评价数量(Long类型)
   "goodRatingCount": 60,          // 好评数量(score >= 4.5,Long类型)
   "midRatingCount": 30,           // 中评数量(3.0 <= score <= 4.0,Long类型)
@@ -213,11 +213,11 @@ CREATE TABLE `store_track_statistics` (
 ```
 
 **字段说明**:
-- `storeScore`: 店铺评分(从common_rating表统计,businessType=1,计算平均分)
-- `tasteScore`: 口味评分(从common_rating表的otherScore字段解析"口味"评分,计算平均分)
-- `environmentScore`: 环境评分(从common_rating表的otherScore字段解析"环境"评分,计算平均分)
-- `serviceScore`: 服务评分(从common_rating表的otherScore字段解析"服务"评分,计算平均分)
-- `ratingCount`: 评价数量(common_rating表中该店铺的评价总数)
+- `storeScore`: 店铺评分(从common_rating表查询score字段,businessType=1,计算平均分)
+- `scoreOne`: 评分1(从common_rating表的score_one字段,计算平均分)
+- `scoreTwo`: 评分2(从common_rating表的score_two字段,计算平均分)
+- `scoreThree`: 评分3(从common_rating表的score_three字段,计算平均分)
+- `ratingCount`: 评价数量(common_rating表中该店铺的累计评价总数)
 - `goodRatingCount`: 好评数量(score >= 4.5的评价数)
 - `midRatingCount`: 中评数量(3.0 <= score <= 4.0的评价数)
 - `badRatingCount`: 差评数量(0.5 <= score <= 2.5的评价数)
@@ -226,14 +226,7 @@ CREATE TABLE `store_track_statistics` (
 - `appealSuccessCount`: 差评申诉成功次数(从store_comment_appeal表统计,appealStatus=2已同意)
 - `appealSuccessPercent`: 差评申诉成功占比(appealSuccessCount / appealCount * 100)
 
-**otherScore字段格式示例**:
-```json
-{
-  "口味": 5.0,
-  "环境": 4.5,
-  "服务": 4.0
-}
-```
+**注意**:评价数据为累计数据,统计截止到统计日期的所有评价。
 
 ---
 

+ 3 - 3
alien-store/doc/埋点需求完整方案.md

@@ -57,9 +57,9 @@
 
 #### 1.1.5 服务质量数据
 - 店铺评分
-- 口味评分
-- 环境评分
-- 服务评分
+- 评分1
+- 评分2
+- 评分3
 - 评价数量
 - 好评数量
 - 中评数量

+ 53 - 0
alien-store/src/main/java/shop/alien/store/aspect/TrackEventAspect.java

@@ -400,6 +400,11 @@ public class TrackEventAspect {
             if ("StoreCuisineController".equals(className) && "getByCuisineType".equals(methodName)) {
                 return queryStoreIdForGetByCuisineType(context);
             }
+            
+            // 处理 /userDynamics/addTransferCount 接口(动态转发)
+            if ("LifeUserDynamicsController".equals(className) && "addTransferCount".equals(methodName)) {
+                return queryStoreIdForAddTransferCount(context);
+            }
         } catch (Exception e) {
             log.debug("根据业务逻辑查询店铺ID失败", e);
         }
@@ -686,6 +691,54 @@ public class TrackEventAspect {
         }
         return null;
     }
+    
+    /**
+     * 为动态转发接口查询店铺ID
+     */
+    private Integer queryStoreIdForAddTransferCount(EvaluationContext context) {
+        try {
+            // 获取id参数(动态ID)
+            Object idObj = context.lookupVariable("id");
+            if (idObj == null) {
+                return null;
+            }
+            
+            Integer dynamicsId = null;
+            if (idObj instanceof Integer) {
+                dynamicsId = (Integer) idObj;
+            } else if (idObj instanceof Number) {
+                dynamicsId = ((Number) idObj).intValue();
+            } else if (idObj instanceof String) {
+                try {
+                    dynamicsId = Integer.parseInt((String) idObj);
+                } catch (NumberFormatException e) {
+                    log.debug("无法将id转换为Integer: {}", idObj);
+                    return null;
+                }
+            }
+            
+            if (dynamicsId != null) {
+                // 通过动态ID查询LifeUserDynamics表获取phoneId
+                LifeUserDynamics dynamics = lifeUserDynamicsMapper.selectById(dynamicsId);
+                if (dynamics != null && dynamics.getPhoneId() != null) {
+                    // phoneId格式:store_手机号 或 user_手机号
+                    if (dynamics.getPhoneId().startsWith("store_")) {
+                        String phone = dynamics.getPhoneId().substring(6); // 去掉 "store_" 前缀
+                        LambdaQueryWrapper<StoreUser> wrapper = new LambdaQueryWrapper<>();
+                        wrapper.eq(StoreUser::getPhone, phone)
+                                .eq(StoreUser::getDeleteFlag, 0);
+                        StoreUser storeUser = storeUserMapper.selectOne(wrapper);
+                        if (storeUser != null && storeUser.getStoreId() != null) {
+                            return storeUser.getStoreId();
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.debug("查询动态转发接口的店铺ID失败", e);
+        }
+        return null;
+    }
 
     /**
      * 从JWT中获取用户ID

+ 80 - 0
alien-store/src/main/java/shop/alien/store/config/WebSocketConfig.java

@@ -5,6 +5,12 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.web.socket.config.annotation.EnableWebSocket;
 import org.springframework.web.socket.server.standard.ServerEndpointExporter;
 
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import java.util.List;
+import java.util.Map;
+
 /**
  * WebSocketConfig
  *
@@ -20,4 +26,78 @@ public class WebSocketConfig {
     public ServerEndpointExporter serverEndpointExporter() {
         return new ServerEndpointExporter();
     }
+    
+    /**
+     * WebSocket配置器,用于在握手时获取IP地址和User-Agent
+     */
+    public static class WebSocketConfigurator extends ServerEndpointConfig.Configurator {
+        @Override
+        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
+            Map<String, Object> userProperties = sec.getUserProperties();
+            Map<String, List<String>> headers = request.getHeaders();
+            
+            // 获取User-Agent
+            List<String> userAgentList = headers.get("User-Agent");
+            String userAgent = (userAgentList != null && !userAgentList.isEmpty()) ? userAgentList.get(0) : null;
+            userProperties.put("userAgent", userAgent);
+            
+            // 获取IP地址(从请求头中获取)
+            String ipAddress = getIpAddress(headers);
+            userProperties.put("ipAddress", ipAddress);
+        }
+        
+        /**
+         * 从请求头中获取客户端IP地址
+         */
+        private String getIpAddress(Map<String, List<String>> headers) {
+            String ip = null;
+            
+            // 尝试从各种请求头中获取IP
+            if (headers.containsKey("X-Forwarded-For")) {
+                List<String> xffList = headers.get("X-Forwarded-For");
+                if (xffList != null && !xffList.isEmpty()) {
+                    ip = xffList.get(0);
+                }
+            }
+            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+                if (headers.containsKey("Proxy-Client-IP")) {
+                    List<String> proxyList = headers.get("Proxy-Client-IP");
+                    if (proxyList != null && !proxyList.isEmpty()) {
+                        ip = proxyList.get(0);
+                    }
+                }
+            }
+            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+                if (headers.containsKey("WL-Proxy-Client-IP")) {
+                    List<String> wlProxyList = headers.get("WL-Proxy-Client-IP");
+                    if (wlProxyList != null && !wlProxyList.isEmpty()) {
+                        ip = wlProxyList.get(0);
+                    }
+                }
+            }
+            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+                if (headers.containsKey("HTTP_CLIENT_IP")) {
+                    List<String> httpClientList = headers.get("HTTP_CLIENT_IP");
+                    if (httpClientList != null && !httpClientList.isEmpty()) {
+                        ip = httpClientList.get(0);
+                    }
+                }
+            }
+            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+                if (headers.containsKey("HTTP_X_FORWARDED_FOR")) {
+                    List<String> httpXffList = headers.get("HTTP_X_FORWARDED_FOR");
+                    if (httpXffList != null && !httpXffList.isEmpty()) {
+                        ip = httpXffList.get(0);
+                    }
+                }
+            }
+            
+            // 如果是多级代理,取第一个IP
+            if (ip != null && ip.contains(",")) {
+                ip = ip.split(",")[0].trim();
+            }
+            
+            return ip;
+        }
+    }
 }

+ 242 - 4
alien-store/src/main/java/shop/alien/store/config/WebSocketProcess.java

@@ -11,17 +11,26 @@ import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
 import shop.alien.entity.store.LifeBlacklist;
 import shop.alien.entity.store.LifeMessage;
+import shop.alien.entity.store.LifeUser;
+import shop.alien.entity.store.StoreTrackEvent;
+import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.LifeBlacklistMapper;
 import shop.alien.mapper.LifeMessageMapper;
+import shop.alien.mapper.LifeUserMapper;
+import shop.alien.mapper.StoreUserMapper;
+import shop.alien.store.config.BaseRedisService;
+import shop.alien.store.util.UserAgentParserUtil;
 import shop.alien.util.common.safe.ImageReviewServiceEnum;
 import shop.alien.util.common.safe.TextModerationResultVO;
 import shop.alien.util.common.safe.TextModerationUtil;
 import shop.alien.util.common.safe.TextReviewServiceEnum;
+import com.fasterxml.jackson.databind.ObjectMapper;
 
 import javax.websocket.*;
 import javax.websocket.server.PathParam;
 import javax.websocket.server.ServerEndpoint;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -35,7 +44,7 @@ import java.util.stream.Collectors;
  */
 @Slf4j
 @Component
-@ServerEndpoint(value = "/socket/{sendId}")
+@ServerEndpoint(value = "/socket/{sendId}", configurator = WebSocketConfig.WebSocketConfigurator.class)
 public class WebSocketProcess implements ApplicationContextAware {
 
     private static ApplicationContext applicationContext;
@@ -43,10 +52,19 @@ public class WebSocketProcess implements ApplicationContextAware {
     private static LifeMessageMapper lifeMessageMapper;
     private static BaseRedisService baseRedisService;
     private static TextModerationUtil textModerationUtil;
+    private static StoreUserMapper storeUserMapper;
+    private static LifeUserMapper lifeUserMapper;
+    private static ObjectMapper objectMapper;
     /*
      * 持有每个webSocket对象,以key-value存储到线程安全ConcurrentHashMap,
      */
     private static final ConcurrentHashMap<String, WebSocketProcess> concurrentHashMap = new ConcurrentHashMap<>(12);
+    
+    /**
+     * 存储每个连接的IP地址和User-Agent信息
+     * key: sendId, value: Map包含ipAddress和userAgent
+     */
+    private static final ConcurrentHashMap<String, Map<String, String>> connectionInfoMap = new ConcurrentHashMap<>(12);
 
     @Override
     public void setApplicationContext(ApplicationContext context) {
@@ -55,12 +73,25 @@ public class WebSocketProcess implements ApplicationContextAware {
         WebSocketProcess.lifeMessageMapper = context.getBean(LifeMessageMapper.class);
         WebSocketProcess.baseRedisService = context.getBean(BaseRedisService.class);
         WebSocketProcess.textModerationUtil = context.getBean(TextModerationUtil.class);
+        WebSocketProcess.storeUserMapper = context.getBean(StoreUserMapper.class);
+        WebSocketProcess.lifeUserMapper = context.getBean(LifeUserMapper.class);
+        WebSocketProcess.objectMapper = context.getBean(ObjectMapper.class);
     }
 
     /**
      * 会话对象
      **/
     private Session session;
+    
+    /**
+     * 当前连接的IP地址
+     */
+    private String ipAddress;
+    
+    /**
+     * 当前连接的User-Agent
+     */
+    private String userAgent;
 
     /**
      * 接收到客户端消息时触发
@@ -121,6 +152,21 @@ public class WebSocketProcess implements ApplicationContextAware {
             }
 
             lifeMessageMapper.insert(lifeMessage);
+            
+            // 记录咨询埋点:如果接收者是店铺(receiverId以store_开头),且是普通消息(不是heartbeat、receipt、position、notice)
+            String category = webSocketVo.getCategory();
+            if (webSocketVo.getReceiverId() != null && webSocketVo.getReceiverId().startsWith("store_") 
+                    && category != null && !"heartbeat".equals(category) && !"receipt".equals(category) 
+                    && !"position".equals(category) && !"notice".equals(category)) {
+                recordConsultEvent(webSocketVo, id);
+            }
+            
+            // 记录店铺分享埋点:消息类型是分享(type="3"链接分享 或 type="14"人员分享)
+            String messageType = webSocketVo.getType();
+            if ("3".equals(messageType) || "14".equals(messageType)) {
+                recordShareEvent(webSocketVo, id);
+            }
+            
             // 发送消息
             webSocketVo.setMessageId(lifeMessage.getId());
             webSocketVo.setCategory("message");
@@ -165,7 +211,19 @@ public class WebSocketProcess implements ApplicationContextAware {
             //每新建立一个连接,就把当前客户id为key,this为value存储到map中
             this.session = session;
             concurrentHashMap.put(id, this);
-            log.info("WebSocketProcess.onOpen() Open a websocket. id={}", id);
+            
+            // 从Session的UserProperties中获取IP地址和User-Agent(由WebSocketConfigurator在握手时设置)
+            Map<String, Object> userProperties = session.getUserProperties();
+            this.ipAddress = (String) userProperties.get("ipAddress");
+            this.userAgent = (String) userProperties.get("userAgent");
+            
+            // 保存连接信息到Map中
+            Map<String, String> connectionInfo = new HashMap<>();
+            connectionInfo.put("ipAddress", this.ipAddress);
+            connectionInfo.put("userAgent", this.userAgent);
+            connectionInfoMap.put(id, connectionInfo);
+            
+            log.info("WebSocketProcess.onOpen() Open a websocket. id={}, ipAddress={}, userAgent={}", id, this.ipAddress, this.userAgent);
 
             // 获取拉黑自己的用户信息
             LambdaQueryWrapper<LifeBlacklist> wrapper = new LambdaQueryWrapper<>();
@@ -244,7 +302,187 @@ public class WebSocketProcess implements ApplicationContextAware {
             }
         }
     }
-
-
+    
+    /**
+     * 记录咨询埋点事件
+     */
+    private void recordConsultEvent(WebSocketVo webSocketVo, String sendId) {
+        try {
+            // 从receiverId(格式:store_手机号)获取店铺ID
+            Integer storeId = null;
+            if (webSocketVo.getReceiverId() != null && webSocketVo.getReceiverId().startsWith("store_")) {
+                String phone = webSocketVo.getReceiverId().substring(6); // 去掉 "store_" 前缀
+                LambdaQueryWrapper<StoreUser> wrapper = new LambdaQueryWrapper<>();
+                wrapper.eq(StoreUser::getPhone, phone)
+                        .eq(StoreUser::getDeleteFlag, 0);
+                StoreUser storeUser = storeUserMapper.selectOne(wrapper);
+                if (storeUser != null && storeUser.getStoreId() != null) {
+                    storeId = storeUser.getStoreId();
+                }
+            }
+            
+            // 从senderId(格式:user_手机号)获取用户ID
+            Integer userId = null;
+            if (webSocketVo.getSenderId() != null && webSocketVo.getSenderId().startsWith("user_")) {
+                String phone = webSocketVo.getSenderId().substring(5); // 去掉 "user_" 前缀
+                LambdaQueryWrapper<LifeUser> wrapper = new LambdaQueryWrapper<>();
+                wrapper.eq(LifeUser::getUserPhone, phone)
+                        .eq(LifeUser::getDeleteFlag, 0);
+                LifeUser lifeUser = lifeUserMapper.selectOne(wrapper);
+                if (lifeUser != null && lifeUser.getId() != null) {
+                    userId = lifeUser.getId();
+                }
+            }
+            
+            // 创建咨询埋点事件
+            if (storeId != null) {
+                StoreTrackEvent trackEvent = new StoreTrackEvent();
+                trackEvent.setEventType("CONSULT");
+                trackEvent.setEventCategory("INTERACTION");
+                trackEvent.setStoreId(storeId);
+                trackEvent.setUserId(userId);
+                trackEvent.setTargetId(storeId);
+                trackEvent.setTargetType("STORE");
+                trackEvent.setEventTime(new java.util.Date());
+                
+                // 获取IP地址、User-Agent和设备类型
+                Map<String, String> connectionInfo = connectionInfoMap.get(sendId);
+                if (connectionInfo != null) {
+                    trackEvent.setIpAddress(connectionInfo.get("ipAddress"));
+                    trackEvent.setUserAgent(connectionInfo.get("userAgent"));
+                    trackEvent.setDeviceType(UserAgentParserUtil.parseDeviceType(connectionInfo.get("userAgent")));
+                } else {
+                    // 如果Map中没有,尝试从当前实例获取
+                    trackEvent.setIpAddress(this.ipAddress);
+                    trackEvent.setUserAgent(this.userAgent);
+                    trackEvent.setDeviceType(UserAgentParserUtil.parseDeviceType(this.userAgent));
+                }
+                
+                // 异步写入Redis List
+                String eventJson = objectMapper.writeValueAsString(trackEvent);
+                baseRedisService.setListRight("track:event:queue", eventJson);
+                
+                log.debug("WebSocket咨询埋点记录成功: storeId={}, userId={}, ipAddress={}, userAgent={}", 
+                        storeId, userId, trackEvent.getIpAddress(), trackEvent.getUserAgent());
+            } else {
+                log.warn("WebSocket咨询埋点记录失败: 无法获取storeId, receiverId={}", webSocketVo.getReceiverId());
+            }
+        } catch (Exception e) {
+            log.error("WebSocket咨询埋点记录异常: receiverId={}, senderId={}", 
+                    webSocketVo.getReceiverId(), webSocketVo.getSenderId(), e);
+        }
+    }
+    
+    /**
+     * 记录店铺分享埋点事件
+     */
+    private void recordShareEvent(WebSocketVo webSocketVo, String sendId) {
+        try {
+            Integer storeId = null;
+            Integer userId = null;
+            
+            // 从消息内容中解析storeId(消息格式:{"sendType":"shopShare","url":"{...storeId:256...}"})
+            try {
+                String text = webSocketVo.getText();
+                if (text != null) {
+                    // 解析第一层JSON
+                    JSONObject textJson = JSONObject.parseObject(text);
+                    String sendType = textJson.getString("sendType");
+                    
+                    // 如果是店铺分享
+                    if ("shopShare".equals(sendType)) {
+                        String urlStr = textJson.getString("url");
+                        if (urlStr != null) {
+                            // 解析url字段(也是JSON字符串)
+                            JSONObject urlJson = JSONObject.parseObject(urlStr);
+                            Object storeIdObj = urlJson.get("storeId");
+                            if (storeIdObj != null) {
+                                if (storeIdObj instanceof Integer) {
+                                    storeId = (Integer) storeIdObj;
+                                } else if (storeIdObj instanceof Number) {
+                                    storeId = ((Number) storeIdObj).intValue();
+                                } else if (storeIdObj instanceof String) {
+                                    try {
+                                        storeId = Integer.parseInt((String) storeIdObj);
+                                    } catch (NumberFormatException e) {
+                                        log.debug("无法将storeId转换为Integer: {}", storeIdObj);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.debug("解析店铺分享消息内容失败: text={}", webSocketVo.getText(), e);
+            }
+            
+            // 从senderId获取用户ID
+            if (webSocketVo.getSenderId() != null) {
+                if (webSocketVo.getSenderId().startsWith("user_")) {
+                    // 普通用户分享
+                    String phone = webSocketVo.getSenderId().substring(5); // 去掉 "user_" 前缀
+                    LambdaQueryWrapper<LifeUser> wrapper = new LambdaQueryWrapper<>();
+                    wrapper.eq(LifeUser::getUserPhone, phone)
+                            .eq(LifeUser::getDeleteFlag, 0);
+                    LifeUser lifeUser = lifeUserMapper.selectOne(wrapper);
+                    if (lifeUser != null && lifeUser.getId() != null) {
+                        userId = lifeUser.getId();
+                    }
+                } else if (webSocketVo.getSenderId().startsWith("store_")) {
+                    // 店铺分享(备用逻辑)
+                    String phone = webSocketVo.getSenderId().substring(6); // 去掉 "store_" 前缀
+                    LambdaQueryWrapper<StoreUser> wrapper = new LambdaQueryWrapper<>();
+                    wrapper.eq(StoreUser::getPhone, phone)
+                            .eq(StoreUser::getDeleteFlag, 0);
+                    StoreUser storeUser = storeUserMapper.selectOne(wrapper);
+                    if (storeUser != null) {
+                        if (storeUser.getStoreId() != null && storeId == null) {
+                            storeId = storeUser.getStoreId();
+                        }
+                        if (storeUser.getId() != null) {
+                            userId = storeUser.getId();
+                        }
+                    }
+                }
+            }
+            
+            // 创建店铺分享埋点事件
+            if (storeId != null) {
+                StoreTrackEvent trackEvent = new StoreTrackEvent();
+                trackEvent.setEventType("SHARE");
+                trackEvent.setEventCategory("INTERACTION");
+                trackEvent.setStoreId(storeId);
+                trackEvent.setUserId(userId);
+                trackEvent.setTargetId(storeId);
+                trackEvent.setTargetType("STORE");
+                trackEvent.setEventTime(new java.util.Date());
+                
+                // 获取IP地址、User-Agent和设备类型
+                Map<String, String> connectionInfo = connectionInfoMap.get(sendId);
+                if (connectionInfo != null) {
+                    trackEvent.setIpAddress(connectionInfo.get("ipAddress"));
+                    trackEvent.setUserAgent(connectionInfo.get("userAgent"));
+                    trackEvent.setDeviceType(UserAgentParserUtil.parseDeviceType(connectionInfo.get("userAgent")));
+                } else {
+                    // 如果Map中没有,尝试从当前实例获取
+                    trackEvent.setIpAddress(this.ipAddress);
+                    trackEvent.setUserAgent(this.userAgent);
+                    trackEvent.setDeviceType(UserAgentParserUtil.parseDeviceType(this.userAgent));
+                }
+                
+                // 异步写入Redis List
+                String eventJson = objectMapper.writeValueAsString(trackEvent);
+                baseRedisService.setListRight("track:event:queue", eventJson);
+                
+                log.debug("WebSocket店铺分享埋点记录成功: storeId={}, userId={}, ipAddress={}, userAgent={}", 
+                        storeId, userId, trackEvent.getIpAddress(), trackEvent.getUserAgent());
+            } else {
+                log.warn("WebSocket店铺分享埋点记录失败: 无法获取storeId, senderId={}", webSocketVo.getSenderId());
+            }
+        } catch (Exception e) {
+            log.error("WebSocket店铺分享埋点记录异常: senderId={}, type={}", 
+                    webSocketVo.getSenderId(), webSocketVo.getType(), e);
+        }
+    }
 
 }

+ 7 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeUserDynamicsController.java

@@ -267,6 +267,13 @@ public class LifeUserDynamicsController {
      * @param id 动态id
      * @return 操作结果
      */
+    @TrackEvent(
+            eventType = "POST_REPOST",
+            eventCategory = "INTERACTION",
+            storeId = "",
+            targetId = "#{#id}",
+            targetType = "POST"
+    )
     @ApiOperation("动态被转发次数+1")
     @ApiOperationSupport(order = 9)
     @GetMapping("addTransferCount")

+ 72 - 92
alien-store/src/main/java/shop/alien/store/service/impl/TrackEventServiceImpl.java

@@ -1,6 +1,5 @@
 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;
@@ -196,8 +195,8 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
         try {
             StoreTrackEventMapper mapper = this.getBaseMapper();
         
-            // 使用数据库聚合查询统计流量数据
-            Map<String, Object> trafficStats = mapper.calculateTrafficStatistics(storeId, startDate, endDate);
+            // 使用数据库聚合查询统计流量数据(累计数据,截至到endDate)
+            Map<String, Object> trafficStats = mapper.calculateTrafficStatistics(storeId, endDate);
         
         // 搜索量
             Object searchCountObj = trafficStats.get("searchCount");
@@ -209,11 +208,11 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             long viewCount = viewCountObj != null ? ((Number) viewCountObj).longValue() : 0L;
         result.put("viewCount", viewCount);
             
-            // 访客数(去重userId)- 使用数据库聚合查询
-            Long visitorCount = mapper.countDistinctVisitors(storeId, startDate, endDate);
+            // 访客数(去重userId)- 累计数据,截至到endDate
+            Long visitorCount = mapper.countDistinctVisitors(storeId, endDate);
             result.put("visitorCount", visitorCount != null ? visitorCount : 0L);
             
-            // 新增访客数 - 使用数据库聚合查询
+            // 新增访客数(统计时间段内首次访问的用户,即在startDate之前没有访问记录的用户)
             Long newVisitorCount = mapper.countNewVisitors(storeId, startDate, endDate);
             result.put("newVisitorCount", newVisitorCount != null ? newVisitorCount : 0L);
             
@@ -252,8 +251,8 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
         try {
             StoreTrackEventMapper mapper = this.getBaseMapper();
             
-            // 使用数据库聚合查询统计互动事件(从埋点事件表store_track_event中统计
-            List<Map<String, Object>> interactionStats = mapper.calculateInteractionStatistics(storeId, startDate, endDate);
+            // 使用数据库聚合查询统计互动事件(累计数据,截至到endDate
+            List<Map<String, Object>> interactionStats = mapper.calculateInteractionStatistics(storeId, endDate);
             
             // 将查询结果转换为Map,便于查找
             // 注意:SQL返回的字段名是下划线命名(event_type),而非驼峰命名
@@ -443,9 +442,9 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
         Map<String, Object> result = new HashMap<>();
         
         try {
-            // 赠送好友相关数据(从life_discount_coupon_user表统计,关联店铺的优惠券,type=1优惠券
+            // 赠送好友相关数据(累计数据,从life_discount_coupon_user表统计)
             String storeIdStr = String.valueOf(storeId);
-            List<LifeDiscountCouponUser> couponUsers = queryCouponUsersByStore(storeIdStr, startDate, endDate);
+            List<LifeDiscountCouponUser> couponUsers = queryCouponUsersByStore(storeIdStr, endDate);
             
             // 赠送好友数量
             long giveToFriendCount = couponUsers.size();
@@ -486,8 +485,8 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             }
             result.put("giveToFriendUseAmountPercent", giveToFriendUseAmountPercent);
             
-            // 好友赠送相关数据(从life_discount_coupon_store_friend表统计,type=1优惠券)
-            List<LifeDiscountCouponStoreFriend> friendCoupons = queryFriendCouponsByStore(storeId, startDate, endDate, 1);
+            // 好友赠送相关数据(累计数据,从life_discount_coupon_store_friend表统计,type=1优惠券)
+            List<LifeDiscountCouponStoreFriend> friendCoupons = queryFriendCouponsByStore(storeId, endDate, 1);
             
             // 好友赠送数量
             long friendGiveCount = friendCoupons.size();
@@ -502,13 +501,12 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
                     .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);
+            // 好友赠送使用数量(累计数据)
+            long friendGiveUseCount = calculateFriendCouponUseCount(friendCoupons, endDate);
             result.put("friendGiveUseCount", friendGiveUseCount);
             
-            // 好友赠送使用金额合计(已使用的优惠券面值总和
-            BigDecimal friendGiveUseAmount = calculateFriendCouponUseAmount(friendCoupons, startDate, endDate);
+            // 好友赠送使用金额合计(累计数据
+            BigDecimal friendGiveUseAmount = calculateFriendCouponUseAmount(friendCoupons, endDate);
             result.put("friendGiveUseAmount", friendGiveUseAmount);
             
             // 好友赠送使用金额占比
@@ -539,9 +537,9 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     }
     
     /**
-     * 查询店铺关联的优惠券用户数据
+     * 查询店铺关联的优惠券用户数据(累计数据,截至到endDate)
      */
-    private List<LifeDiscountCouponUser> queryCouponUsersByStore(String storeId, Date startDate, Date endDate) {
+    private List<LifeDiscountCouponUser> queryCouponUsersByStore(String storeId, Date endDate) {
         try {
             // 先查询店铺的优惠券(type=1优惠券)
             LambdaQueryWrapper<LifeDiscountCoupon> couponWrapper = new LambdaQueryWrapper<>();
@@ -558,10 +556,9 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
                     .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);
@@ -572,9 +569,9 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     }
     
     /**
-     * 查询好友赠送的优惠券/代金券数据
+     * 查询好友赠送的优惠券/代金券数据(累计数据,截至到endDate)
      */
-    private List<LifeDiscountCouponStoreFriend> queryFriendCouponsByStore(Integer storeId, Date startDate, Date endDate, Integer type) {
+    private List<LifeDiscountCouponStoreFriend> queryFriendCouponsByStore(Integer storeId, Date endDate, Integer type) {
         try {
             // 先查询店铺用户
             LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
@@ -590,11 +587,9 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
                     .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);
@@ -613,9 +608,9 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     }
     
     /**
-     * 计算好友赠送优惠券使用数量
+     * 计算好友赠送优惠券使用数量(累计数据,截至到endDate)
      */
-    private long calculateFriendCouponUseCount(List<LifeDiscountCouponStoreFriend> friendCoupons, Date startDate, Date endDate) {
+    private long calculateFriendCouponUseCount(List<LifeDiscountCouponStoreFriend> friendCoupons, Date endDate) {
         if (friendCoupons == null || friendCoupons.isEmpty()) {
             return 0L;
         }
@@ -629,7 +624,6 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             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);
@@ -640,9 +634,9 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     }
     
     /**
-     * 计算好友赠送优惠券使用金额
+     * 计算好友赠送优惠券使用金额(累计数据,截至到endDate)
      */
-    private BigDecimal calculateFriendCouponUseAmount(List<LifeDiscountCouponStoreFriend> friendCoupons, Date startDate, Date endDate) {
+    private BigDecimal calculateFriendCouponUseAmount(List<LifeDiscountCouponStoreFriend> friendCoupons, Date endDate) {
         if (friendCoupons == null || friendCoupons.isEmpty()) {
             return BigDecimal.ZERO;
         }
@@ -656,7 +650,6 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             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);
@@ -682,8 +675,8 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
         try {
             StoreTrackEventMapper mapper = this.getBaseMapper();
             
-            // 使用数据库聚合查询统计代金券事件
-            List<Map<String, Object>> voucherStats = mapper.calculateVoucherStatistics(storeId, startDate, endDate);
+            // 使用数据库聚合查询统计代金券事件(累计数据,截至到endDate)
+            List<Map<String, Object>> voucherStats = mapper.calculateVoucherStatistics(storeId, endDate);
             
             // 将查询结果转换为Map,便于查找
             // 注意:SQL返回的字段名是下划线命名(event_type),而非驼峰命名
@@ -729,8 +722,8 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             }
             result.put("giveToFriendUseAmountPercent", giveToFriendUseAmountPercent);
             
-            // 好友赠送代金券相关数据(从life_discount_coupon_store_friend表统计,type=2代金券)
-            List<LifeDiscountCouponStoreFriend> friendVouchers = queryFriendCouponsByStore(storeId, startDate, endDate, 2); // type=2代金券
+            // 好友赠送代金券相关数据(累计数据,type=2代金券)
+            List<LifeDiscountCouponStoreFriend> friendVouchers = queryFriendCouponsByStore(storeId, endDate, 2);
             
             // 好友赠送数量
             long friendGiveCount = friendVouchers.size();
@@ -745,12 +738,12 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
                     .reduce(BigDecimal.ZERO, BigDecimal::add);
             result.put("friendGiveAmount", friendGiveAmount);
             
-            // 好友赠送使用数量(已使用的数量
-            long friendGiveUseCount = calculateFriendCouponUseCount(friendVouchers, startDate, endDate);
+            // 好友赠送使用数量(累计数据
+            long friendGiveUseCount = calculateFriendCouponUseCount(friendVouchers, endDate);
             result.put("friendGiveUseCount", friendGiveUseCount);
             
-            // 好友赠送使用金额合计(已使用的代金券面值总和
-            BigDecimal friendGiveUseAmount = calculateFriendCouponUseAmount(friendVouchers, startDate, endDate);
+            // 好友赠送使用金额合计(累计数据
+            BigDecimal friendGiveUseAmount = calculateFriendCouponUseAmount(friendVouchers, endDate);
             result.put("friendGiveUseAmount", friendGiveUseAmount);
             
             // 好友赠送使用金额占比
@@ -789,11 +782,10 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
         try {
             StoreTrackEventMapper mapper = this.getBaseMapper();
             
-            // 使用数据库聚合查询统计服务事件
-            List<Map<String, Object>> serviceStats = mapper.calculateServiceStatistics(storeId, startDate, endDate);
+            // 使用数据库聚合查询统计服务事件(累计数据,截至到endDate)
+            List<Map<String, Object>> serviceStats = mapper.calculateServiceStatistics(storeId, endDate);
             
             // 将查询结果转换为Map,便于查找
-            // 注意:SQL返回的字段名是下划线命名(event_type),而非驼峰命名
             Map<String, Long> eventTypeCountMap = new HashMap<>();
             for (Map<String, Object> stat : serviceStats) {
                 String eventType = (String) stat.get("event_type");
@@ -815,11 +807,13 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             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));
+            // 评分1、评分2、评分3(直接从common_rating表的scoreOne/scoreTwo/scoreThree字段获取)
+            Double scoreOne = calculateScoreAverage(ratings, CommonRating::getScoreOne);
+            Double scoreTwo = calculateScoreAverage(ratings, CommonRating::getScoreTwo);
+            Double scoreThree = calculateScoreAverage(ratings, CommonRating::getScoreThree);
+            result.put("scoreOne", scoreOne);
+            result.put("scoreTwo", scoreTwo);
+            result.put("scoreThree", scoreThree);
             
             // 评价数量
             long ratingCount = ratings.size();
@@ -853,8 +847,8 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             }
             result.put("badRatingPercent", badRatingPercent);
             
-            // 差评申诉成功次数
-            long appealSuccessCount = calculateAppealSuccessCount(storeId, startDate, endDate);
+            // 差评申诉成功次数(累计数据)
+            long appealSuccessCount = calculateAppealSuccessCount(storeId, endDate);
             result.put("appealSuccessCount", appealSuccessCount);
             
             // 差评申诉成功占比
@@ -870,9 +864,9 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             log.error("计算服务质量数据失败: storeId={}", storeId, e);
             // 返回默认值,避免统计失败
             result.put("storeScore", null);
-            result.put("tasteScore", null);
-            result.put("environmentScore", null);
-            result.put("serviceScore", null);
+            result.put("scoreOne", null);
+            result.put("scoreTwo", null);
+            result.put("scoreThree", null);
             result.put("ratingCount", 0L);
             result.put("goodRatingCount", 0L);
             result.put("midRatingCount", 0L);
@@ -894,8 +888,7 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             LambdaQueryWrapper<CommonRating> wrapper = new LambdaQueryWrapper<>();
             wrapper.eq(CommonRating::getBusinessId, storeId)
                     .eq(CommonRating::getBusinessType, 1) // 商铺评价
-                    .ge(CommonRating::getCreatedTime, startDate)
-                    .lt(CommonRating::getCreatedTime, endDate)
+                    .lt(CommonRating::getCreatedTime, endDate) // 累计数据,只限制截止日期
                     .eq(CommonRating::getDeleteFlag, 0);
             return commonRatingMapper.selectList(wrapper);
         } catch (Exception e) {
@@ -931,54 +924,41 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     }
     
     /**
-     * 计算多维度评分(口味、环境、服务)
+     * 计算单个评分字段的平均分
      */
-    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;
-            }
-            
-            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);
-            }
+    private Double calculateScoreAverage(List<CommonRating> ratings, java.util.function.Function<CommonRating, Double> scoreExtractor) {
+        if (ratings == null || ratings.isEmpty()) {
+            return null;
         }
         
-        // 计算每个维度的平均分
-        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());
-            }
+        double sum = ratings.stream()
+                .map(scoreExtractor)
+                .filter(score -> score != null)
+                .mapToDouble(Double::doubleValue)
+                .sum();
+        long count = ratings.stream()
+                .map(scoreExtractor)
+                .filter(score -> score != null)
+                .count();
+        
+        if (count == 0) {
+            return null;
         }
         
-        return result;
+        double avg = sum / count;
+        return BigDecimal.valueOf(avg)
+                .setScale(1, RoundingMode.HALF_UP)
+                .doubleValue();
     }
     
     /**
-     * 计算差评申诉成功次数
+     * 计算差评申诉成功次数(累计数据,截至到endDate)
      */
-    private long calculateAppealSuccessCount(Integer storeId, Date startDate, Date endDate) {
+    private long calculateAppealSuccessCount(Integer storeId, 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);
@@ -995,8 +975,8 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
         try {
             StoreTrackEventMapper mapper = this.getBaseMapper();
             
-            // 使用数据库聚合查询统计价目表排名数据(已按viewCount降序排列
-            List<Map<String, Object>> result = mapper.calculatePriceRanking(storeId, startDate, endDate);
+            // 使用数据库聚合查询统计价目表排名数据(累计数据,截至到endDate
+            List<Map<String, Object>> result = mapper.calculatePriceRanking(storeId, endDate);
             
             // 转换数据类型,确保与文档格式一致
             for (Map<String, Object> priceData : result) {