Explorar o código

fix:用户端埋点增加接口,优化统计数据方法

penghao hai 2 meses
pai
achega
1883e2f979

+ 1 - 0
alien-entity/pom.xml

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

+ 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")

+ 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")

+ 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"),

+ 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>