# 埋点实现代码清单 ## 一、已创建的文件 ### 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 eventList = baseRedisService.popBatchFromList(REDIS_QUEUE_KEY); if (eventList == null || eventList.isEmpty()) { return; } // 转换为实体对象 List 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 getBusinessData(Integer storeId, Date startDate, Date endDate, String category) { Map result = new HashMap<>(); if (category == null || category.equals("TRAFFIC")) { // 流量数据统计 Map trafficData = new HashMap<>(); // 查询浏览量、访客数等 result.put("trafficData", trafficData); } if (category == null || category.equals("INTERACTION")) { // 互动数据统计 Map 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. 埋点数据量大,需要定期清理历史数据