Explorar el Código

fix: 差评申述添加埋点及数据统计优化

penghao hace 2 meses
padre
commit
34c4080f61

+ 1 - 1
alien-entity/src/main/java/shop/alien/mapper/StoreCommentAppealMapper.java

@@ -53,7 +53,7 @@ public interface StoreCommentAppealMapper extends BaseMapper<StoreCommentAppeal>
             "left join life_user d on c.user_id = d.id and d.delete_flag = 0 " +
             "left join store_info e on a.store_id = e.id and e.delete_flag = 0 " +
             "Left join store_user f on e.id = f.store_id and f.delete_flag = 0 ${ew.customSqlSegment}")
-    List<StoreCommentAppealVo> getStoreCommentAppealPage(@Param(Constants.WRAPPER) QueryWrapper<StoreCommentAppealVo> queryWrapper);
+    List<StoreCommentAppealVo> getStoreCommentAppealList(@Param(Constants.WRAPPER) QueryWrapper<StoreCommentAppealVo> queryWrapper);
 
     /**
      * 申诉详情

+ 15 - 0
alien-gateway/pom.xml

@@ -220,10 +220,25 @@
                     <target>1.8</target>
                     <source>1.8</source>
                     <encoding>UTF-8</encoding>
+                    <parameters>true</parameters>
                     <!-- <compilerArguments> <extdirs>lib</extdirs> </compilerArguments> -->
                 </configuration>
             </plugin>
             <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>shop.alien.gateway.AlienGatewayApplication</mainClass>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
                 <configuration>

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

@@ -27,7 +27,7 @@
 | 序号 | 接口路径 | HTTP方法 | 方法名 | 所在类 | 事件类型 |
 |------|---------|---------|--------|--------|---------|
 | 11 | `/commonRating/addRating` | POST | `add` | `CommonRatingController` | RATING_ADD |
-| 12 | `/commentAppeal/submit` | POST | `submitAppeal` | `CommentAppealController` | APPEAL |
+| 12 | `/storeCommentAppeal/addAppealNew` | POST | `addAppealNew` | `StoreCommentAppealController` | APPEAL |
 
 ## 四、价目表(PRICE)
 
@@ -63,5 +63,5 @@
 
 ---
 
-**文档版本**:v1.2  
-**最后更新**:2026-01-22
+**文档版本**:v1.3  
+**最后更新**:2026-01-24

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

@@ -80,8 +80,8 @@
 
 ```
 前端 (客户端)
-  ↓ HTTP请求
-Controller (埋点上报接口)
+  ↓ HTTP请求 / WebSocket消息
+Controller / WebSocketProcess (埋点触发)
   ↓ 异步写入
 Redis List (消息队列)
   ↓ 定时任务批量消费
@@ -94,13 +94,24 @@ AI推荐服务
 
 ### 2.2 核心组件
 
-1. **埋点注解** (`@TrackEvent`): 标注需要埋点的方法
+1. **埋点注解** (`@TrackEvent`): 标注需要埋点的HTTP接口方法
 2. **AOP切面** (`TrackEventAspect`): 拦截标注的方法,自动收集数据
-3. **Redis List**: 作为消息队列,异步存储埋点数据
-4. **消费服务** (`TrackEventConsumer`): 定时从Redis List批量消费数据并写入数据库
-5. **前端接口** (`TrackEventController`): 提供前端主动上报埋点的接口
-6. **统计接口** (`BusinessDataController`): 提供经营数据统计查询
-7. **AI推荐接口** (`AIRecoveryController`): 基于埋点数据提供AI推荐
+3. **WebSocket处理** (`WebSocketProcess`): 处理WebSocket消息时触发埋点
+4. **Redis List**: 作为消息队列,异步存储埋点数据
+5. **消费服务** (`TrackEventConsumer`): 定时从Redis List批量消费数据并写入数据库
+6. **前端接口** (`TrackEventController`): 提供前端主动上报埋点和手动触发统计的接口
+7. **统计接口** (`BusinessDataController`): 提供经营数据统计查询
+8. **AI推荐接口** (`AIRecoveryController`): 基于埋点数据提供AI推荐
+
+### 2.3 埋点方式
+
+系统支持三种埋点方式:
+
+| 方式 | 说明 | 适用场景 |
+|-----|------|---------|
+| `@TrackEvent`注解 | AOP自动拦截,无侵入式 | 标准HTTP接口 |
+| WebSocket埋点 | 在WebSocket消息处理中主动记录 | 实时消息场景(咨询、分享) |
+| 前端主动上报 | 通过`/track/event`接口上报 | 前端特有行为(浏览时长等) |
 
 ## 三、数据库表设计
 
@@ -166,37 +177,130 @@ CREATE TABLE `store_track_statistics` (
 
 定义在 `alien-store/src/main/java/shop/alien/store/annotation/TrackEvent.java`
 
+```java
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface TrackEvent {
+    String eventType();           // 事件类型
+    String eventCategory();       // 事件分类
+    String storeId() default "";  // 店铺ID的SpEL表达式
+    String targetId() default ""; // 目标对象ID的SpEL表达式
+    String targetType() default "";// 目标对象类型
+    String userId() default "";   // 用户ID的SpEL表达式
+    boolean async() default true; // 是否异步执行
+}
+```
+
 ### 4.2 AOP切面实现
 
 定义在 `alien-store/src/main/java/shop/alien/store/aspect/TrackEventAspect.java`
 
-### 4.3 Redis List 异步存储
+**主要功能**:
+- 拦截带有`@TrackEvent`注解的方法
+- 使用SpEL表达式从方法参数或返回值中提取数据
+- 自动获取IP地址、User-Agent、设备类型等信息
+- 支持业务逻辑兜底查询storeId(适用于SpEL无法直接获取的场景)
+
+### 4.3 WebSocket埋点实现
+
+定义在 `alien-store/src/main/java/shop/alien/store/config/WebSocketProcess.java`
+
+**支持的WebSocket埋点**:
+
+| 事件类型 | 触发条件 | 说明 |
+|---------|---------|------|
+| CONSULT | receiverId以`store_`开头的普通消息 | 用户咨询店铺 |
+| SHARE | 消息type为"3"(链接分享)或"14"(人员分享) | 用户分享店铺 |
+
+**WebSocket配置**:
+- `WebSocketConfig.java`: 配置WebSocket并提供自定义Configurator
+- `WebSocketConfigurator`: 在握手阶段获取IP地址和User-Agent
+
+### 4.4 Redis List 异步存储
 
 使用现有的 `BaseRedisService` 的 `setListRight` 方法将埋点数据写入Redis List
 
-### 4.4 定时任务消费
+**Redis Key**:
+- 埋点队列: `track:event:queue`
+- 消费锁: `track:event:consumer:lock`
+
+### 4.5 定时任务消费
 
 定义在 `alien-store/src/main/java/shop/alien/store/service/TrackEventConsumer.java`
 
 使用 `@Scheduled` 注解定时从Redis List批量取出数据并写入数据库
 
-### 4.5 前端上报接口
+**消费策略**:
+- 消费频率: 每10秒执行一次
+- 使用分布式锁防止多实例重复消费
+- 批量保存到数据库
+
+### 4.6 前端上报接口
 
 定义在 `alien-store/src/main/java/shop/alien/store/controller/TrackEventController.java`
 
-### 4.6 统计查询接口
+## 五、埋点接口清单
 
-定义在 `alien-store/src/main/java/shop/alien/store/controller/BusinessDataController.java`
+### 5.1 HTTP接口埋点(16个)
 
-### 4.7 AI推荐接口
+#### 流量数据(TRAFFIC)- 1个
 
-定义在 `alien-store/src/main/java/shop/alien/store/controller/AIRecoveryController.java`
+| 接口路径 | 方法名 | 事件类型 |
+|---------|--------|---------|
+| `/aiSearch/search` | search | SEARCH |
 
-## 五、前端联调
+#### 互动数据(INTERACTION)- 9个
 
-### 5.1 前端需要的后端接口
+| 接口路径 | 方法名 | 事件类型 |
+|---------|--------|---------|
+| `/collect/addCollect` | addCollect | COLLECT |
+| `/storeClockIn/addStoreClockIn` | addStoreClockIn | CHECKIN |
+| `/renovation/requirement/consultRequirement` | consultRequirement | CONSULT |
+| `/userDynamics/addOrUpdate` | addOrUpdate | POST_PUBLISH |
+| `/userDynamics/addTransferCount` | addTransferCount | POST_REPOST |
+| `/comment/like` | like | POST_LIKE |
+| `/commonComment/addComment` | addComment | POST_COMMENT |
+| `/user-violation/reporting` | reporting | REPORT |
+| `/life-blacklist/blackList` | blackList | BLOCK |
 
-#### 5.1.1 埋点上报接口
+#### 服务质量(SERVICE)- 2个
+
+| 接口路径 | 方法名 | 事件类型 |
+|---------|--------|---------|
+| `/commonRating/addRating` | add | RATING_ADD |
+| `/storeCommentAppeal/addAppealNew` | addAppealNew | APPEAL |
+
+#### 价目表(PRICE)- 2个
+
+| 接口路径 | 方法名 | 事件类型 |
+|---------|--------|---------|
+| `/store/price/getById` | getById | PRICE_VIEW |
+| `/store/cuisine/getByCuisineType` | getByCuisineType | PRICE_VIEW |
+
+#### 优惠券(COUPON)- 2个
+
+| 接口路径 | 方法名 | 事件类型 |
+|---------|--------|---------|
+| `/life-discount-coupon-store-friend/setFriendCoupon` | setFriendCoupon | COUPON_GIVE |
+| `/coupon/verify` | verify | COUPON_USE |
+
+### 5.2 WebSocket埋点(2个)
+
+| 触发场景 | 消息类型 | 事件类型 | 说明 |
+|---------|---------|---------|------|
+| 用户咨询店铺 | WebSocket消息 | CONSULT | receiverId以store_开头时触发 |
+| 用户分享店铺 | type="3"或"14" | SHARE | 从消息内容中解析storeId |
+
+### 5.3 统计汇总
+
+- **总埋点数**: 18个
+- **HTTP接口**: 16个
+- **WebSocket埋点**: 2个
+
+## 六、前端联调
+
+### 6.1 前端主动调用埋点上报接口
 
 **接口路径**: `POST /track/event`
 
@@ -226,156 +330,36 @@ CREATE TABLE `store_track_statistics` (
 }
 ```
 
-#### 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 数据对比接口
+#### 6.2 手动触发统计接口(测试用)
 
-**接口路径**: `GET /business/data/compare`
+**接口路径**: `POST /track/statistics/calculate`
 
-**接口说明**: 对比两个时间段的数据
+**接口说明**: 手动触发统计数据计算
 
 **请求参数**:
 - `storeId`: 店铺ID (必填)
-- `startDate1`: 时间段1开始日期
-- `endDate1`: 时间段1结束日期
-- `startDate2`: 时间段2开始日期
-- `endDate2`: 时间段2结束日期
+- `statDate`: 统计日期 (格式: yyyy-MM-dd)
+- `statType`: 统计类型 (DAILY/WEEKLY/MONTHLY)
 
 **响应示例**:
 ```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": "寻找原材料更便宜的菜场、菜量降低、菜名突出特色,如锡林郭勒盟羔羊烤羊腿"
-      }
-    ]
-  }
+  "msg": "统计数据计算成功",
+  "data": null
 }
 ```
 
-### 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", "打卡"),
@@ -389,61 +373,89 @@ public enum EventType {
     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", "评价"),
+    
+    // 服务质量
+    RATING_ADD("RATING_ADD", "添加评价"),
     APPEAL("APPEAL", "申诉");
 }
 ```
 
-### 6.2 Redis Key 设计
-
-- 埋点队列: `track:event:queue`
-- 消费锁: `track:event:consumer:lock`
-
-### 6.3 消费策略
+## 八、统计数据说明
 
-- 每次消费数量: 100条
-- 消费频率: 每10秒执行一次
-- 使用分布式锁防止多实例重复消费
+### 8.1 数据统计规则
 
-## 七、AI推荐实现
+| 数据类型 | 统计规则 | 说明 |
+|---------|---------|------|
+| 大部分数据 | **累计统计** | 统计截止到统计日期的所有数据 |
+| 新增访客数 | **增量统计** | 统计周期内的新增访客(排除之前访问过的用户) |
 
-### 7.1 AI推荐流程
+### 8.2 统计时间范围计算
 
-1. 收集店铺的埋点数据
-2. 调用AI服务分析数据
-3. 对比同行业、同星级店铺数据
-4. 生成推荐建议
+| 统计类型 | 开始时间 | 结束时间 |
+|---------|---------|---------|
+| DAILY | statDate 00:00:00 | statDate+1 00:00:00 |
+| WEEKLY | statDate所在周的周一 00:00:00 | 周日+1 00:00:00 |
+| MONTHLY | statDate所在月的1号 00:00:00 | 下月1号 00:00:00 |
 
-### 7.2 AI接口调用
+### 8.3 JSON格式说明
 
-使用现有的 `AlienAIFeign` 调用AI服务,需要新增推荐接口。
+详见:[埋点统计数据JSON格式说明.md](./埋点统计数据JSON格式说明.md)
 
-## 、部署和运维
+## 、部署和运维
 
-### 8.1 配置项
+### 9.1 配置项
 
 - Redis List最大长度: 100000
 - 批量消费大小: 100
 - 消费间隔: 10秒
 - 统计数据保留期: 2年
 
-### 8.2 监控指标
+### 9.2 监控指标
 
 - Redis List长度
 - 消费延迟
 - 消费失败率
 - 统计查询响应时间
 
-## 九、注意事项
+### 9.3 Maven编译配置
+
+确保pom.xml中配置了`-parameters`参数,以保留方法参数名称(SpEL表达式需要):
+
+```xml
+<plugin>
+    <groupId>org.apache.maven.plugins</groupId>
+    <artifactId>maven-compiler-plugin</artifactId>
+    <configuration>
+        <source>1.8</source>
+        <target>1.8</target>
+        <parameters>true</parameters>
+    </configuration>
+</plugin>
+```
+
+## 十、注意事项
+
+1. **埋点数据量可能很大**,需要定期清理历史数据
+2. **Redis List需要设置最大长度**,防止内存溢出
+3. **消费服务需要异常处理和重试机制**
+4. **统计数据建议使用定时任务预计算**,提升查询性能
+5. **AI推荐接口需要缓存**,避免频繁调用AI服务
+6. **WebSocket埋点通过握手阶段获取IP和User-Agent**,需要配置自定义Configurator
+7. **SpEL表达式无法直接获取storeId时**,切面会通过业务逻辑兜底查询
+
+---
 
-1. 埋点数据量可能很大,需要定期清理历史数据
-2. Redis List需要设置最大长度,防止内存溢出
-3. 消费服务需要异常处理和重试机制
-4. 统计数据建议使用定时任务预计算,提升查询性能
-5. AI推荐接口需要缓存,避免频繁调用AI服务
+**文档版本**:v2.0  
+**最后更新**:2026-01-24

+ 57 - 3
alien-store/src/main/java/shop/alien/store/aspect/TrackEventAspect.java

@@ -52,6 +52,7 @@ public class TrackEventAspect {
     private final StoreUserMapper storeUserMapper;
     private final StorePriceMapper storePriceMapper;
     private final StoreCuisineMapper storeCuisineMapper;
+    private final LifeUserMapper lifeUserMapper;
     
     private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
     private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@@ -163,9 +164,15 @@ public class TrackEventAspect {
 
         // 解析SpEL表达式获取userId
         Integer userId = parseSpEL(annotation.userId(), context, Integer.class);
-        if (userId == null && StringUtils.isBlank(annotation.userId())) {
-            // 从JWT中获取用户ID
-            userId = getUserIdFromJWT();
+        if (userId == null) {
+            // SpEL解析失败,尝试从phoneId格式的字符串中解析用户ID
+            if (StringUtils.isNotBlank(annotation.userId())) {
+                userId = queryUserIdByPhoneId(context, annotation.userId());
+            }
+            // 如果仍然为空,尝试从JWT中获取用户ID
+            if (userId == null) {
+                userId = getUserIdFromJWT();
+            }
         }
         trackEvent.setUserId(userId);
 
@@ -741,6 +748,53 @@ public class TrackEventAspect {
     }
 
     /**
+     * 从phoneId格式的字符串中解析用户ID
+     * phoneId格式:user_手机号 或 store_手机号
+     */
+    private Integer queryUserIdByPhoneId(EvaluationContext context, String userIdExpression) {
+        try {
+            // 先尝试从SpEL表达式中获取原始值(String类型)
+            String phoneId = parseSpEL(userIdExpression, context, String.class);
+            if (phoneId == null) {
+                return null;
+            }
+            
+            // 判断是普通用户还是店铺用户
+            if (phoneId.startsWith("user_")) {
+                // 普通用户:user_手机号
+                String phone = phoneId.substring(5); // 去掉 "user_" 前缀
+                LambdaQueryWrapper<LifeUser> wrapper = new LambdaQueryWrapper<>();
+                wrapper.eq(LifeUser::getUserPhone, phone)
+                        .eq(LifeUser::getDeleteFlag, 0);
+                LifeUser lifeUser = lifeUserMapper.selectOne(wrapper);
+                if (lifeUser != null && lifeUser.getId() != null) {
+                    return lifeUser.getId();
+                }
+            } else if (phoneId.startsWith("store_")) {
+                // 店铺用户:store_手机号
+                String phone = phoneId.substring(6); // 去掉 "store_" 前缀
+                LambdaQueryWrapper<StoreUser> wrapper = new LambdaQueryWrapper<>();
+                wrapper.eq(StoreUser::getPhone, phone)
+                        .eq(StoreUser::getDeleteFlag, 0);
+                StoreUser storeUser = storeUserMapper.selectOne(wrapper);
+                if (storeUser != null && storeUser.getId() != null) {
+                    return storeUser.getId();
+                }
+            } else {
+                // 如果不是phoneId格式,尝试直接转换为Integer
+                try {
+                    return Integer.parseInt(phoneId);
+                } catch (NumberFormatException e) {
+                    log.debug("无法将userId转换为Integer: {}", phoneId);
+                }
+            }
+        } catch (Exception e) {
+            log.debug("从phoneId解析用户ID失败: expression={}", userIdExpression, e);
+        }
+        return null;
+    }
+    
+    /**
      * 从JWT中获取用户ID
      */
     private Integer getUserIdFromJWT() {

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

@@ -34,12 +34,6 @@ public class CommentAppealController {
 
     private final CommentAppealService commentAppealService;
 
-    @TrackEvent(
-            eventType = "APPEAL",
-            eventCategory = "SERVICE",
-            storeId = "#{#commentAppeal.storeId}",
-            targetType = "COMMENT"
-    )
     @ApiOperation(value = "提交申诉", notes = "用户提交评论申诉")
     @ApiOperationSupport(order = 1)
     @ApiImplicitParams({

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

@@ -25,7 +25,8 @@ public class StoreClockInController {
             eventType = "CHECKIN",
             eventCategory = "INTERACTION",
             storeId = "#{#storeClockIn.storeId}",
-            targetType = "STORE"
+            targetType = "STORE",
+            targetId = "#{#storeClockIn.id}"
     )
     @ApiOperation("打卡")
     @ApiOperationSupport(order = 1)

+ 9 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreCommentAppealController.java

@@ -11,6 +11,7 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreCommentAppeal;
 import shop.alien.entity.store.vo.StoreCommentAppealInfoVo;
 import shop.alien.entity.store.vo.StoreCommentAppealVo;
+import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.StoreCommentAppealService;
 
 import java.util.Set;
@@ -44,6 +45,14 @@ public class StoreCommentAppealController {
         return R.data(storeCommentAppealService.getAppealHistory(pageNum, pageSize, storeId, appealStatus));
     }
 
+    @TrackEvent(
+            eventType = "APPEAL",
+            eventCategory = "SERVICE",
+            storeId = "#{#storeId}",
+            userId = "",
+            targetId = "#{#commentId}",
+            targetType = "STORE"
+    )
     @ApiOperationSupport(order = 2)
     @ApiOperation(value = "商家端-新增申诉(0:申诉成功, 1:申诉失败, 2:申诉已存在)")
     @ApiImplicitParams({@ApiImplicitParam(name = "multipartRequest", value = "文件", dataType = "File", paramType = "query", required = true),