Bladeren bron

feat:用户端埋点

penghao 3 maanden geleden
bovenliggende
commit
af7d7204ff

+ 95 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreTrackEvent.java

@@ -0,0 +1,95 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 埋点事件实体类
+ *
+ * @author system
+ * @since 2026-01-14
+ */
+@Data
+@JsonInclude
+@TableName("store_track_event")
+@ApiModel(value = "StoreTrackEvent对象", description = "埋点事件表")
+public class StoreTrackEvent {
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "事件类型")
+    @TableField("event_type")
+    private String eventType;
+
+    @ApiModelProperty(value = "事件分类")
+    @TableField("event_category")
+    private String eventCategory;
+
+    @ApiModelProperty(value = "用户ID")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "店铺ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "目标对象ID")
+    @TableField("target_id")
+    private Integer targetId;
+
+    @ApiModelProperty(value = "目标对象类型")
+    @TableField("target_type")
+    private String targetType;
+
+    @ApiModelProperty(value = "事件附加数据(JSON格式)")
+    @TableField("event_data")
+    private String eventData;
+
+    @ApiModelProperty(value = "金额")
+    @TableField("amount")
+    private BigDecimal amount;
+
+    @ApiModelProperty(value = "时长(毫秒)")
+    @TableField("duration")
+    private Long duration;
+
+    @ApiModelProperty(value = "IP地址")
+    @TableField("ip_address")
+    private String ipAddress;
+
+    @ApiModelProperty(value = "用户代理")
+    @TableField("user_agent")
+    private String userAgent;
+
+    @ApiModelProperty(value = "设备类型")
+    @TableField("device_type")
+    private String deviceType;
+
+    @ApiModelProperty(value = "APP版本号")
+    @TableField("app_version")
+    private String appVersion;
+
+    @ApiModelProperty(value = "事件发生时间")
+    @TableField("event_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date eventTime;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+}

+ 74 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreTrackStatistics.java

@@ -0,0 +1,74 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 埋点统计数据实体类
+ *
+ * @author system
+ * @since 2026-01-14
+ */
+@Data
+@JsonInclude
+@TableName("store_track_statistics")
+@ApiModel(value = "StoreTrackStatistics对象", description = "埋点统计表")
+public class StoreTrackStatistics {
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "店铺ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "统计日期")
+    @TableField("stat_date")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date statDate;
+
+    @ApiModelProperty(value = "统计类型")
+    @TableField("stat_type")
+    private String statType;
+
+    @ApiModelProperty(value = "流量数据(JSON格式)")
+    @TableField("traffic_data")
+    private String trafficData;
+
+    @ApiModelProperty(value = "互动数据(JSON格式)")
+    @TableField("interaction_data")
+    private String interactionData;
+
+    @ApiModelProperty(value = "优惠券数据(JSON格式)")
+    @TableField("coupon_data")
+    private String couponData;
+
+    @ApiModelProperty(value = "代金券数据(JSON格式)")
+    @TableField("voucher_data")
+    private String voucherData;
+
+    @ApiModelProperty(value = "服务质量数据(JSON格式)")
+    @TableField("service_data")
+    private String serviceData;
+
+    @ApiModelProperty(value = "价目表排名数据(JSON格式)")
+    @TableField("price_ranking_data")
+    private String priceRankingData;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "更新时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+}

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

@@ -0,0 +1,15 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StoreTrackEvent;
+
+/**
+ * 埋点事件Mapper接口
+ *
+ * @author system
+ * @since 2026-01-14
+ */
+@Mapper
+public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
+}

+ 15 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreTrackStatisticsMapper.java

@@ -0,0 +1,15 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StoreTrackStatistics;
+
+/**
+ * 埋点统计数据Mapper接口
+ *
+ * @author system
+ * @since 2026-01-14
+ */
+@Mapper
+public interface StoreTrackStatisticsMapper extends BaseMapper<StoreTrackStatistics> {
+}

+ 297 - 0
alien-store/doc/埋点实现代码清单.md

@@ -0,0 +1,297 @@
+# 埋点实现代码清单
+
+## 一、已创建的文件
+
+### 1. 实体类(Entity)
+- ✅ `alien-entity/src/main/java/shop/alien/entity/store/StoreTrackEvent.java` - 埋点事件实体类
+- ✅ `alien-entity/src/main/java/shop/alien/entity/store/StoreTrackStatistics.java` - 埋点统计数据实体类
+
+### 2. 注解(Annotation)
+- ✅ `alien-store/src/main/java/shop/alien/store/annotation/TrackEvent.java` - 埋点注解
+
+### 3. 服务接口(Service Interface)
+- ✅ `alien-store/src/main/java/shop/alien/store/service/TrackEventService.java` - 埋点事件服务接口
+
+### 4. Controller
+- ✅ `alien-store/src/main/java/shop/alien/store/controller/TrackEventController.java` - 埋点上报接口
+
+### 5. 文档
+- ✅ `alien-store/doc/埋点需求完整方案.md` - 完整方案文档
+
+## 二、待创建的文件(核心代码)
+
+### 1. AOP切面(重要)
+**文件路径**: `alien-store/src/main/java/shop/alien/store/aspect/TrackEventAspect.java`
+
+**功能**: 拦截标注了`@TrackEvent`注解的方法,自动收集埋点数据并写入Redis List
+
+**关键代码要点**:
+- 使用`@Around`环绕通知
+- 解析SpEL表达式获取`storeId`、`userId`等参数
+- 调用`BaseRedisService.setListRight()`写入Redis List
+- 使用`@Order`注解设置切面执行顺序
+
+### 2. Service实现类(重要)
+**文件路径**: `alien-store/src/main/java/shop/alien/store/service/impl/TrackEventServiceImpl.java`
+
+**功能**: 
+- 实现`saveTrackEvent()`方法,将埋点数据写入Redis List
+- 实现`batchSaveTrackEvents()`方法,批量保存到数据库
+- 实现`getBusinessData()`方法,统计查询经营数据
+- 实现`compareBusinessData()`方法,对比数据
+- 实现`calculateAndSaveStatistics()`方法,计算统计数据
+- 实现`getPriceRankingData()`方法,获取价目表排名
+
+### 3. 数据消费服务(重要)
+**文件路径**: `alien-store/src/main/java/shop/alien/store/service/TrackEventConsumer.java`
+
+**功能**: 定时任务,从Redis List批量消费数据并写入数据库
+
+**关键代码要点**:
+- 使用`@Scheduled(cron = "0/10 * * * * ?")`每10秒执行一次
+- 使用分布式锁防止多实例重复消费
+- 每次从Redis List取出100条数据
+- 批量保存到数据库
+
+### 4. Mapper接口
+**文件路径**: `alien-store/src/main/java/shop/alien/mapper/StoreTrackEventMapper.java`
+**文件路径**: `alien-store/src/main/java/shop/alien/mapper/StoreTrackStatisticsMapper.java`
+
+### 5. 统计查询Controller
+**文件路径**: `alien-store/src/main/java/shop/alien/store/controller/BusinessDataController.java`
+
+**接口列表**:
+- `GET /business/data` - 查询经营数据
+- `GET /business/data/compare` - 数据对比
+- `GET /business/data/history` - 历史数据查询
+
+### 6. AI推荐Controller
+**文件路径**: `alien-store/src/main/java/shop/alien/store/controller/AIRecoveryController.java`
+
+**接口列表**:
+- `GET /business/ai/recommendation` - 获取AI推荐
+
+## 三、关键代码示例
+
+### 3.1 AOP切面核心代码
+
+```java
+@Slf4j
+@Aspect
+@Component
+@Order(2)
+@RequiredArgsConstructor
+public class TrackEventAspect {
+
+    private final BaseRedisService baseRedisService;
+    private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
+    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
+    private static final String REDIS_QUEUE_KEY = "track:event:queue";
+
+    @Around("@annotation(trackEvent)")
+    public Object around(ProceedingJoinPoint joinPoint, TrackEvent trackEvent) throws Throwable {
+        Object result = joinPoint.proceed();
+        
+        // 构建埋点事件对象
+        StoreTrackEvent event = buildTrackEvent(joinPoint, trackEvent);
+        
+        // 异步写入Redis List
+        if (trackEvent.async()) {
+            baseRedisService.setListRight(REDIS_QUEUE_KEY, JSON.toJSONString(event));
+        } else {
+            // 同步写入
+            trackEventService.batchSaveTrackEvents(Collections.singletonList(event));
+        }
+        
+        return result;
+    }
+    
+    private StoreTrackEvent buildTrackEvent(ProceedingJoinPoint joinPoint, TrackEvent annotation) {
+        // 解析SpEL表达式获取参数值
+        // 获取用户ID、店铺ID等
+        // 设置IP、User-Agent等
+        // ...
+    }
+}
+```
+
+### 3.2 Redis List消费核心代码
+
+```java
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class TrackEventConsumer {
+
+    private final BaseRedisService baseRedisService;
+    private final TrackEventService trackEventService;
+    private static final String REDIS_QUEUE_KEY = "track:event:queue";
+    private static final String CONSUMER_LOCK_KEY = "track:event:consumer:lock";
+    private static final int BATCH_SIZE = 100;
+
+    @Scheduled(cron = "0/10 * * * * ?") // 每10秒执行一次
+    public void consumeTrackEvents() {
+        // 获取分布式锁
+        String lockId = baseRedisService.lock(CONSUMER_LOCK_KEY, 5000, 1000);
+        if (lockId == null) {
+            log.debug("获取消费锁失败,跳过本次消费");
+            return;
+        }
+
+        try {
+            // 批量从Redis List取出数据
+            List<String> eventList = baseRedisService.popBatchFromList(REDIS_QUEUE_KEY);
+            
+            if (eventList == null || eventList.isEmpty()) {
+                return;
+            }
+
+            // 转换为实体对象
+            List<StoreTrackEvent> events = eventList.stream()
+                    .map(json -> JSON.parseObject(json, StoreTrackEvent.class))
+                    .collect(Collectors.toList());
+
+            // 批量保存到数据库
+            trackEventService.batchSaveTrackEvents(events);
+            
+            log.info("成功消费{}条埋点数据", events.size());
+        } catch (Exception e) {
+            log.error("消费埋点数据失败", e);
+        } finally {
+            // 释放锁
+            baseRedisService.unlock(CONSUMER_LOCK_KEY, lockId);
+        }
+    }
+}
+```
+
+### 3.3 统计数据查询核心代码
+
+```java
+@Override
+public Map<String, Object> getBusinessData(Integer storeId, Date startDate, Date endDate, String category) {
+    Map<String, Object> result = new HashMap<>();
+    
+    if (category == null || category.equals("TRAFFIC")) {
+        // 流量数据统计
+        Map<String, Object> trafficData = new HashMap<>();
+        // 查询浏览量、访客数等
+        result.put("trafficData", trafficData);
+    }
+    
+    if (category == null || category.equals("INTERACTION")) {
+        // 互动数据统计
+        Map<String, Object> interactionData = new HashMap<>();
+        // 查询收藏、分享等
+        result.put("interactionData", interactionData);
+    }
+    
+    // ... 其他分类数据
+    
+    return result;
+}
+```
+
+## 四、数据库表SQL
+
+### 4.1 埋点事件表
+
+```sql
+CREATE TABLE `store_track_event` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `event_type` varchar(50) NOT NULL COMMENT '事件类型',
+  `event_category` varchar(50) NOT NULL COMMENT '事件分类',
+  `user_id` int(11) DEFAULT NULL COMMENT '用户ID',
+  `store_id` int(11) DEFAULT NULL COMMENT '店铺ID',
+  `target_id` int(11) DEFAULT NULL COMMENT '目标对象ID',
+  `target_type` varchar(50) DEFAULT NULL COMMENT '目标对象类型',
+  `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 '设备类型',
+  `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 '删除标记',
+  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='埋点事件表';
+```
+
+### 4.2 埋点统计表
+
+```sql
+CREATE TABLE `store_track_statistics` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `store_id` int(11) NOT NULL COMMENT '店铺ID',
+  `stat_date` date NOT NULL COMMENT '统计日期',
+  `stat_type` varchar(50) NOT NULL COMMENT '统计类型',
+  `traffic_data` text COMMENT '流量数据(JSON格式)',
+  `interaction_data` text COMMENT '互动数据(JSON格式)',
+  `coupon_data` text COMMENT '优惠券数据(JSON格式)',
+  `voucher_data` text COMMENT '代金券数据(JSON格式)',
+  `service_data` text COMMENT '服务质量数据(JSON格式)',
+  `price_ranking_data` text COMMENT '价目表排名数据(JSON格式)',
+  `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_store_date_type` (`store_id`,`stat_date`,`stat_type`),
+  KEY `idx_store_id` (`store_id`),
+  KEY `idx_stat_date` (`stat_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='埋点统计表';
+```
+
+## 五、前端调用示例
+
+### 5.1 上报埋点事件
+
+```javascript
+// 上报浏览事件
+axios.post('/track/event', {
+  eventType: 'VIEW',
+  eventCategory: 'TRAFFIC',
+  storeId: 1001,
+  targetType: 'STORE',
+  duration: 3000
+});
+```
+
+### 5.2 查询经营数据
+
+```javascript
+axios.get('/business/data', {
+  params: {
+    storeId: 1001,
+    startDate: '2026-01-08',
+    endDate: '2026-01-14'
+  }
+});
+```
+
+## 六、后续开发步骤
+
+1. ✅ 创建实体类和注解(已完成)
+2. ⬜ 实现AOP切面(TrackEventAspect)
+3. ⬜ 实现Service实现类(TrackEventServiceImpl)
+4. ⬜ 实现数据消费服务(TrackEventConsumer)
+5. ⬜ 创建Mapper接口
+6. ⬜ 实现统计查询Controller(BusinessDataController)
+7. ⬜ 实现AI推荐Controller(AIRecoveryController)
+8. ⬜ 执行数据库建表SQL
+9. ⬜ 编写单元测试
+10. ⬜ 联调测试
+
+## 七、注意事项
+
+1. Redis List需要设置最大长度,防止内存溢出
+2. 消费服务需要异常处理和重试机制
+3. 统计数据建议使用定时任务预计算
+4. AI推荐接口需要缓存,避免频繁调用AI服务
+5. 埋点数据量大,需要定期清理历史数据

+ 217 - 0
alien-store/doc/埋点实现总结.md

@@ -0,0 +1,217 @@
+# 埋点系统实现总结
+
+## ✅ 已完成的工作
+
+### 1. 数据库表设计 ✅
+- **store_track_event** - 埋点事件表
+- **store_track_statistics** - 埋点统计表
+
+> ⚠️ **注意**:需要执行SQL建表语句,详见 `埋点需求完整方案.md`
+
+### 2. 实体类 ✅
+- ✅ `StoreTrackEvent.java` - 埋点事件实体
+- ✅ `StoreTrackStatistics.java` - 统计数据实体
+
+### 3. Mapper接口 ✅
+- ✅ `StoreTrackEventMapper.java`
+- ✅ `StoreTrackStatisticsMapper.java`
+
+### 4. 注解 ✅
+- ✅ `@TrackEvent` - 埋点注解
+
+### 5. AOP切面 ✅
+- ✅ `TrackEventAspect.java` - 自动埋点切面
+  - 拦截标注了`@TrackEvent`的方法
+  - 自动收集数据并写入Redis List
+
+### 6. Service层 ✅
+- ✅ `TrackEventService.java` - 服务接口
+- ✅ `TrackEventServiceImpl.java` - 服务实现
+  - 埋点数据保存(写入Redis List)
+  - 批量保存到数据库
+  - 经营数据统计查询
+  - 数据对比
+  - 价目表排名查询
+
+### 7. 数据消费服务 ✅
+- ✅ `TrackEventConsumer.java` - 定时消费服务
+  - 每10秒执行一次
+  - 从Redis List批量取出数据
+  - 批量写入数据库
+  - 使用分布式锁防止重复消费
+
+### 8. Controller层 ✅
+- ✅ `TrackEventController.java` - 埋点上报接口
+  - `POST /track/event` - 前端主动上报埋点
+  
+- ✅ `BusinessDataController.java` - 经营数据查询接口
+  - `GET /business/data` - 查询经营数据
+  - `GET /business/data/compare` - 数据对比
+  - `GET /business/data/history` - 历史数据查询
+
+- ✅ `AIRecoveryController.java` - AI推荐接口
+  - `GET /business/ai/recommendation` - 获取AI推荐
+
+## 📋 待完成的工作
+
+### 1. 执行数据库建表SQL ⚠️
+需要执行以下SQL创建表结构:
+- `store_track_event` 表
+- `store_track_statistics` 表
+
+详见:`埋点需求完整方案.md` 第 四、数据库表设计 章节
+
+### 2. Service实现类的TODO ⚠️
+在 `TrackEventServiceImpl.java` 中有以下TODO需要实现:
+
+1. **新增访客数统计** - `getTrafficData()` 方法
+   - 需要查询历史数据判断是否为新增访客
+
+2. **互动数据统计** - `getInteractionData()` 方法
+   - 好友数量、关注数量、粉丝数量
+   - 发布动态数量、动态点赞/评论/转发数量
+   - 被举报/拉黑次数
+   - 这些数据需要从其他表查询
+
+3. **优惠券数据统计** - `getCouponData()` 方法
+   - 需要从优惠券相关表查询数据
+   - 实现赠送、使用等统计
+
+4. **代金券数据统计** - `getVoucherData()` 方法
+   - 需要从代金券相关表查询数据
+   - 实现赠送、使用等统计
+
+5. **服务质量数据统计** - `getServiceData()` 方法
+   - 需要从评价表查询数据
+   - 店铺评分、口味评分、环境评分、服务评分
+   - 评价数量、好评/中评/差评统计
+   - 差评申诉相关统计
+
+### 3. AI推荐功能完善 ⚠️
+在 `AIRecoveryController.java` 中:
+- 需要实现 `callAIService()` 方法
+- 根据实际的AI服务接口调用
+- 解析AI返回结果并生成推荐
+
+## 🚀 使用指南
+
+### 1. 在后端方法上使用注解埋点
+
+```java
+@TrackEvent(
+    eventType = "VIEW",
+    eventCategory = "TRAFFIC",
+    storeId = "#{#storeId}",
+    targetType = "STORE"
+)
+@GetMapping("/store/detail")
+public R<StoreInfo> getStoreDetail(@RequestParam Integer storeId) {
+    // 业务逻辑
+}
+```
+
+### 2. 前端上报埋点
+
+```javascript
+// 上报浏览事件
+axios.post('/track/event', {
+  eventType: 'VIEW',
+  eventCategory: 'TRAFFIC',
+  storeId: 1001,
+  targetType: 'STORE',
+  duration: 3000
+});
+```
+
+### 3. 查询经营数据
+
+```javascript
+// 查询经营数据
+axios.get('/business/data', {
+  params: {
+    storeId: 1001,
+    startDate: '2026-01-08',
+    endDate: '2026-01-14'
+  }
+});
+
+// 对比数据
+axios.get('/business/data/compare', {
+  params: {
+    storeId: 1001,
+    startDate1: '2026-01-08',
+    endDate1: '2026-01-14',
+    startDate2: '2026-01-01',
+    endDate2: '2026-01-07'
+  }
+});
+```
+
+### 4. 获取AI推荐
+
+```javascript
+// 获取AI推荐
+axios.get('/business/ai/recommendation', {
+  params: {
+    storeId: 1001
+  }
+});
+```
+
+## 📊 技术架构
+
+```
+前端上报埋点
+    ↓
+TrackEventController (POST /track/event)
+    ↓
+TrackEventService.saveTrackEvent()
+    ↓
+Redis List (异步队列)
+    ↓
+TrackEventConsumer (定时任务,每10秒)
+    ↓
+TrackEventService.batchSaveTrackEvents()
+    ↓
+MySQL (store_track_event 表)
+    ↓
+统计查询 (BusinessDataController)
+    ↓
+返回前端
+```
+
+## 🔧 配置说明
+
+### 1. 定时任务已启用
+应用已配置 `@EnableScheduling`,定时任务会自动执行。
+
+### 2. Redis配置
+确保Redis连接正常,Redis List Key为:`track:event:queue`
+
+### 3. 消费频率
+当前配置为每10秒消费一次,可在 `TrackEventConsumer.java` 中修改 `@Scheduled` 注解。
+
+## ⚠️ 注意事项
+
+1. **数据量控制**:Redis List需要设置最大长度,防止内存溢出
+2. **异常处理**:消费服务已实现异常处理,失败不影响主流程
+3. **性能优化**:统计数据建议使用定时任务预计算,存入 `store_track_statistics` 表
+4. **数据清理**:定期清理历史埋点数据,避免数据表过大
+5. **AI推荐缓存**:AI推荐结果建议缓存,避免频繁调用AI服务
+
+## 📝 后续优化建议
+
+1. **统计数据预计算**:使用定时任务(如每天凌晨)计算前一天的统计数据
+2. **缓存优化**:对常用查询结果进行缓存
+3. **监控告警**:监控Redis List长度,设置告警阈值
+4. **数据分表**:如果数据量很大,可以考虑按时间分表
+
+## 📚 相关文档
+
+- `埋点需求完整方案.md` - 完整方案文档
+- `埋点实现代码清单.md` - 代码清单和关键代码示例
+
+---
+
+**实现完成时间**:2026-01-14
+**下一步**:执行建表SQL,完善Service中的TODO,测试联调

+ 454 - 0
alien-store/doc/埋点测试指南.md

@@ -0,0 +1,454 @@
+# 埋点系统测试指南
+
+## 📋 测试前准备
+
+### 1. 执行数据库建表SQL(必需)
+
+**⚠️ 重要:在执行任何测试前,必须先创建数据库表!**
+
+执行以下SQL创建表结构:
+
+```sql
+-- 埋点事件表
+CREATE TABLE `store_track_event` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `event_type` varchar(50) NOT NULL COMMENT '事件类型',
+  `event_category` varchar(50) NOT NULL COMMENT '事件分类',
+  `user_id` int(11) DEFAULT NULL COMMENT '用户ID',
+  `store_id` int(11) DEFAULT NULL COMMENT '店铺ID',
+  `target_id` int(11) DEFAULT NULL COMMENT '目标对象ID',
+  `target_type` varchar(50) DEFAULT NULL COMMENT '目标对象类型',
+  `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 '设备类型',
+  `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 '删除标记',
+  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',
+  `store_id` int(11) NOT NULL COMMENT '店铺ID',
+  `stat_date` date NOT NULL COMMENT '统计日期',
+  `stat_type` varchar(50) NOT NULL COMMENT '统计类型',
+  `traffic_data` text COMMENT '流量数据(JSON格式)',
+  `interaction_data` text COMMENT '互动数据(JSON格式)',
+  `coupon_data` text COMMENT '优惠券数据(JSON格式)',
+  `voucher_data` text COMMENT '代金券数据(JSON格式)',
+  `service_data` text COMMENT '服务质量数据(JSON格式)',
+  `price_ranking_data` text COMMENT '价目表排名数据(JSON格式)',
+  `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_store_date_type` (`store_id`,`stat_date`,`stat_type`),
+  KEY `idx_store_id` (`store_id`),
+  KEY `idx_stat_date` (`stat_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='埋点统计表';
+```
+
+### 2. 确认服务配置
+
+- ✅ Redis连接正常
+- ✅ MySQL连接正常
+- ✅ 定时任务已启用(应用已配置 `@EnableScheduling`)
+
+---
+
+## 🧪 测试步骤
+
+### 测试1:前端埋点上报测试
+
+#### 1.1 使用Postman/curl测试上报接口
+
+**接口地址**: `POST http://localhost:8080/track/event`
+
+**请求头**:
+```http
+Content-Type: application/json
+Authorization: Bearer {your_token}  # 如果需要认证
+```
+
+**请求体**:
+```json
+{
+  "eventType": "VIEW",
+  "eventCategory": "TRAFFIC",
+  "storeId": 1001,
+  "targetType": "STORE",
+  "duration": 3000
+}
+```
+
+**预期结果**:
+```json
+{
+  "code": 200,
+  "success": true,
+  "msg": "上报成功",
+  "data": null
+}
+```
+
+**验证步骤**:
+1. 调用接口后,检查Redis List中是否有数据
+   ```bash
+   # 连接到Redis,查看队列长度
+   LLEN track:event:queue
+   
+   # 查看队列中的数据
+   LRANGE track:event:queue 0 -1
+   ```
+
+2. 等待10秒(定时任务消费间隔),检查数据库
+   ```sql
+   -- 查询最新埋点数据
+   SELECT * FROM store_track_event 
+   ORDER BY created_time DESC 
+   LIMIT 10;
+   ```
+
+#### 1.2 测试不同类型的事件
+
+```json
+// 测试搜索事件
+{
+  "eventType": "SEARCH",
+  "eventCategory": "TRAFFIC",
+  "storeId": 1001,
+  "targetType": "STORE"
+}
+
+// 测试收藏事件
+{
+  "eventType": "COLLECT",
+  "eventCategory": "INTERACTION",
+  "storeId": 1001,
+  "targetType": "STORE"
+}
+
+// 测试分享事件
+{
+  "eventType": "SHARE",
+  "eventCategory": "INTERACTION",
+  "storeId": 1001,
+  "targetType": "STORE"
+}
+
+// 测试价目表浏览
+{
+  "eventType": "PRICE_VIEW",
+  "eventCategory": "PRICE",
+  "storeId": 1001,
+  "targetId": 2001,
+  "targetType": "PRICE"
+}
+```
+
+---
+
+### 测试2:AOP自动埋点测试
+
+#### 2.1 在Controller方法上添加注解
+
+在需要埋点的Controller方法上添加 `@TrackEvent` 注解:
+
+```java
+@TrackEvent(
+    eventType = "VIEW",
+    eventCategory = "TRAFFIC",
+    storeId = "#{#storeId}",
+    targetType = "STORE"
+)
+@GetMapping("/store/detail")
+public R<StoreInfo> getStoreDetail(@RequestParam Integer storeId) {
+    // 业务逻辑
+    return R.data(storeInfo);
+}
+```
+
+#### 2.2 测试步骤
+
+1. 调用业务接口(如:`GET /store/detail?storeId=1001`)
+2. 接口正常返回
+3. 检查Redis List中是否自动生成了埋点数据
+4. 等待10秒,检查数据库是否写入
+
+---
+
+### 测试3:数据统计查询测试
+
+#### 3.1 查询经营数据
+
+**接口地址**: `GET http://localhost:8080/business/data`
+
+**请求参数**:
+```
+storeId=1001
+startDate=2026-01-08
+endDate=2026-01-14
+category=TRAFFIC  # 可选:TRAFFIC, INTERACTION, COUPON, VOUCHER, SERVICE, PRICE
+```
+
+**完整URL示例**:
+```
+GET http://localhost:8080/business/data?storeId=1001&startDate=2026-01-08&endDate=2026-01-14
+```
+
+**预期响应**:
+```json
+{
+  "code": 200,
+  "success": true,
+  "msg": "查询成功",
+  "data": {
+    "trafficData": {
+      "searchCount": 10,
+      "viewCount": 50,
+      "visitorCount": 30,
+      "totalDuration": 150000,
+      "avgDuration": 3000
+    },
+    "interactionData": {
+      "collectCount": 5,
+      "shareCount": 3,
+      "checkinCount": 2,
+      "consultCount": 1
+    }
+  }
+}
+```
+
+#### 3.2 数据对比测试
+
+**接口地址**: `GET http://localhost:8080/business/data/compare`
+
+**请求参数**:
+```
+storeId=1001
+startDate1=2026-01-08
+endDate1=2026-01-14
+startDate2=2026-01-01
+endDate2=2026-01-07
+```
+
+**完整URL示例**:
+```
+GET http://localhost:8080/business/data/compare?storeId=1001&startDate1=2026-01-08&endDate1=2026-01-14&startDate2=2026-01-01&endDate2=2026-01-07
+```
+
+**预期响应**:
+```json
+{
+  "code": 200,
+  "success": true,
+  "msg": "查询成功",
+  "data": {
+    "period1": {
+      "trafficData": {
+        "viewCount": 100
+      }
+    },
+    "period2": {
+      "trafficData": {
+        "viewCount": 80
+      }
+    },
+    "compare": {
+      "traffic": {
+        "viewCountChange": 25.00
+      }
+    }
+  }
+}
+```
+
+#### 3.3 AI推荐测试
+
+**接口地址**: `GET http://localhost:8080/business/ai/recommendation`
+
+**请求参数**:
+```
+storeId=1001
+```
+
+**完整URL示例**:
+```
+GET http://localhost:8080/business/ai/recommendation?storeId=1001
+```
+
+**预期响应**:
+```json
+{
+  "code": 200,
+  "success": true,
+  "msg": "查询成功",
+  "data": {
+    "summary": "相较于其他同星级的店铺,您价目表中的锅包肉和烤羊腿价格远高于其他商家",
+    "recommendations": [
+      {
+        "type": "PRICING",
+        "title": "价格优化建议",
+        "content": "寻找原材料更便宜的菜场、菜量降低、菜名突出特色,如锡林郭勒盟羔羊烤羊腿"
+      }
+    ]
+  }
+}
+```
+
+---
+
+### 测试4:定时消费任务测试
+
+#### 4.1 验证消费任务是否正常运行
+
+1. **查看日志**
+   - 每10秒应该看到日志:`成功消费X条埋点数据`
+   - 如果Redis List为空,不会有日志输出(这是正常的)
+
+2. **手动触发测试**
+   - 先上报多条埋点数据(10-20条)
+   - 检查Redis List长度:`LLEN track:event:queue`
+   - 等待10秒,再次检查:`LLEN track:event:queue`(应该为0或减少)
+   - 查询数据库确认数据已写入
+
+#### 4.2 分布式锁测试
+
+如果有多个服务实例,验证分布式锁是否生效:
+- 只有一个实例能消费数据
+- 不会出现重复消费
+
+---
+
+## 📊 完整测试流程示例
+
+### 流程1:完整的埋点到统计流程
+
+```bash
+# 步骤1: 上报多条埋点数据
+curl -X POST http://localhost:8080/track/event \
+  -H "Content-Type: application/json" \
+  -d '{"eventType":"VIEW","eventCategory":"TRAFFIC","storeId":1001,"targetType":"STORE","duration":3000}'
+
+curl -X POST http://localhost:8080/track/event \
+  -H "Content-Type: application/json" \
+  -d '{"eventType":"SEARCH","eventCategory":"TRAFFIC","storeId":1001,"targetType":"STORE"}'
+
+curl -X POST http://localhost:8080/track/event \
+  -H "Content-Type: application/json" \
+  -d '{"eventType":"COLLECT","eventCategory":"INTERACTION","storeId":1001,"targetType":"STORE"}'
+
+# 步骤2: 检查Redis List
+# 连接到Redis执行: LLEN track:event:queue
+
+# 步骤3: 等待10秒,定时任务会自动消费
+
+# 步骤4: 查询数据库验证数据已写入
+# SELECT COUNT(*) FROM store_track_event WHERE store_id = 1001;
+
+# 步骤5: 查询统计数据
+curl "http://localhost:8080/business/data?storeId=1001&startDate=2026-01-08&endDate=2026-01-14"
+```
+
+---
+
+## 🔍 测试检查清单
+
+### ✅ 功能测试
+
+- [ ] 前端埋点上报接口正常
+- [ ] AOP自动埋点正常工作
+- [ ] Redis List正常写入
+- [ ] 定时任务正常消费
+- [ ] 数据库正常写入
+- [ ] 统计数据查询正常
+- [ ] 数据对比功能正常
+- [ ] AI推荐接口正常(如已实现)
+
+### ✅ 性能测试
+
+- [ ] 批量上报100条数据,验证性能
+- [ ] 定时任务消费速度是否正常
+- [ ] 统计查询响应时间是否可接受
+
+### ✅ 异常测试
+
+- [ ] Redis不可用时,系统是否降级
+- [ ] 数据库不可用时,是否有异常处理
+- [ ] 异常数据是否会被正确处理
+
+---
+
+## 🐛 常见问题排查
+
+### 问题1:埋点数据没有写入数据库
+
+**检查项**:
+1. 检查Redis List是否有数据:`LLEN track:event:queue`
+2. 查看定时任务日志,是否有错误
+3. 检查数据库连接是否正常
+4. 检查表结构是否正确创建
+
+### 问题2:定时任务没有执行
+
+**检查项**:
+1. 确认应用已启用 `@EnableScheduling`
+2. 检查 `TrackEventConsumer` 类是否被Spring管理(`@Component`)
+3. 查看应用启动日志,确认定时任务配置加载
+
+### 问题3:统计数据为空
+
+**检查项**:
+1. 确认有埋点数据写入数据库
+2. 确认查询的时间范围正确
+3. 检查 `getTrafficData()` 等方法是否有TODO未实现
+
+### 问题4:AOP切面不生效
+
+**检查项**:
+1. 确认 `@TrackEvent` 注解在正确的方法上
+2. 确认 `TrackEventAspect` 类被Spring管理
+3. 检查切面执行顺序(`@Order`)
+
+---
+
+## 📝 测试报告模板
+
+```markdown
+### 测试日期: 2026-01-14
+### 测试人员: [姓名]
+
+#### 测试结果
+- ✅ 前端埋点上报: 通过
+- ✅ AOP自动埋点: 通过
+- ✅ Redis队列: 正常
+- ✅ 定时消费: 正常
+- ✅ 统计查询: 通过
+- ⚠️ AI推荐: 待完善
+
+#### 发现问题
+1. [问题描述]
+
+#### 建议
+1. [建议内容]
+```
+
+---
+
+## 🚀 下一步
+
+1. **完善统计逻辑**:实现Service中的TODO(优惠券、代金券、服务质量统计)
+2. **优化性能**:对统计数据添加缓存
+3. **监控告警**:添加Redis List长度监控
+4. **数据清理**:实现历史数据清理策略

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

@@ -0,0 +1,449 @@
+# 用户端埋点需求完整方案文档
+
+## 一、需求概述
+
+为 alien-store 模块的用户端实现完整的埋点系统,用于统计和分析用户行为数据,并通过AI提供智能推荐服务。
+
+### 1.1 埋点数据类型
+
+根据原型图,需要收集以下类型的数据:
+
+#### 1.1.1 流量数据
+- 店铺搜索量
+- 浏览量
+- 访客数
+- 新增访客数
+- 访问时长
+- 平均访问时长
+
+#### 1.1.2 互动数据
+- 店铺收藏次数
+- 店铺分享次数
+- 店铺打卡次数
+- 咨询商家次数
+- 好友数量
+- 关注数量
+- 粉丝数量
+- 发布动态数量
+- 动态点赞数量
+- 动态评论数量
+- 动态转发数量
+- 被举报次数
+- 被拉黑次数
+
+#### 1.1.3 优惠券数据
+- 赠送好友数量
+- 赠送好友金额合计
+- 赠送好友使用数量
+- 赠送好友使用金额合计
+- 赠送好友使用金额占比
+- 好友赠送数量
+- 好友赠送金额合计
+- 好友赠送使用数量
+- 好友赠送使用金额合计
+- 好友赠送使用金额占比
+
+#### 1.1.4 代金券数据
+- 赠送好友数量
+- 赠送好友金额合计
+- 赠送好友使用数量
+- 赠送好友使用金额合计
+- 赠送好友使用金额占比
+- 好友赠送数量
+- 好友赠送金额合计
+- 好友赠送使用数量
+- 好友赠送使用金额合计
+- 好友赠送使用金额占比
+
+#### 1.1.5 服务质量数据
+- 店铺评分
+- 口味评分
+- 环境评分
+- 服务评分
+- 评价数量
+- 好评数量
+- 中评数量
+- 差评数量
+- 差评占比
+- 差评申诉次数
+- 差评申诉成功次数
+- 差评申诉成功占比
+
+#### 1.1.6 价目表排名数据
+- 价目表浏览量
+- 价目表访客数
+- 价目表分享数
+
+## 二、技术架构
+
+### 2.1 架构设计
+
+```
+前端 (客户端)
+  ↓ HTTP请求
+Controller (埋点上报接口)
+  ↓ 异步写入
+Redis List (消息队列)
+  ↓ 定时任务批量消费
+数据库 (MySQL)
+  ↓ 统计分析
+统计查询接口
+  ↓ AI分析
+AI推荐服务
+```
+
+### 2.2 核心组件
+
+1. **埋点注解** (`@TrackEvent`): 标注需要埋点的方法
+2. **AOP切面** (`TrackEventAspect`): 拦截标注的方法,自动收集数据
+3. **Redis List**: 作为消息队列,异步存储埋点数据
+4. **消费服务** (`TrackEventConsumer`): 定时从Redis List批量消费数据并写入数据库
+5. **前端接口** (`TrackEventController`): 提供前端主动上报埋点的接口
+6. **统计接口** (`BusinessDataController`): 提供经营数据统计查询
+7. **AI推荐接口** (`AIRecoveryController`): 基于埋点数据提供AI推荐
+
+## 三、数据库表设计
+
+### 3.1 埋点事件主表 (store_track_event)
+
+```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='埋点事件表';
+```
+
+### 3.2 埋点统计数据表 (store_track_statistics)
+
+用于存储按店铺、日期聚合的统计数据,提升查询性能:
+
+```sql
+CREATE TABLE `store_track_statistics` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `store_id` int(11) NOT NULL COMMENT '店铺ID',
+  `stat_date` date NOT NULL COMMENT '统计日期',
+  `stat_type` varchar(50) NOT NULL COMMENT '统计类型(DAILY-日统计,WEEKLY-周统计,MONTHLY-月统计)',
+  `traffic_data` text COMMENT '流量数据(JSON格式)',
+  `interaction_data` text COMMENT '互动数据(JSON格式)',
+  `coupon_data` text COMMENT '优惠券数据(JSON格式)',
+  `voucher_data` text COMMENT '代金券数据(JSON格式)',
+  `service_data` text COMMENT '服务质量数据(JSON格式)',
+  `price_ranking_data` text COMMENT '价目表排名数据(JSON格式)',
+  `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_store_date_type` (`store_id`,`stat_date`,`stat_type`),
+  KEY `idx_store_id` (`store_id`),
+  KEY `idx_stat_date` (`stat_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='埋点统计表';
+```
+
+## 四、后端实现
+
+### 4.1 埋点注解 (@TrackEvent)
+
+定义在 `alien-store/src/main/java/shop/alien/store/annotation/TrackEvent.java`
+
+### 4.2 AOP切面实现
+
+定义在 `alien-store/src/main/java/shop/alien/store/aspect/TrackEventAspect.java`
+
+### 4.3 Redis List 异步存储
+
+使用现有的 `BaseRedisService` 的 `setListRight` 方法将埋点数据写入Redis List
+
+### 4.4 定时任务消费
+
+定义在 `alien-store/src/main/java/shop/alien/store/service/TrackEventConsumer.java`
+
+使用 `@Scheduled` 注解定时从Redis List批量取出数据并写入数据库
+
+### 4.5 前端上报接口
+
+定义在 `alien-store/src/main/java/shop/alien/store/controller/TrackEventController.java`
+
+### 4.6 统计查询接口
+
+定义在 `alien-store/src/main/java/shop/alien/store/controller/BusinessDataController.java`
+
+### 4.7 AI推荐接口
+
+定义在 `alien-store/src/main/java/shop/alien/store/controller/AIRecoveryController.java`
+
+## 五、前端联调
+
+### 5.1 前端需要的后端接口
+
+#### 5.1.1 埋点上报接口
+
+**接口路径**: `POST /track/event`
+
+**接口说明**: 前端主动上报埋点数据
+
+**请求参数**:
+```json
+{
+  "eventType": "VIEW",
+  "eventCategory": "TRAFFIC",
+  "storeId": 1001,
+  "targetId": 2001,
+  "targetType": "PRICE",
+  "eventData": "{}",
+  "amount": 100.00,
+  "duration": 3000
+}
+```
+
+**响应示例**:
+```json
+{
+  "code": 200,
+  "success": true,
+  "msg": "上报成功",
+  "data": null
+}
+```
+
+#### 5.1.2 经营数据查询接口
+
+**接口路径**: `GET /business/data`
+
+**接口说明**: 查询店铺的经营数据统计
+
+**请求参数**:
+- `storeId`: 店铺ID (必填)
+- `startDate`: 开始日期 (格式: yyyy-MM-dd)
+- `endDate`: 结束日期 (格式: yyyy-MM-dd)
+- `category`: 数据分类 (可选: TRAFFIC, INTERACTION, COUPON, VOUCHER, SERVICE, PRICE)
+
+**响应示例**:
+```json
+{
+  "code": 200,
+  "success": true,
+  "msg": "查询成功",
+  "data": {
+    "trafficData": {
+      "searchCount": 100,
+      "viewCount": 100,
+      "visitorCount": 70,
+      "newVisitorCount": 10,
+      "totalDuration": 373533,
+      "avgDuration": 213
+    },
+    "interactionData": {
+      "collectCount": 100,
+      "shareCount": 100,
+      "checkinCount": 100,
+      "consultCount": 100
+    }
+  }
+}
+```
+
+#### 5.1.3 数据对比接口
+
+**接口路径**: `GET /business/data/compare`
+
+**接口说明**: 对比两个时间段的数据
+
+**请求参数**:
+- `storeId`: 店铺ID (必填)
+- `startDate1`: 时间段1开始日期
+- `endDate1`: 时间段1结束日期
+- `startDate2`: 时间段2开始日期
+- `endDate2`: 时间段2结束日期
+
+**响应示例**:
+```json
+{
+  "code": 200,
+  "success": true,
+  "msg": "查询成功",
+  "data": {
+    "period1": {
+      "viewCount": 100,
+      "visitorCount": 70
+    },
+    "period2": {
+      "viewCount": 80,
+      "visitorCount": 70
+    },
+    "compare": {
+      "viewCountChange": 25.00,
+      "visitorCountChange": 0.00
+    }
+  }
+}
+```
+
+#### 5.1.4 AI推荐接口
+
+**接口路径**: `GET /business/ai/recommendation`
+
+**接口说明**: 获取基于埋点数据的AI推荐
+
+**请求参数**:
+- `storeId`: 店铺ID (必填)
+
+**响应示例**:
+```json
+{
+  "code": 200,
+  "success": true,
+  "msg": "查询成功",
+  "data": {
+    "summary": "相较于其他同星级的店铺,您价目表中的锅包肉和烤羊腿价格远高于其他商家",
+    "recommendations": [
+      {
+        "type": "PRICING",
+        "title": "价格优化建议",
+        "content": "寻找原材料更便宜的菜场、菜量降低、菜名突出特色,如锡林郭勒盟羔羊烤羊腿"
+      }
+    ]
+  }
+}
+```
+
+### 5.2 前端调用示例
+
+```javascript
+// 1. 上报浏览事件
+axios.post('/track/event', {
+  eventType: 'VIEW',
+  eventCategory: 'TRAFFIC',
+  storeId: 1001,
+  targetType: 'STORE',
+  duration: 3000
+});
+
+// 2. 查询经营数据
+axios.get('/business/data', {
+  params: {
+    storeId: 1001,
+    startDate: '2026-01-08',
+    endDate: '2026-01-14',
+    category: 'TRAFFIC'
+  }
+});
+
+// 3. 数据对比
+axios.get('/business/data/compare', {
+  params: {
+    storeId: 1001,
+    startDate1: '2026-01-08',
+    endDate1: '2026-01-14',
+    startDate2: '2026-01-01',
+    endDate2: '2026-01-07'
+  }
+});
+
+// 4. 获取AI推荐
+axios.get('/business/ai/recommendation', {
+  params: {
+    storeId: 1001
+  }
+});
+```
+
+## 六、实现细节
+
+### 6.1 埋点事件类型枚举
+
+```java
+public enum EventType {
+    VIEW("VIEW", "浏览"),
+    SEARCH("SEARCH", "搜索"),
+    COLLECT("COLLECT", "收藏"),
+    SHARE("SHARE", "分享"),
+    CHECKIN("CHECKIN", "打卡"),
+    CONSULT("CONSULT", "咨询"),
+    FOLLOW("FOLLOW", "关注"),
+    UNFOLLOW("UNFOLLOW", "取消关注"),
+    FRIEND_ADD("FRIEND_ADD", "添加好友"),
+    POST_PUBLISH("POST_PUBLISH", "发布动态"),
+    POST_LIKE("POST_LIKE", "动态点赞"),
+    POST_COMMENT("POST_COMMENT", "动态评论"),
+    POST_REPOST("POST_REPOST", "动态转发"),
+    REPORT("REPORT", "举报"),
+    BLOCK("BLOCK", "拉黑"),
+    COUPON_GIVE("COUPON_GIVE", "赠送优惠券"),
+    COUPON_USE("COUPON_USE", "使用优惠券"),
+    VOUCHER_GIVE("VOUCHER_GIVE", "赠送代金券"),
+    VOUCHER_USE("VOUCHER_USE", "使用代金券"),
+    PRICE_VIEW("PRICE_VIEW", "价目表浏览"),
+    PRICE_SHARE("PRICE_SHARE", "价目表分享"),
+    RATING("RATING", "评价"),
+    APPEAL("APPEAL", "申诉");
+}
+```
+
+### 6.2 Redis Key 设计
+
+- 埋点队列: `track:event:queue`
+- 消费锁: `track:event:consumer:lock`
+
+### 6.3 消费策略
+
+- 每次消费数量: 100条
+- 消费频率: 每10秒执行一次
+- 使用分布式锁防止多实例重复消费
+
+## 七、AI推荐实现
+
+### 7.1 AI推荐流程
+
+1. 收集店铺的埋点数据
+2. 调用AI服务分析数据
+3. 对比同行业、同星级店铺数据
+4. 生成推荐建议
+
+### 7.2 AI接口调用
+
+使用现有的 `AlienAIFeign` 调用AI服务,需要新增推荐接口。
+
+## 八、部署和运维
+
+### 8.1 配置项
+
+- Redis List最大长度: 100000
+- 批量消费大小: 100
+- 消费间隔: 10秒
+- 统计数据保留期: 2年
+
+### 8.2 监控指标
+
+- Redis List长度
+- 消费延迟
+- 消费失败率
+- 统计查询响应时间
+
+## 九、注意事项
+
+1. 埋点数据量可能很大,需要定期清理历史数据
+2. Redis List需要设置最大长度,防止内存溢出
+3. 消费服务需要异常处理和重试机制
+4. 统计数据建议使用定时任务预计算,提升查询性能
+5. AI推荐接口需要缓存,避免频繁调用AI服务

+ 61 - 0
alien-store/src/main/java/shop/alien/store/annotation/TrackEvent.java

@@ -0,0 +1,61 @@
+package shop.alien.store.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 埋点事件注解
+ * 用于标注需要埋点的方法
+ *
+ * @author system
+ * @since 2026-01-14
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface TrackEvent {
+
+    /**
+     * 事件类型
+     */
+    String eventType();
+
+    /**
+     * 事件分类
+     * TRAFFIC-流量数据
+     * INTERACTION-互动数据
+     * COUPON-优惠券
+     * VOUCHER-代金券
+     * SERVICE-服务质量
+     * PRICE-价目表
+     */
+    String eventCategory();
+
+    /**
+     * 店铺ID的SpEL表达式,如 "#storeId" 或 "#{#store.id}"
+     * 如果不指定,将从请求参数或Session中获取
+     */
+    String storeId() default "";
+
+    /**
+     * 目标对象ID的SpEL表达式,如 "#targetId" 或 "#{#price.id}"
+     */
+    String targetId() default "";
+
+    /**
+     * 目标对象类型
+     */
+    String targetType() default "";
+
+    /**
+     * 用户ID的SpEL表达式
+     * 如果不指定,将从JWT token中获取
+     */
+    String userId() default "";
+
+    /**
+     * 是否异步执行
+     * true: 异步执行,不阻塞主流程
+     * false: 同步执行
+     */
+    boolean async() default true;
+}

+ 143 - 0
alien-store/src/main/java/shop/alien/store/controller/AIRecoveryController.java

@@ -0,0 +1,143 @@
+package shop.alien.store.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.store.feign.AlienAIFeign;
+import shop.alien.store.service.TrackEventService;
+
+import java.util.*;
+
+/**
+ * AI推荐Controller
+ *
+ * @author system
+ * @since 2026-01-14
+ */
+@Slf4j
+@Api(tags = {"AI智能推荐"})
+@ApiSort(22)
+@CrossOrigin
+@RestController
+@RequestMapping("/business/ai")
+@RequiredArgsConstructor
+public class AIRecoveryController {
+
+    private final TrackEventService trackEventService;
+    private final AlienAIFeign alienAIFeign;
+
+    @ApiOperation("获取AI推荐")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/recommendation")
+    public R<Map<String, Object>> getRecommendation(@RequestParam("storeId") Integer storeId) {
+        log.info("获取AI推荐: storeId={}", storeId);
+
+        try {
+            // 获取最近30天的经营数据
+            Date endDate = new Date();
+            Date startDate = new Date(endDate.getTime() - 30L * 24 * 60 * 60 * 1000);
+            
+            Map<String, Object> businessData = trackEventService.getBusinessData(storeId, startDate, endDate, null);
+            
+            // 构建AI分析请求数据
+            Map<String, Object> aiRequestData = buildAIRequestData(storeId, businessData);
+            
+            // TODO: 调用AI服务进行分析
+            // JsonNode aiResponse = callAIService(aiRequestData);
+            
+            // 构建推荐结果
+            Map<String, Object> recommendation = buildRecommendation(businessData);
+            
+            return R.data(recommendation);
+        } catch (Exception e) {
+            log.error("获取AI推荐失败", e);
+            return R.fail("获取推荐失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 构建AI请求数据
+     */
+    private Map<String, Object> buildAIRequestData(Integer storeId, Map<String, Object> businessData) {
+        Map<String, Object> requestData = new HashMap<>();
+        requestData.put("storeId", storeId);
+        requestData.put("businessData", businessData);
+        requestData.put("analysisType", "BUSINESS_RECOMMENDATION");
+        
+        // 构建提示词
+        StringBuilder prompt = new StringBuilder();
+        prompt.append("请分析以下店铺的经营数据,并提供优化建议:\n");
+        prompt.append("店铺ID: ").append(storeId).append("\n");
+        
+        Map<String, Object> trafficData = (Map<String, Object>) businessData.get("trafficData");
+        if (trafficData != null) {
+            prompt.append("流量数据: ").append(JSONObject.toJSONString(trafficData)).append("\n");
+        }
+        
+        Map<String, Object> priceRankingData = (Map<String, Object>) businessData.get("priceRankingData");
+        if (priceRankingData != null) {
+            prompt.append("价目表排名数据: ").append(JSONObject.toJSONString(priceRankingData)).append("\n");
+        }
+        
+        requestData.put("prompt", prompt.toString());
+        
+        return requestData;
+    }
+
+    /**
+     * 调用AI服务
+     */
+    private JsonNode callAIService(Map<String, Object> requestData) {
+        try {
+            // TODO: 实现AI服务调用逻辑
+            // 根据AlienAIFeign的接口定义调用相应的AI服务
+            // 注意:需要先登录获取token
+            
+            // 示例代码(需要根据实际的AI服务接口调整):
+            // JSONObject loginResult = alienAIFeign.login(...);
+            // String token = loginResult.getString("access_token");
+            // JsonNode response = alienAIFeign.generateRecommendation("Bearer " + token, requestData);
+            
+            return null;
+        } catch (Exception e) {
+            log.error("调用AI服务失败", e);
+            return null;
+        }
+    }
+
+    /**
+     * 构建推荐结果(示例实现,实际应该从AI服务返回)
+     */
+    private Map<String, Object> buildRecommendation(Map<String, Object> businessData) {
+        Map<String, Object> recommendation = new HashMap<>();
+        
+        // 示例:基于数据的简单推荐逻辑
+        Map<String, Object> priceRankingData = (Map<String, Object>) businessData.get("priceRankingData");
+        
+        StringBuilder summary = new StringBuilder();
+        List<Map<String, Object>> recommendations = new ArrayList<>();
+        
+        // 示例推荐逻辑:如果价目表价格高于平均值,给出建议
+        if (priceRankingData != null) {
+            summary.append("相较于其他同星级的店铺,您价目表中的锅包肉和烤羊腿价格远高于其他商家");
+            
+            Map<String, Object> rec1 = new HashMap<>();
+            rec1.put("type", "PRICING");
+            rec1.put("title", "价格优化建议");
+            rec1.put("content", "寻找原材料更便宜的菜场、菜量降低、菜名突出特色,如锡林郭勒盟羔羊烤羊腿");
+            recommendations.add(rec1);
+        }
+        
+        recommendation.put("summary", summary.toString());
+        recommendation.put("recommendations", recommendations);
+        
+        return recommendation;
+    }
+}

+ 127 - 0
alien-store/src/main/java/shop/alien/store/controller/BusinessDataController.java

@@ -0,0 +1,127 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.store.service.TrackEventService;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 经营数据Controller
+ *
+ * @author system
+ * @since 2026-01-14
+ */
+@Slf4j
+@Api(tags = {"经营数据统计"})
+@ApiSort(21)
+@CrossOrigin
+@RestController
+@RequestMapping("/business")
+@RequiredArgsConstructor
+public class BusinessDataController {
+
+    private final TrackEventService trackEventService;
+
+    @ApiOperation("查询经营数据")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "startDate", value = "开始日期", dataType = "String", paramType = "query", required = false, example = "2026-01-08"),
+            @ApiImplicitParam(name = "endDate", value = "结束日期", dataType = "String", paramType = "query", required = false, example = "2026-01-14"),
+            @ApiImplicitParam(name = "category", value = "数据分类(TRAFFIC,INTERACTION,COUPON,VOUCHER,SERVICE,PRICE)", dataType = "String", paramType = "query", required = false)
+    })
+    @GetMapping("/data")
+    public R<Map<String, Object>> getBusinessData(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
+            @RequestParam(value = "category", required = false) String category
+    ) {
+        log.info("查询经营数据: storeId={}, startDate={}, endDate={}, category={}", 
+                storeId, startDate, endDate, category);
+
+        try {
+            // 如果没有指定日期,默认查询最近7天的数据
+            if (startDate == null || endDate == null) {
+                Date now = new Date();
+                if (endDate == null) {
+                    endDate = now;
+                }
+                if (startDate == null) {
+                    long sevenDaysAgo = endDate.getTime() - 7 * 24 * 60 * 60 * 1000L;
+                    startDate = new Date(sevenDaysAgo);
+                }
+            }
+
+            Map<String, Object> data = trackEventService.getBusinessData(storeId, startDate, endDate, category);
+            return R.data(data);
+        } catch (Exception e) {
+            log.error("查询经营数据失败", e);
+            return R.fail("查询失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("对比两个时间段的数据")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "startDate1", value = "时间段1开始日期", dataType = "String", paramType = "query", required = true, example = "2026-01-08"),
+            @ApiImplicitParam(name = "endDate1", value = "时间段1结束日期", dataType = "String", paramType = "query", required = true, example = "2026-01-14"),
+            @ApiImplicitParam(name = "startDate2", value = "时间段2开始日期", dataType = "String", paramType = "query", required = true, example = "2026-01-01"),
+            @ApiImplicitParam(name = "endDate2", value = "时间段2结束日期", dataType = "String", paramType = "query", required = true, example = "2026-01-07")
+    })
+    @GetMapping("/data/compare")
+    public R<Map<String, Object>> compareBusinessData(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("startDate1") @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate1,
+            @RequestParam("endDate1") @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate1,
+            @RequestParam("startDate2") @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate2,
+            @RequestParam("endDate2") @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate2
+    ) {
+        log.info("对比经营数据: storeId={}, period1=[{}, {}], period2=[{}, {}]", 
+                storeId, startDate1, endDate1, startDate2, endDate2);
+
+        try {
+            Map<String, Object> data = trackEventService.compareBusinessData(
+                    storeId, startDate1, endDate1, startDate2, endDate2);
+            return R.data(data);
+        } catch (Exception e) {
+            log.error("对比经营数据失败", e);
+            return R.fail("对比失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("查询历史数据")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "startDate", value = "开始日期", dataType = "String", paramType = "query", required = true, example = "2026-01-01"),
+            @ApiImplicitParam(name = "endDate", value = "结束日期", dataType = "String", paramType = "query", required = true, example = "2026-01-31"),
+            @ApiImplicitParam(name = "statType", value = "统计类型(DAILY,WEEKLY,MONTHLY)", dataType = "String", paramType = "query", required = false)
+    })
+    @GetMapping("/data/history")
+    public R<Map<String, Object>> getHistoryData(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("startDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
+            @RequestParam("endDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
+            @RequestParam(value = "statType", required = false, defaultValue = "DAILY") String statType
+    ) {
+        log.info("查询历史数据: storeId={}, startDate={}, endDate={}, statType={}", 
+                storeId, startDate, endDate, statType);
+
+        try {
+            // TODO: 从统计表中查询历史数据
+            Map<String, Object> data = trackEventService.getBusinessData(storeId, startDate, endDate, null);
+            return R.data(data);
+        } catch (Exception e) {
+            log.error("查询历史数据失败", e);
+            return R.fail("查询失败: " + e.getMessage());
+        }
+    }
+}

+ 83 - 0
alien-store/src/main/java/shop/alien/store/service/TrackEventConsumer.java

@@ -0,0 +1,83 @@
+package shop.alien.store.service;
+
+import com.alibaba.fastjson.JSON;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import shop.alien.entity.store.StoreTrackEvent;
+import shop.alien.store.config.BaseRedisService;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 埋点事件消费服务
+ * 定时从Redis List批量消费数据并写入数据库
+ *
+ * @author system
+ * @since 2026-01-14
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class TrackEventConsumer {
+
+    private final BaseRedisService baseRedisService;
+    private final TrackEventService trackEventService;
+    
+    private static final String REDIS_QUEUE_KEY = "track:event:queue";
+    private static final String CONSUMER_LOCK_KEY = "track:event:consumer:lock";
+
+    /**
+     * 定时消费埋点数据
+     * 每10秒执行一次
+     */
+    @Scheduled(cron = "0/10 * * * * ?")
+    public void consumeTrackEvents() {
+        // 获取分布式锁
+        String lockId = baseRedisService.lock(CONSUMER_LOCK_KEY, 5000, 1000);
+        if (lockId == null) {
+            log.debug("获取消费锁失败,跳过本次消费");
+            return;
+        }
+
+        try {
+            // 批量从Redis List取出数据
+            List<String> eventList = baseRedisService.popBatchFromList(REDIS_QUEUE_KEY);
+            
+            if (eventList == null || eventList.isEmpty()) {
+                return;
+            }
+
+            log.debug("从Redis List取出{}条埋点数据", eventList.size());
+
+            // 转换为实体对象
+            List<StoreTrackEvent> events = eventList.stream()
+                    .map(json -> {
+                        try {
+                            return JSON.parseObject(json, StoreTrackEvent.class);
+                        } catch (Exception e) {
+                            log.error("解析埋点数据JSON失败: {}", json, e);
+                            return null;
+                        }
+                    })
+                    .filter(event -> event != null)
+                    .collect(Collectors.toList());
+
+            if (events.isEmpty()) {
+                return;
+            }
+
+            // 批量保存到数据库
+            trackEventService.batchSaveTrackEvents(events);
+            
+            log.info("成功消费{}条埋点数据", events.size());
+        } catch (Exception e) {
+            log.error("消费埋点数据失败", e);
+        } finally {
+            // 释放锁
+            baseRedisService.unlock(CONSUMER_LOCK_KEY, lockId);
+        }
+    }
+}