Sfoglia il codice sorgente

Merge remote-tracking branch 'origin/sit' into sit

panzhilin 2 mesi fa
parent
commit
b5f3d14802
35 ha cambiato i file con 3257 aggiunte e 571 eliminazioni
  1. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreOperationalStatisticsHistory.java
  2. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreRenovationRequirementDto.java
  3. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeBlacklistVo.java
  4. 33 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreBusinessStatusVo.java
  5. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOperationalStatisticsComparisonVo.java
  6. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOperationalStatisticsVo.java
  7. 7 19
      alien-entity/src/main/java/shop/alien/mapper/StoreTrackEventMapper.java
  8. 29 21
      alien-store/doc/埋点接口清单.md
  9. 11 18
      alien-store/doc/埋点统计数据JSON格式说明.md
  10. 3 3
      alien-store/doc/埋点需求完整方案.md
  11. 53 0
      alien-store/src/main/java/shop/alien/store/aspect/TrackEventAspect.java
  12. 80 0
      alien-store/src/main/java/shop/alien/store/config/WebSocketConfig.java
  13. 242 4
      alien-store/src/main/java/shop/alien/store/config/WebSocketProcess.java
  14. 12 2
      alien-store/src/main/java/shop/alien/store/controller/LifeBlacklistController.java
  15. 7 0
      alien-store/src/main/java/shop/alien/store/controller/LifeUserDynamicsController.java
  16. 176 2
      alien-store/src/main/java/shop/alien/store/controller/StoreFileUploadController.java
  17. 10 0
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  18. 53 3
      alien-store/src/main/java/shop/alien/store/controller/StoreOperationalStatisticsController.java
  19. 9 1
      alien-store/src/main/java/shop/alien/store/service/LifeBlacklistService.java
  20. 3 4
      alien-store/src/main/java/shop/alien/store/service/LifeUserDynamicsService.java
  21. 9 0
      alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java
  22. 19 0
      alien-store/src/main/java/shop/alien/store/service/StoreOperationalStatisticsService.java
  23. 3 2
      alien-store/src/main/java/shop/alien/store/service/impl/CommonCommentServiceImpl.java
  24. 12 12
      alien-store/src/main/java/shop/alien/store/service/impl/LifeBlacklistServiceImpl.java
  25. 2 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeMessageServiceImpl.java
  26. 3 1
      alien-store/src/main/java/shop/alien/store/service/impl/PerformanceListServiceImpl.java
  27. 512 326
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  28. 823 60
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalStatisticsServiceImpl.java
  29. 31 1
      alien-store/src/main/java/shop/alien/store/service/impl/StoreRenovationRequirementServiceImpl.java
  30. 72 92
      alien-store/src/main/java/shop/alien/store/service/impl/TrackEventServiceImpl.java
  31. 7 0
      alien-store/src/main/java/shop/alien/store/util/CommonConstant.java
  32. 33 0
      alien-store/src/main/java/shop/alien/store/util/FileUploadUtil.java
  33. 242 0
      alien-store/src/main/java/shop/alien/store/util/ImageToPdfUploadUtil.java
  34. 63 0
      alien-util/src/main/java/shop/alien/util/ali/AliOSSUtil.java
  35. 682 0
      alien-util/src/main/java/shop/alien/util/pdf/ImageToPdfUtil.java

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreOperationalStatisticsHistory.java

@@ -48,6 +48,10 @@ public class StoreOperationalStatisticsHistory {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date queryTime;
 
+    @ApiModelProperty(value = "PDF文件URL(前端生成的PDF文件地址)")
+    @TableField("pdf_url")
+    private String pdfUrl;
+
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreRenovationRequirementDto.java

@@ -104,6 +104,9 @@ public class StoreRenovationRequirementDto {
     @ApiModelProperty(value = "商铺简介")
     private String storeBlurb;
 
+    @ApiModelProperty(value = "商铺头像")
+    private String storeAvatar;
+
     @ApiModelProperty(value = "是否已与发布商铺发生沟通(true:已沟通, false:未沟通)")
     private Boolean hasCommunicated;
 }

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeBlacklistVo.java

@@ -7,6 +7,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 import shop.alien.entity.store.LifeBlacklist;
+import shop.alien.entity.store.StoreInfo;
 
 /**
  * @Author: fcw
@@ -29,4 +30,6 @@ public class LifeBlacklistVo extends LifeBlacklist {
     @ApiModelProperty(value = "用户头像")
     private String phoneId;
 
+    @ApiModelProperty(value = "店铺信息")
+    private StoreInfo storeInfo;
 }

+ 33 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreBusinessStatusVo.java

@@ -0,0 +1,33 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.StoreBusinessInfo;
+
+import java.util.List;
+
+/**
+ * 店铺营业状态VO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreBusinessStatusVo对象", description = "店铺营业状态")
+public class StoreBusinessStatusVo {
+
+    @ApiModelProperty(value = "是否营业中(0否1是)")
+    private Integer yyFlag;
+
+    @ApiModelProperty(value = "当前营业时间信息")
+    private StoreBusinessInfo storeBusinessInfo;
+
+    @ApiModelProperty(value = "所有营业时间信息列表(已排序)")
+    private List<StoreBusinessInfo> storeBusinessInfos;
+
+    @ApiModelProperty(value = "店铺营业状态(0:正常营业, 其他:非正常营业)")
+    private Integer businessStatus;
+}

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOperationalStatisticsComparisonVo.java

@@ -32,6 +32,9 @@ public class StoreOperationalStatisticsComparisonVo implements Serializable {
     @ApiModelProperty("上期结束时间")
     private String previousEndTime;
 
+    @ApiModelProperty("历史记录ID(用于后续更新PDF URL)")
+    private Integer historyId;
+
     /**
      * 流量数据对比
      */

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreOperationalStatisticsVo.java

@@ -254,6 +254,9 @@ public class StoreOperationalStatisticsVo implements Serializable {
         @ApiModelProperty("排名")
         private Integer rank;
 
+        @ApiModelProperty("价目表ID")
+        private Integer priceId;
+
         @ApiModelProperty("价目表名称")
         private String priceListItemName;
 

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

@@ -20,22 +20,20 @@ import java.util.Map;
 public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
     
     /**
-     * 统计访客数(去重userId)- 使用数据库聚合查询
+     * 统计访客数(去重userId)
      */
     @Select("SELECT COUNT(DISTINCT user_id) as visitorCount " +
             "FROM store_track_event " +
             "WHERE store_id = #{storeId} " +
-            "AND event_time >= #{startDate} " +
             "AND event_time < #{endDate} " +
             "AND delete_flag = 0 " +
             "AND event_category = 'TRAFFIC' " +
             "AND user_id IS NOT NULL")
     Long countDistinctVisitors(@Param("storeId") Integer storeId,
-                               @Param("startDate") Date startDate,
                                @Param("endDate") Date endDate);
     
     /**
-     * 统计新增访客数 - 使用数据库聚合查询
+     * 统计新增访客数(统计时间段内首次访问的用户)
      */
     @Select("SELECT COUNT(DISTINCT t1.user_id) as newVisitorCount " +
             "FROM store_track_event t1 " +
@@ -57,7 +55,7 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
                           @Param("endDate") Date endDate);
     
     /**
-     * 统计流量数据:搜索量、浏览量、访问时长 - 使用数据库聚合查询
+     * 统计流量数据:搜索量、浏览量、访问时长
      */
     @Select("SELECT " +
             "    SUM(CASE WHEN event_type = 'SEARCH' THEN 1 ELSE 0 END) as searchCount, " +
@@ -66,16 +64,14 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
             "    COALESCE(AVG(CASE WHEN event_type = 'VIEW' AND duration IS NOT NULL THEN duration END), 0) as avgDuration " +
             "FROM store_track_event " +
             "WHERE store_id = #{storeId} " +
-            "AND event_time >= #{startDate} " +
             "AND event_time < #{endDate} " +
             "AND delete_flag = 0 " +
             "AND event_category = 'TRAFFIC'")
     Map<String, Object> calculateTrafficStatistics(@Param("storeId") Integer storeId,
-                                                    @Param("startDate") Date startDate,
                                                     @Param("endDate") Date endDate);
     
     /**
-     * 按价目表ID统计浏览量、访客数、分享数 - 使用数据库聚合查询
+     * 按价目表ID统计浏览量、访客数、分享数
      */
     @Select("SELECT " +
             "    target_id as priceId, " +
@@ -84,7 +80,6 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
             "    SUM(CASE WHEN event_type = 'PRICE_SHARE' THEN 1 ELSE 0 END) as shareCount " +
             "FROM store_track_event " +
             "WHERE store_id = #{storeId} " +
-            "AND event_time >= #{startDate} " +
             "AND event_time < #{endDate} " +
             "AND delete_flag = 0 " +
             "AND event_category = 'PRICE' " +
@@ -92,11 +87,10 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
             "GROUP BY target_id " +
             "ORDER BY viewCount DESC")
     List<Map<String, Object>> calculatePriceRanking(@Param("storeId") Integer storeId,
-                                                     @Param("startDate") Date startDate,
                                                      @Param("endDate") Date endDate);
     
     /**
-     * 统计互动数据(按事件类型统计数量)- 使用数据库聚合查询
+     * 统计互动数据(按事件类型统计数量)
      * 从埋点事件表store_track_event中统计互动相关事件
      */
     @Select("SELECT " +
@@ -105,17 +99,15 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
             "FROM store_track_event " +
             "WHERE store_id = #{storeId} " +
             "AND store_id IS NOT NULL " +
-            "AND event_time >= #{startDate} " +
             "AND event_time < #{endDate} " +
             "AND delete_flag = 0 " +
             "AND event_category = 'INTERACTION' " +
             "GROUP BY event_type")
     List<Map<String, Object>> calculateInteractionStatistics(@Param("storeId") Integer storeId,
-                                                              @Param("startDate") Date startDate,
                                                               @Param("endDate") Date endDate);
     
     /**
-     * 统计代金券数据(按事件类型统计数量和金额)- 使用数据库聚合查询
+     * 统计代金券数据(按事件类型统计数量和金额)
      */
     @Select("SELECT " +
             "    event_type, " +
@@ -123,30 +115,26 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
             "    COALESCE(SUM(amount), 0) as totalAmount " +
             "FROM store_track_event " +
             "WHERE store_id = #{storeId} " +
-            "AND event_time >= #{startDate} " +
             "AND event_time < #{endDate} " +
             "AND delete_flag = 0 " +
             "AND event_category = 'VOUCHER' " +
             "AND event_type IN ('VOUCHER_GIVE', 'VOUCHER_USE') " +
             "GROUP BY event_type")
     List<Map<String, Object>> calculateVoucherStatistics(@Param("storeId") Integer storeId,
-                                                          @Param("startDate") Date startDate,
                                                           @Param("endDate") Date endDate);
     
     /**
-     * 统计服务质量数据(按事件类型统计数量)- 使用数据库聚合查询
+     * 统计服务质量数据(按事件类型统计数量)
      */
     @Select("SELECT " +
             "    event_type, " +
             "    COUNT(*) as count " +
             "FROM store_track_event " +
             "WHERE store_id = #{storeId} " +
-            "AND event_time >= #{startDate} " +
             "AND event_time < #{endDate} " +
             "AND delete_flag = 0 " +
             "AND event_category = 'SERVICE' " +
             "GROUP BY event_type")
     List<Map<String, Object>> calculateServiceStatistics(@Param("storeId") Integer storeId,
-                                                          @Param("startDate") Date startDate,
                                                           @Param("endDate") Date endDate);
 }

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

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

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

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

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

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

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

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

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

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

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

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

+ 12 - 2
alien-store/src/main/java/shop/alien/store/controller/LifeBlacklistController.java

@@ -57,11 +57,21 @@ public class LifeBlacklistController {
         }
     }
 
+    /**
+     * 根据用户ID查询拉黑列表
+     *
+     * @param userType 用户类型
+     * @param userId   用户ID
+     * @param type     查找类型 1-被拉黑商户列表 2-拉黑用户列表
+     * @return 拉黑列表
+     */
     @ApiOperation("拉黑列表")
     @ApiOperationSupport(order = 2)
     @GetMapping("/blackListByUserId")
-    public R<List<LifeBlacklistVo>> blackListByUserId(@RequestParam(value = "userType") String userType, @RequestParam(value = "userId") String userId) {
-        return R.data(lifeBlacklistService.blackListByUserId(userType, userId));
+    public R<List<LifeBlacklistVo>> blackListByUserId(@RequestParam(value = "userType") String userType,
+                                                      @RequestParam(value = "userId") String userId,
+                                                      @RequestParam(value = "type", defaultValue = "1") Integer type) {
+        return R.data(lifeBlacklistService.blackListByUserId(userType, userId, type));
     }
 
     @ApiOperation("是否为已拉黑")

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

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

+ 176 - 2
alien-store/src/main/java/shop/alien/store/controller/StoreFileUploadController.java

@@ -1,17 +1,22 @@
 package shop.alien.store.controller;
 
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiOperationSupport;
 import io.swagger.annotations.ApiSort;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartRequest;
 import shop.alien.entity.result.R;
 import shop.alien.store.util.FileUploadUtil;
+import shop.alien.store.util.ImageToPdfUploadUtil;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -31,8 +36,10 @@ import java.util.List;
 public class StoreFileUploadController {
 
     private final FileUploadUtil fileUpload;
+    
+    private final ImageToPdfUploadUtil imageToPdfUploadUtil;
 
-    @ApiOperation("单个文件上传(图片或视频,返回路径)")
+    @ApiOperation("单个文件上传(图片、视频或PDF,返回路径)")
     @ApiOperationSupport(order = 1)
     @PostMapping("/upload")
     public R<String> upload(@RequestParam("file") MultipartFile file) {
@@ -40,7 +47,7 @@ public class StoreFileUploadController {
         return R.data(fileUpload.uploadOneFile(file));
     }
 
-    @ApiOperation("多个文件上传(图片或视频,视频会截取第一秒图片,返回路径)")
+    @ApiOperation("多个文件上传(图片、视频或PDF,视频会截取第一秒图片,返回路径)")
     @ApiOperationSupport(order = 2)
     @PostMapping("/uploadMore")
     public R<List<String>> uploadMore(MultipartRequest multipartRequest) {
@@ -63,4 +70,171 @@ public class StoreFileUploadController {
         return R.data(fileUpload.uploadApp(file));
     }
 
+    @ApiOperation("上传PDF文件(返回路径)")
+    @ApiOperationSupport(order = 5)
+    @PostMapping("/uploadPdf")
+    public R<String> uploadPdf(@RequestParam("file") MultipartFile file) {
+        log.info("StoreFileUploadController.uploadPdf fileName:{}", file.getOriginalFilename());
+        try {
+            String filePath = fileUpload.uploadPdf(file);
+            if (filePath == null) {
+                return R.fail("上传失败,文件不是PDF格式");
+            }
+            return R.data(filePath);
+        } catch (Exception e) {
+            log.error("上传PDF文件失败: {}", e.getMessage(), e);
+            return R.fail("上传失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("图片转PDF并上传(单张图片,返回PDF的线上URL)")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "file",
+                    value = "图片文件(支持jpg、jpeg、png、bmp、webp、gif格式)",
+                    dataType = "file",
+                    paramType = "form",
+                    required = true
+            )
+    })
+    @PostMapping("/imageToPdf")
+    public R<String> imageToPdf(@RequestParam("file") MultipartFile file) {
+        log.info("StoreFileUploadController.imageToPdf fileName:{}", file.getOriginalFilename());
+        try {
+            // 参数验证
+            if (file == null || file.isEmpty()) {
+                return R.fail("请选择要转换的图片文件");
+            }
+            
+            String pdfUrl = imageToPdfUploadUtil.imageToPdfAndUpload(file);
+            if (pdfUrl == null) {
+                return R.fail("图片转PDF并上传失败,请检查图片格式是否正确");
+            }
+            return R.data(pdfUrl);
+        } catch (IllegalArgumentException e) {
+            log.warn("图片转PDF参数错误: {}", e.getMessage());
+            return R.fail("参数错误: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("图片转PDF并上传失败: {}", e.getMessage(), e);
+            return R.fail("图片转PDF并上传失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("图片转PDF并上传(多张图片合并为一个PDF,返回PDF的线上URL)")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "files",
+                    value = "图片文件列表(支持jpg、jpeg、png、bmp、webp、gif格式,可上传多张)",
+                    dataType = "file",
+                    paramType = "form",
+                    required = true,
+                    allowMultiple = true
+            )
+    })
+    @PostMapping("/imagesToPdf")
+    public R<String> imagesToPdf(MultipartRequest multipartRequest) {
+        log.info("StoreFileUploadController.imagesToPdf");
+        try {
+            if (multipartRequest == null) {
+                return R.fail("请求参数不能为空");
+            }
+            
+            List<MultipartFile> imageFiles = new ArrayList<>();
+            for (String key : multipartRequest.getFileMap().keySet()) {
+                MultipartFile file = multipartRequest.getFile(key);
+                if (file != null && !file.isEmpty()) {
+                    imageFiles.add(file);
+                }
+            }
+            
+            if (imageFiles.isEmpty()) {
+                return R.fail("请至少上传一张图片");
+            }
+            
+            log.info("StoreFileUploadController.imagesToPdf 收到{}张图片", imageFiles.size());
+            
+            String pdfUrl = imageToPdfUploadUtil.imagesToPdfAndUpload(imageFiles);
+            if (pdfUrl == null) {
+                return R.fail("图片转PDF并上传失败,请检查图片格式是否正确");
+            }
+            return R.data(pdfUrl);
+        } catch (IllegalArgumentException e) {
+            log.warn("图片转PDF参数错误: {}", e.getMessage());
+            return R.fail("参数错误: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("图片转PDF并上传失败: {}", e.getMessage(), e);
+            return R.fail("图片转PDF并上传失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("图片转PDF并上传(A4页面大小,单张图片,返回PDF的线上URL)")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "file",
+                    value = "图片文件(支持jpg、jpeg、png、bmp、webp、gif格式,图片会自动缩放适应A4页面)",
+                    dataType = "file",
+                    paramType = "form",
+                    required = true
+            )
+    })
+    @PostMapping("/imageToPdfA4")
+    public R<String> imageToPdfA4(@RequestParam("file") MultipartFile file) {
+        log.info("StoreFileUploadController.imageToPdfA4 fileName:{}", file.getOriginalFilename());
+        try {
+            // 参数验证
+            if (file == null || file.isEmpty()) {
+                return R.fail("请选择要转换的图片文件");
+            }
+            
+            String pdfUrl = imageToPdfUploadUtil.imageToPdfA4AndUpload(file);
+            if (pdfUrl == null) {
+                return R.fail("图片转PDF并上传失败,请检查图片格式是否正确");
+            }
+            return R.data(pdfUrl);
+        } catch (IllegalArgumentException e) {
+            log.warn("图片转PDF参数错误: {}", e.getMessage());
+            return R.fail("参数错误: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("图片转PDF并上传失败: {}", e.getMessage(), e);
+            return R.fail("图片转PDF并上传失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("Base64图片转PDF并上传(返回PDF的线上URL)")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "base64Image",
+                    value = "Base64编码的图片字符串(支持带或不带data:image前缀,如:data:image/png;base64,iVBORw0KGgo...)",
+                    dataType = "String",
+                    paramType = "form",
+                    required = true
+            )
+    })
+    @PostMapping("/base64ToPdf")
+    public R<String> base64ToPdf(@RequestParam("base64Image") String base64Image) {
+        log.info("StoreFileUploadController.base64ToPdf 收到Base64图片");
+        try {
+            // 参数验证
+            if (!StringUtils.hasText(base64Image)) {
+                return R.fail("Base64图片字符串不能为空");
+            }
+
+            String pdfUrl = imageToPdfUploadUtil.base64ToPdfAndUpload(base64Image);
+            if (pdfUrl == null) {
+                return R.fail("Base64图片转PDF并上传失败,请检查Base64格式是否正确");
+            }
+            return R.data(pdfUrl);
+        } catch (IllegalArgumentException e) {
+            log.warn("Base64图片转PDF参数错误: {}", e.getMessage());
+            return R.fail("参数错误: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("Base64图片转PDF并上传失败: {}", e.getMessage(), e);
+            return R.fail("Base64图片转PDF并上传失败: " + e.getMessage());
+        }
+    }
+
 }

+ 10 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -1041,6 +1041,16 @@ public class StoreInfoController {
         return R.data(storeDetail);
     }
 
+    @ApiOperation(value = "获取店铺营业状态", notes = "判断店铺当前是否在营业时间内,返回营业状态和营业时间信息")
+    @ApiOperationSupport(order = 17)
+    @GetMapping("/getStoreBusinessStatus")
+    @ResponseBody
+    public R<StoreBusinessStatusVo> getStoreBusinessStatus(@RequestParam("id") String id) {
+        log.info("StoreInfoController.getStoreBusinessStatus?id={}", id);
+        StoreBusinessStatusVo businessStatus = storeInfoService.getStoreBusinessStatus(id);
+        return R.data(businessStatus);
+    }
+
     @ApiOperation(value = "查询当前门店的资质信息", notes = "查询营业执照和其他资质证明图片")
     @ApiOperationSupport(order = 18)
     @GetMapping("/getStoreQualificationInfo")

+ 53 - 3
alien-store/src/main/java/shop/alien/store/controller/StoreOperationalStatisticsController.java

@@ -30,7 +30,7 @@ public class StoreOperationalStatisticsController {
 
     private final StoreOperationalStatisticsService storeOperationalStatisticsService;
 
-    @ApiOperation("获取商家经营统计数据")
+    @ApiOperation("获取商家经营统计数据(符合埋点统计数据JSON格式)")
     @ApiOperationSupport(order = 1)
     @ApiImplicitParams({
             @ApiImplicitParam(
@@ -61,8 +61,14 @@ public class StoreOperationalStatisticsController {
             @RequestParam("startTime") String startTime,
             @RequestParam("endTime") String endTime) {
         log.info("StoreOperationalStatisticsController.getStatistics - storeId={}, startTime={}, endTime={}", storeId, startTime, endTime);
-        StoreOperationalStatisticsVo statistics = storeOperationalStatisticsService.getStatistics(storeId, startTime, endTime);
-        return R.data(statistics);
+        try {
+            StoreOperationalStatisticsVo statistics = storeOperationalStatisticsService.getStatisticsInTrackFormat(storeId, startTime, endTime);
+            return R.data(statistics);
+        } catch (Exception e) {
+            log.error("获取商家经营统计数据失败 - storeId={}, startTime={}, endTime={}, error={}", 
+                    storeId, startTime, endTime, e.getMessage(), e);
+            return R.fail("获取统计数据失败: " + e.getMessage());
+        }
     }
 
     @ApiOperation("获取商家经营统计数据对比")
@@ -222,4 +228,48 @@ public class StoreOperationalStatisticsController {
             return R.fail("批量删除失败: " + e.getMessage());
         }
     }
+
+    @ApiOperation("更新历史统计记录的PDF URL")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "historyId",
+                    value = "历史记录ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "pdfUrl",
+                    value = "PDF文件URL(前端生成的PDF文件地址)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true
+            )
+    })
+    @PutMapping("/history/updatePdfUrl")
+    public R<String> updateHistoryPdfUrl(
+            @RequestParam("historyId") Integer historyId,
+            @RequestParam("pdfUrl") String pdfUrl) {
+        log.info("StoreOperationalStatisticsController.updateHistoryPdfUrl - historyId={}, pdfUrl={}", historyId, pdfUrl);
+        try {
+            if (historyId == null || historyId <= 0) {
+                return R.fail("历史记录ID不能为空且必须大于0");
+            }
+            
+            if (pdfUrl == null || pdfUrl.trim().isEmpty()) {
+                return R.fail("PDF URL不能为空");
+            }
+            
+            boolean result = storeOperationalStatisticsService.updateHistoryPdfUrl(historyId, pdfUrl);
+            if (result) {
+                return R.success("更新PDF URL成功");
+            } else {
+                return R.fail("更新PDF URL失败,历史记录不存在或已被删除");
+            }
+        } catch (Exception e) {
+            log.error("更新历史统计记录PDF URL失败 - historyId={}, pdfUrl={}, error={}", historyId, pdfUrl, e.getMessage(), e);
+            return R.fail("更新PDF URL失败: " + e.getMessage());
+        }
+    }
 }

+ 9 - 1
alien-store/src/main/java/shop/alien/store/service/LifeBlacklistService.java

@@ -19,7 +19,15 @@ public interface LifeBlacklistService extends IService<LifeBlacklist> {
 
     int blackList(LifeBlacklist lifeBlacklist);
 
-    List<LifeBlacklistVo> blackListByUserId(String userType, String userId);
+    /**
+     * 根据用户ID查询拉黑列表
+     *
+     * @param userType 用户类型
+     * @param userId   用户ID
+     * @param type     查找类型
+     * @return 拉黑列表
+     */
+    List<LifeBlacklistVo> blackListByUserId(String userType, String userId, Integer type);
 
     Boolean isBlackListUser(LifeBlacklist lifeBlacklist);
 

+ 3 - 4
alien-store/src/main/java/shop/alien/store/service/LifeUserDynamicsService.java

@@ -570,18 +570,15 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
 
         // TODO  bugfix: 过滤掉我拉黑的和拉黑我的。
         //是否关注用户
-        String targetPhone;
         LifeUser myLifeUser = new LifeUser();
         StoreUser myStoreUser = new StoreUser();
 
         if ("user".equals(phoneId.split("_")[0])) {
             String myselfUserPhone = phoneId.split("_")[1];
             myLifeUser = lifeUserService.getUserByPhone(myselfUserPhone);
-            targetPhone = myLifeUser.getUserPhone();
         } else {
             String myselfStorePhone = phoneId.split("_")[1];
             myStoreUser = storeUserService.getUserByPhone(myselfStorePhone);
-            targetPhone = myStoreUser.getPhone();
         }
 
         Integer myType ;
@@ -620,7 +617,9 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
                 if (lifeUserDynamicsVo.getType().equals("2")){
                     String phoneIdNew = lifeUserDynamicsVo.getPhoneId().substring(6);
                      StoreInfo storeInfo=storeInfoMapper.getStoreNameByPhone(phoneIdNew);
-                    lifeUserDynamicsVo.setStoreName(storeInfo.getStoreName());
+                     if(storeInfo != null){
+                         lifeUserDynamicsVo.setStoreName(storeInfo.getStoreName());
+                     }
                 } else if (lifeUserDynamicsVo.getType().equals("1")) {
                     String phoneIdNew = lifeUserDynamicsVo.getPhoneId().substring(5);
                     //根据手机号查询用户表

+ 9 - 0
alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java

@@ -440,6 +440,15 @@ public interface StoreInfoService extends IService<StoreInfo> {
     StoreInfoVo getClientStoreDetail(String storeId, String userId, String jingdu, String weidu);
 
     /**
+     * 获取店铺营业状态
+     * 判断店铺当前是否在营业时间内,返回营业状态和营业时间信息
+     *
+     * @param storeId 店铺ID
+     * @return StoreBusinessStatusVo 营业状态信息
+     */
+    StoreBusinessStatusVo getStoreBusinessStatus(String storeId);
+
+    /**
      * 获取休闲娱乐分类数据(主分类和子分类)
      * 返回层级结构的分类数据,包含主分类和对应的子分类
      *

+ 19 - 0
alien-store/src/main/java/shop/alien/store/service/StoreOperationalStatisticsService.java

@@ -61,4 +61,23 @@ public interface StoreOperationalStatisticsService {
      * @return 是否成功
      */
     boolean batchDeleteHistory(List<Integer> ids);
+
+    /**
+     * 获取商家经营统计数据(符合埋点统计数据JSON格式)
+     *
+     * @param storeId   店铺ID
+     * @param startTime 开始时间(格式:yyyy-MM-dd)
+     * @param endTime   结束时间(格式:yyyy-MM-dd)
+     * @return 经营统计数据
+     */
+    StoreOperationalStatisticsVo getStatisticsInTrackFormat(Integer storeId, String startTime, String endTime);
+
+    /**
+     * 更新历史统计记录的PDF URL
+     *
+     * @param historyId 历史记录ID
+     * @param pdfUrl    PDF文件URL
+     * @return 是否成功
+     */
+    boolean updateHistoryPdfUrl(Integer historyId, String pdfUrl);
 }

+ 3 - 2
alien-store/src/main/java/shop/alien/store/service/impl/CommonCommentServiceImpl.java

@@ -274,10 +274,11 @@ public class CommonCommentServiceImpl extends ServiceImpl<CommonCommentMapper, C
             // 2.查询商家头像 TODO -> 动态发布的时候id不应该用Store_phone的格式,来不及重构动态的位置,后续等有缘人吧
             StoreUser storeUser = storeUserMapper.selectOne(new QueryWrapper<StoreUser>().eq("phone", lifeUserDynamics.getPhoneId().split("_")[1]));
             map.put("userImage",storeUser.getHeadImg()!= null?storeUser.getHeadImg():"");
+            LifeUser lifeUser = lifeUserMapper.selectById(userId);
             // 3.查询当前用户是否喜欢
             LifeLikeRecord lifeLikeRecord = lifeLikeRecordMapper.selectOne(new QueryWrapper<LifeLikeRecord>()
                     .eq("type", CommonConstant.LIKE_TYPE_DYNAMICS)
-                    .eq("dianzan_id", userId)
+                    .eq("dianzan_id", "user_".concat(lifeUser.getUserPhone()))
                     .eq("huifu_id", sourceId)
                     .eq("delete_flag", 0));
             if(null != lifeLikeRecord){
@@ -286,7 +287,7 @@ public class CommonCommentServiceImpl extends ServiceImpl<CommonCommentMapper, C
                 map.put("isLike",0);
             }
             // 4.查询当前登录人是否关注了动态发布者
-            LifeUser lifeUser = lifeUserMapper.selectById(userId);
+
             LifeFans lifeFans = lifeFansMapper.selectOne(new QueryWrapper<LifeFans>().eq("followed_id", lifeUserDynamics.getPhoneId())
                     .eq("fans_id", "user_".concat(lifeUser.getUserPhone()))
                     .eq("fans_type", 1)

+ 12 - 12
alien-store/src/main/java/shop/alien/store/service/impl/LifeBlacklistServiceImpl.java

@@ -7,15 +7,9 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
-import shop.alien.entity.store.LifeBlacklist;
-import shop.alien.entity.store.LifeUser;
-import shop.alien.entity.store.StoreImg;
-import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.store.*;
 import shop.alien.entity.store.vo.LifeBlacklistVo;
-import shop.alien.mapper.LifeBlacklistMapper;
-import shop.alien.mapper.LifeUserMapper;
-import shop.alien.mapper.StoreImgMapper;
-import shop.alien.mapper.StoreUserMapper;
+import shop.alien.mapper.*;
 import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.service.LifeBlacklistService;
 import shop.alien.util.common.JwtUtil;
@@ -44,6 +38,7 @@ public class LifeBlacklistServiceImpl extends ServiceImpl<LifeBlacklistMapper, L
     private final LifeUserMapper lifeUserMapper;
     private final StoreImgMapper storeImgMapper;
     private final BaseRedisService baseRedisService;
+    private final StoreInfoMapper storeInfoMapper;
 
     @Override
     public int blackList(LifeBlacklist lifeBlacklist) {
@@ -88,11 +83,16 @@ public class LifeBlacklistServiceImpl extends ServiceImpl<LifeBlacklistMapper, L
     }
 
     @Override
-    public List<LifeBlacklistVo> blackListByUserId(String userType, String userId) {
+    public List<LifeBlacklistVo> blackListByUserId(String userType, String userId, Integer type) {
         List<LifeBlacklistVo> lifeBlacklistVos = new ArrayList<>();
         LambdaUpdateWrapper<LifeBlacklist> wrapper = new LambdaUpdateWrapper<>();
         wrapper.eq(LifeBlacklist::getBlockerId, userId);
         wrapper.eq(LifeBlacklist::getBlockerType, userType);
+        if (1 == type) {
+            wrapper.eq(LifeBlacklist::getBlockedType, "1");
+        } else if (2 == type) {
+            wrapper.eq(LifeBlacklist::getBlockedType, "2");
+        }
         List<LifeBlacklist> lifeBlacklists = lifeBlacklistMapper.selectList(wrapper);
 
         for (LifeBlacklist lifeBlacklist : lifeBlacklists) {
@@ -105,14 +105,14 @@ public class LifeBlacklistServiceImpl extends ServiceImpl<LifeBlacklistMapper, L
             if (lifeBlacklist.getBlockedId() != null && lifeBlacklist.getBlockedType() != null) {
                 if ("1".equals(lifeBlacklist.getBlockedType())) {
                     StoreUser storeUser = storeUserMapper.selectById(lifeBlacklist.getBlockedId());
+                    StoreInfo storeInfo = storeInfoMapper.selectById(storeUser.getStoreId());
+                    lifeBlacklist.setStoreInfo(storeInfo);
                     StoreImg storeImg = null;
                     if (storeUser != null) {
-                        storeImg = storeImgMapper.selectOne(new LambdaUpdateWrapper<StoreImg>().eq(StoreImg::getStoreId, storeUser.getStoreId()).eq(StoreImg::getImgType, 10));
-                    }
-                    if (storeUser != null) {
                         lifeBlacklist.setName(storeUser.getName());
                         lifeBlacklist.setPhoneId(storeUser.getPhone());
                         lifeBlacklist.setUserImage(storeUser.getHeadImg());
+                        storeImg = storeImgMapper.selectOne(new LambdaUpdateWrapper<StoreImg>().eq(StoreImg::getStoreId, storeUser.getStoreId()).eq(StoreImg::getImgType, 10));
                         if (storeImg != null) {
                             lifeBlacklist.setUserImage(storeImg.getImgUrl());
                         }

+ 2 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeMessageServiceImpl.java

@@ -219,6 +219,8 @@ public class LifeMessageServiceImpl extends ServiceImpl<LifeMessageMapper, LifeM
             Integer status = order.getOrderStatus();
             enabled = !Objects.equals(status, LawyerStatusEnum.COMPLETE.getStatus())
                     && !Objects.equals(status, LawyerStatusEnum.CANCEL.getStatus())
+                    && !Objects.equals(status, LawyerStatusEnum.WAIT_PAY.getStatus())
+                    && !Objects.equals(status, LawyerStatusEnum.WAIT_ACCEPT.getStatus())
                     && !Objects.equals(status, LawyerStatusEnum.REFUNDED.getStatus());
         }
         String chatStr = "1";

+ 3 - 1
alien-store/src/main/java/shop/alien/store/service/impl/PerformanceListServiceImpl.java

@@ -990,7 +990,9 @@ public class PerformanceListServiceImpl implements PerformanceListService {
 
             // 转换为VO列表
             for (StoreStaffConfig staff : staffList) {
-                if (staff == null || !CommonConstant.DELETE_FLAG_UNDELETE.equals(staff.getDeleteFlag())) {
+                if (staff == null || !CommonConstant.DELETE_FLAG_UNDELETE.equals(staff.getDeleteFlag())
+                        ||!"1".equals(staff.getStatus()) ||!CommonConstant.ONLINE_STATUS.equals(staff.getOnlineStatus())
+                        ) {
                     continue;
                 }
 

+ 512 - 326
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -970,12 +970,45 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         //存入审批状态为待审批
         storeInfo.setStoreApplicationStatus(0);
         //处理一下行政区域信息
-        EssentialCityCode essentialCityCode1 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, storeInfo.getAdministrativeRegionProvinceAdcode()));
-        storeInfo.setAdministrativeRegionProvinceName(essentialCityCode1.getAreaName());
-        EssentialCityCode essentialCityCode2 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, storeInfo.getAdministrativeRegionCityAdcode()));
-        storeInfo.setAdministrativeRegionCityName(essentialCityCode2.getAreaName());
-        EssentialCityCode essentialCityCode3 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, storeInfo.getAdministrativeRegionDistrictAdcode()));
-        storeInfo.setAdministrativeRegionDistrictName(essentialCityCode3.getAreaName());
+        if (StringUtils.isNotEmpty(storeInfo.getAdministrativeRegionProvinceAdcode())) {
+            try {
+                Integer provinceAdcode = Integer.parseInt(storeInfo.getAdministrativeRegionProvinceAdcode());
+                EssentialCityCode essentialCityCode1 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, provinceAdcode));
+                if (null != essentialCityCode1) {
+                    storeInfo.setAdministrativeRegionProvinceName(essentialCityCode1.getAreaName());
+                } else {
+                    log.warn("未找到省份编码对应的地区信息:{}", storeInfo.getAdministrativeRegionProvinceAdcode());
+                }
+            } catch (NumberFormatException e) {
+                log.error("省份编码格式错误:{}", storeInfo.getAdministrativeRegionProvinceAdcode(), e);
+            }
+        }
+        if (StringUtils.isNotEmpty(storeInfo.getAdministrativeRegionCityAdcode())) {
+            try {
+                Integer cityAdcode = Integer.parseInt(storeInfo.getAdministrativeRegionCityAdcode());
+                EssentialCityCode essentialCityCode2 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, cityAdcode));
+                if (null != essentialCityCode2) {
+                    storeInfo.setAdministrativeRegionCityName(essentialCityCode2.getAreaName());
+                } else {
+                    log.warn("未找到城市编码对应的地区信息:{}", storeInfo.getAdministrativeRegionCityAdcode());
+                }
+            } catch (NumberFormatException e) {
+                log.error("城市编码格式错误:{}", storeInfo.getAdministrativeRegionCityAdcode(), e);
+            }
+        }
+        if (StringUtils.isNotEmpty(storeInfo.getAdministrativeRegionDistrictAdcode())) {
+            try {
+                Integer districtAdcode = Integer.parseInt(storeInfo.getAdministrativeRegionDistrictAdcode());
+                EssentialCityCode essentialCityCode3 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, districtAdcode));
+                if (null != essentialCityCode3) {
+                    storeInfo.setAdministrativeRegionDistrictName(essentialCityCode3.getAreaName());
+                } else {
+                    log.warn("未找到区县编码对应的地区信息:{}", storeInfo.getAdministrativeRegionDistrictAdcode());
+                }
+            } catch (NumberFormatException e) {
+                log.error("区县编码格式错误:{}", storeInfo.getAdministrativeRegionDistrictAdcode(), e);
+            }
+        }
         if (null == storeInfo.getCommissionRate()) {
             storeInfo.setCommissionRate("3");
         }
@@ -1325,22 +1358,43 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
     @Override
     public int saveStoreInfoDraft(StoreInfoDraft storeInfoDraft) {
         //处理一下行政区域信息
-        if (storeInfoDraft.getAdministrativeRegionProvinceAdcode() != null) {
-            EssentialCityCode essentialCityCode1 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, storeInfoDraft.getAdministrativeRegionProvinceAdcode()));
-            if (null != essentialCityCode1) {
-                storeInfoDraft.setAdministrativeRegionProvinceName(essentialCityCode1.getAreaName());
+        if (StringUtils.isNotEmpty(storeInfoDraft.getAdministrativeRegionProvinceAdcode())) {
+            try {
+                Integer provinceAdcode = Integer.parseInt(storeInfoDraft.getAdministrativeRegionProvinceAdcode());
+                EssentialCityCode essentialCityCode1 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, provinceAdcode));
+                if (null != essentialCityCode1) {
+                    storeInfoDraft.setAdministrativeRegionProvinceName(essentialCityCode1.getAreaName());
+                } else {
+                    log.warn("未找到省份编码对应的地区信息:{}", storeInfoDraft.getAdministrativeRegionProvinceAdcode());
+                }
+            } catch (NumberFormatException e) {
+                log.error("省份编码格式错误:{}", storeInfoDraft.getAdministrativeRegionProvinceAdcode(), e);
             }
         }
-        if (storeInfoDraft.getAdministrativeRegionCityAdcode() != null) {
-            EssentialCityCode essentialCityCode2 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, storeInfoDraft.getAdministrativeRegionCityAdcode()));
-            if (null != essentialCityCode2) {
-                storeInfoDraft.setAdministrativeRegionCityName(essentialCityCode2.getAreaName());
+        if (StringUtils.isNotEmpty(storeInfoDraft.getAdministrativeRegionCityAdcode())) {
+            try {
+                Integer cityAdcode = Integer.parseInt(storeInfoDraft.getAdministrativeRegionCityAdcode());
+                EssentialCityCode essentialCityCode2 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, cityAdcode));
+                if (null != essentialCityCode2) {
+                    storeInfoDraft.setAdministrativeRegionCityName(essentialCityCode2.getAreaName());
+                } else {
+                    log.warn("未找到城市编码对应的地区信息:{}", storeInfoDraft.getAdministrativeRegionCityAdcode());
+                }
+            } catch (NumberFormatException e) {
+                log.error("城市编码格式错误:{}", storeInfoDraft.getAdministrativeRegionCityAdcode(), e);
             }
         }
-        if (storeInfoDraft.getAdministrativeRegionDistrictAdcode() != null) {
-            EssentialCityCode essentialCityCode3 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, storeInfoDraft.getAdministrativeRegionDistrictAdcode()));
-            if (null != essentialCityCode3) {
-                storeInfoDraft.setAdministrativeRegionDistrictName(essentialCityCode3.getAreaName());
+        if (StringUtils.isNotEmpty(storeInfoDraft.getAdministrativeRegionDistrictAdcode())) {
+            try {
+                Integer districtAdcode = Integer.parseInt(storeInfoDraft.getAdministrativeRegionDistrictAdcode());
+                EssentialCityCode essentialCityCode3 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, districtAdcode));
+                if (null != essentialCityCode3) {
+                    storeInfoDraft.setAdministrativeRegionDistrictName(essentialCityCode3.getAreaName());
+                } else {
+                    log.warn("未找到区县编码对应的地区信息:{}", storeInfoDraft.getAdministrativeRegionDistrictAdcode());
+                }
+            } catch (NumberFormatException e) {
+                log.error("区县编码格式错误:{}", storeInfoDraft.getAdministrativeRegionDistrictAdcode(), e);
             }
         }
         if (StringUtils.isEmpty(storeInfoDraft.getBusinessLicenseUrl())) {
@@ -1484,12 +1538,45 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         }
         
         //处理一下行政区域信息
-        EssentialCityCode essentialCityCode1 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, storeInfo.getAdministrativeRegionProvinceAdcode()));
-        storeInfo.setAdministrativeRegionProvinceName(essentialCityCode1.getAreaName());
-        EssentialCityCode essentialCityCode2 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, storeInfo.getAdministrativeRegionCityAdcode()));
-        storeInfo.setAdministrativeRegionCityName(essentialCityCode2.getAreaName());
-        EssentialCityCode essentialCityCode3 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, storeInfo.getAdministrativeRegionDistrictAdcode()));
-        storeInfo.setAdministrativeRegionDistrictName(essentialCityCode3.getAreaName());
+        if (StringUtils.isNotEmpty(storeInfo.getAdministrativeRegionProvinceAdcode())) {
+            try {
+                Integer provinceAdcode = Integer.parseInt(storeInfo.getAdministrativeRegionProvinceAdcode());
+                EssentialCityCode essentialCityCode1 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, provinceAdcode));
+                if (null != essentialCityCode1) {
+                    storeInfo.setAdministrativeRegionProvinceName(essentialCityCode1.getAreaName());
+                } else {
+                    log.warn("未找到省份编码对应的地区信息:{}", storeInfo.getAdministrativeRegionProvinceAdcode());
+                }
+            } catch (NumberFormatException e) {
+                log.error("省份编码格式错误:{}", storeInfo.getAdministrativeRegionProvinceAdcode(), e);
+            }
+        }
+        if (StringUtils.isNotEmpty(storeInfo.getAdministrativeRegionCityAdcode())) {
+            try {
+                Integer cityAdcode = Integer.parseInt(storeInfo.getAdministrativeRegionCityAdcode());
+                EssentialCityCode essentialCityCode2 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, cityAdcode));
+                if (null != essentialCityCode2) {
+                    storeInfo.setAdministrativeRegionCityName(essentialCityCode2.getAreaName());
+                } else {
+                    log.warn("未找到城市编码对应的地区信息:{}", storeInfo.getAdministrativeRegionCityAdcode());
+                }
+            } catch (NumberFormatException e) {
+                log.error("城市编码格式错误:{}", storeInfo.getAdministrativeRegionCityAdcode(), e);
+            }
+        }
+        if (StringUtils.isNotEmpty(storeInfo.getAdministrativeRegionDistrictAdcode())) {
+            try {
+                Integer districtAdcode = Integer.parseInt(storeInfo.getAdministrativeRegionDistrictAdcode());
+                EssentialCityCode essentialCityCode3 = essentialCityCodeMapper.selectOne(new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, districtAdcode));
+                if (null != essentialCityCode3) {
+                    storeInfo.setAdministrativeRegionDistrictName(essentialCityCode3.getAreaName());
+                } else {
+                    log.warn("未找到区县编码对应的地区信息:{}", storeInfo.getAdministrativeRegionDistrictAdcode());
+                }
+            } catch (NumberFormatException e) {
+                log.error("区县编码格式错误:{}", storeInfo.getAdministrativeRegionDistrictAdcode(), e);
+            }
+        }
         storeInfoMapper.updateById(storeInfo);
         nearMeService.inGeolocation(new Point(Double.parseDouble(storeInfoDto.getStorePositionLongitude()), Double.parseDouble(storeInfoDto.getStorePositionLatitude())), storeInfo.getId().toString(), Boolean.TRUE);
 
@@ -2815,10 +2902,16 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         if (StringUtils.isEmpty(areaCode)) {
             return ""; // 或返回null,根据业务定义
         }
-        EssentialCityCode cityCode = essentialCityCodeMapper.selectOne(
-                new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, areaCode)
-        );
-        return Objects.nonNull(cityCode) ? cityCode.getAreaName() : "";
+        try {
+            Integer areaCodeInt = Integer.parseInt(areaCode);
+            EssentialCityCode cityCode = essentialCityCodeMapper.selectOne(
+                    new LambdaQueryWrapper<EssentialCityCode>().eq(EssentialCityCode::getAreaCode, areaCodeInt)
+            );
+            return Objects.nonNull(cityCode) ? cityCode.getAreaName() : "";
+        } catch (NumberFormatException e) {
+            log.error("区域编码格式错误:{}", areaCode, e);
+            return "";
+        }
     }
 
     @Override
@@ -5742,304 +5835,8 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             result.setStoreBusinessInfos(storeBusinessInfos);
         }
         if(storeInfo.getBusinessStatus() == 0){
-            // 优先判断当前时间是否是特殊日期
-            LocalDate currentDate = LocalDate.now();
-            LocalTime currentTime = LocalTime.now();
-
-            log.info("开始判断营业状态 - 当前日期: {}, 当前时间: {}, 店铺ID: {}", currentDate, currentTime, storeId);
-
-            // 标记当前是否是特殊营业时间
-            boolean isCurrentSpecialBusinessTime = false;
-
-            // 查找特殊营业时间(businessType == 2)
-            StoreBusinessInfo specialBusinessInfo = storeBusinessInfos.stream()
-                    .filter(item -> item.getBusinessType() != null && item.getBusinessType() == 2)
-                    .filter(item -> {
-                        // 判断当前日期是否匹配特殊日期
-                        if (StringUtils.isEmpty(item.getBusinessDate())) {
-                            return false;
-                        }
-                        try {
-                            String businessDate = item.getBusinessDate().trim();
-
-                            // 移除开头的数字和空格(如 "2 元旦、春节、儿童节" -> "元旦、春节、儿童节")
-                            String holidayNames = businessDate.replaceAll("^\\d+\\s*", "").trim();
-
-                            // 判断是否包含节假日名称(支持所有特殊日期:元旦、春节、情人节、元宵节、清明节、劳动节、儿童节、端午节、七夕、中秋节、国庆节、冬至、平安夜、圣诞节)
-                            boolean isHolidayName = holidayNames.contains("元旦") || holidayNames.contains("春节") ||
-                                holidayNames.contains("情人节") || holidayNames.contains("元宵") ||
-                                holidayNames.contains("七夕") || holidayNames.contains("冬至") ||
-                                holidayNames.contains("平安夜") || holidayNames.contains("圣诞") ||
-                                holidayNames.contains("清明") || holidayNames.contains("劳动") ||
-                                holidayNames.contains("端午") || holidayNames.contains("中秋") ||
-                                holidayNames.contains("国庆") || holidayNames.contains("儿童");
-
-                            if (isHolidayName) {
-
-                                List<LocalDate[]> holidayRanges = getHolidayDateRanges(currentDate.getYear(), holidayNames);
-                                for (LocalDate[] range : holidayRanges) {
-                                    if (range != null && range.length == 2) {
-                                        LocalDate startDate = range[0];
-                                        LocalDate endDate = range[1];
-                                        if (!currentDate.isBefore(startDate) && !currentDate.isAfter(endDate)) {
-                                            log.info("特殊日期判断(节假日) - 节假日: {}, 日期范围: {}至{}, 当前日期: {}, 是否在范围内: true",
-                                                    holidayNames, startDate, endDate, currentDate);
-                                            return true;
-                                        }
-                                    }
-                                }
-                                log.info("特殊日期判断(节假日) - 节假日: {}, 当前日期: {}, 是否在范围内: false",
-                                        holidayNames, currentDate);
-                                return false;
-                            }
-
-                            // 如果不是节假日名称,尝试解析日期格式(兼容旧格式)
-                            String[] dateFormats = {"yyyy-MM-dd", "yyyy/MM/dd", "yyyyMMdd"};
-
-                            // 先检查是否包含"至"(日期范围)
-                            if (businessDate.contains("至")) {
-                                String[] dateRange = businessDate.split("至");
-                                if (dateRange.length >= 2) {
-                                    try {
-                                        LocalDate startDate = null;
-                                        LocalDate endDate = null;
-                                        for (String format : dateFormats) {
-                                            try {
-                                                DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
-                                                startDate = LocalDate.parse(dateRange[0].trim(), formatter);
-                                                endDate = LocalDate.parse(dateRange[1].trim(), formatter);
-                                                break;
-                                            } catch (Exception ignored) {
-                                            }
-                                        }
-                                        if (startDate != null && endDate != null) {
-                                            boolean inRange = !currentDate.isBefore(startDate) && !currentDate.isAfter(endDate);
-                                            log.info("特殊日期范围判断 - 日期范围: {}至{}, 当前日期: {}, 是否在范围内: {}",
-                                                    startDate, endDate, currentDate, inRange);
-                                            return inRange;
-                                        }
-                                    } catch (Exception e) {
-                                        log.warn("解析特殊日期范围失败: {}", businessDate, e);
-                                    }
-                                }
-                            } else {
-                                // 尝试解析单个日期格式
-                                for (String format : dateFormats) {
-                                    try {
-                                        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
-                                        LocalDate specialDate = LocalDate.parse(businessDate, formatter);
-                                        boolean matches = specialDate.equals(currentDate);
-                                        log.info("特殊日期判断 - 特殊日期: {}, 当前日期: {}, 是否匹配: {}",
-                                                specialDate, currentDate, matches);
-                                        if (matches) {
-                                            return true;
-                                        }
-                                    } catch (Exception ignored) {
-                                        // 继续尝试下一个格式
-                                    }
-                                }
-                            }
-                        } catch (Exception e) {
-                            log.warn("解析特殊日期失败: {}", item.getBusinessDate(), e);
-                        }
-                        return false;
-                    })
-                    .findFirst()
-                    .orElse(null);
-
-            // 如果找到匹配的特殊日期,判断是否在营业时间内
-            if (specialBusinessInfo != null) {
-                log.info("找到匹配的特殊日期营业时间 - 开始时间: {}, 结束时间: {}",
-                        specialBusinessInfo.getStartTime(), specialBusinessInfo.getEndTime());
-                if (StringUtils.isNotEmpty(specialBusinessInfo.getStartTime())
-                        && StringUtils.isNotEmpty(specialBusinessInfo.getEndTime())) {
-                    try {
-                        String[] startArr = specialBusinessInfo.getStartTime().split(":");
-                        String[] endArr = specialBusinessInfo.getEndTime().split(":");
-                        if (startArr.length >= 2 && endArr.length >= 2) {
-                            LocalTime start = LocalTime.of(
-                                    Integer.parseInt(startArr[0]),
-                                    Integer.parseInt(startArr[1])
-                            );
-                            LocalTime end = LocalTime.of(
-                                    Integer.parseInt(endArr[0]),
-                                    Integer.parseInt(endArr[1])
-                            );
-
-                            boolean isInBusiness;
-                            // 判断是否是全天营业(00:00到00:00)
-                            LocalTime midnight = LocalTime.of(0, 0);
-                            if (start.equals(midnight) && end.equals(midnight)) {
-                                // 全天营业
-                                isInBusiness = true;
-                                log.info("特殊日期营业时间判断 - 全天营业(00:00-00:00),当前时间: {},是否在营业时间内: true", currentTime);
-                            } else {
-                                // 处理跨天营业
-                                if (start.isBefore(end) || start.equals(end)) {
-                                    // 同一天营业:包含边界值
-                                    isInBusiness = (currentTime.isAfter(start) || currentTime.equals(start))
-                                            && (currentTime.isBefore(end) || currentTime.equals(end));
-                                } else {
-                                    // 跨天营业:包含边界值
-                                    isInBusiness = (currentTime.isAfter(start) || currentTime.equals(start))
-                                            || (currentTime.isBefore(end) || currentTime.equals(end));
-                                }
-                                log.info("特殊日期营业时间判断 - 开始时间: {}, 结束时间: {}, 当前时间: {}, 是否在营业时间内: {}",
-                                        start, end, currentTime, isInBusiness);
-                            }
-
-                            result.setYyFlag(isInBusiness ? 1 : 0);
-                            isCurrentSpecialBusinessTime = isInBusiness;
-                        } else {
-                            log.warn("特殊日期营业时间格式错误 - 开始时间: {}, 结束时间: {}",
-                                    specialBusinessInfo.getStartTime(), specialBusinessInfo.getEndTime());
-                            result.setYyFlag(0);
-                            isCurrentSpecialBusinessTime = false;
-                        }
-                    } catch (NumberFormatException e) {
-                        log.warn("解析特殊日期营业时间失败 - 开始时间: {}, 结束时间: {}",
-                                specialBusinessInfo.getStartTime(), specialBusinessInfo.getEndTime(), e);
-                        result.setYyFlag(0);
-                        isCurrentSpecialBusinessTime = false;
-                    }
-                } else {
-                    log.warn("特殊日期营业时间为空 - 开始时间: {}, 结束时间: {}",
-                            specialBusinessInfo.getStartTime(), specialBusinessInfo.getEndTime());
-                    result.setYyFlag(0);
-                    isCurrentSpecialBusinessTime = false;
-                }
-            } else {
-                // 如果不是特殊日期,判断正常营业时间(businessType == 1)
-                StoreBusinessInfo normalBusinessInfo = storeBusinessInfos.stream()
-                        .filter(item -> item.getBusinessType() != null && item.getBusinessType() == 1)
-                        .findFirst()
-                        .orElse(null);
-
-                if (normalBusinessInfo != null) {
-                    log.info("判断正常营业时间 - 营业日期: {}, 开始时间: {}, 结束时间: {}",
-                            normalBusinessInfo.getBusinessDate(),
-                            normalBusinessInfo.getStartTime(),
-                            normalBusinessInfo.getEndTime());
-                    // 获取当前星期并转换为业务格式
-//                    Calendar calendar = Calendar.getInstance();
-//                    int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); // 1=周日,2=周一...7=周六
-//                    String[] days = {"7", "1", "2", "3", "4", "5", "6"};
-//                    String day = days[dayOfWeek - 1];
-
-                    Calendar calendar = Calendar.getInstance();
-                    int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); // 1=周日,2=周一...7=周六
-                    // 映射数组:索引对应dayOfWeek-1,值为中文星期
-                    String[] weekDaysCN = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
-                    String day = weekDaysCN[dayOfWeek - 1];
-
-                    log.info("当前星期: {}, 转换后: {}", dayOfWeek, day);
-
-                    // 判断当前日期是否在营业日期范围内
-                    if (StringUtils.isNotEmpty(normalBusinessInfo.getBusinessDate())
-                            && normalBusinessInfo.getBusinessDate().contains(day)) {
-                        log.info("当前星期在营业日期范围内");
-                        if (StringUtils.isNotEmpty(normalBusinessInfo.getStartTime())
-                                && StringUtils.isNotEmpty(normalBusinessInfo.getEndTime())) {
-                            try {
-                                String[] startArr = normalBusinessInfo.getStartTime().split(":");
-                                String[] endArr = normalBusinessInfo.getEndTime().split(":");
-                                if (startArr.length >= 2 && endArr.length >= 2) {
-                                    LocalTime start = LocalTime.of(
-                                            Integer.parseInt(startArr[0]),
-                                            Integer.parseInt(startArr[1])
-                                    );
-                                    LocalTime end = LocalTime.of(
-                                            Integer.parseInt(endArr[0]),
-                                            Integer.parseInt(endArr[1])
-                                    );
-
-                                    boolean isInBusiness;
-                                    // 判断是否是全天营业(00:00到00:00)
-                                    LocalTime midnight = LocalTime.of(0, 0);
-                                    if (start.equals(midnight) && end.equals(midnight)) {
-                                        // 全天营业
-                                        isInBusiness = true;
-                                        log.info("正常营业时间判断 - 全天营业(00:00-00:00),当前时间: {},是否在营业时间内: true", currentTime);
-                                    } else {
-                                        // 处理跨天营业
-                                        if (start.isBefore(end) || start.equals(end)) {
-                                            // 同一天营业:包含边界值
-                                            isInBusiness = (currentTime.isAfter(start) || currentTime.equals(start))
-                                                    && (currentTime.isBefore(end) || currentTime.equals(end));
-                                        } else {
-                                            // 跨天营业:包含边界值
-                                            isInBusiness = (currentTime.isAfter(start) || currentTime.equals(start))
-                                                    || (currentTime.isBefore(end) || currentTime.equals(end));
-                                        }
-                                        log.info("正常营业时间判断 - 开始时间: {}, 结束时间: {}, 当前时间: {}, 是否在营业时间内: {}",
-                                                start, end, currentTime, isInBusiness);
-                                    }
-
-                                    result.setYyFlag(isInBusiness ? 1 : 0);
-                                } else {
-                                    log.warn("正常营业时间格式错误 - 开始时间: {}, 结束时间: {}",
-                                            normalBusinessInfo.getStartTime(), normalBusinessInfo.getEndTime());
-                                    result.setYyFlag(0);
-                                }
-                            } catch (NumberFormatException e) {
-                                log.warn("解析正常营业时间失败 - 开始时间: {}, 结束时间: {}",
-                                        normalBusinessInfo.getStartTime(), normalBusinessInfo.getEndTime(), e);
-                                result.setYyFlag(0);
-                            }
-                        } else {
-                            log.warn("正常营业时间为空 - 开始时间: {}, 结束时间: {}",
-                                    normalBusinessInfo.getStartTime(), normalBusinessInfo.getEndTime());
-                            result.setYyFlag(0);
-                        }
-                    } else {
-                        log.info("当前星期不在营业日期范围内 - 营业日期: {}, 当前星期: {}",
-                                normalBusinessInfo.getBusinessDate(), day);
-                        result.setYyFlag(0);
-                    }
-                } else {
-                    log.warn("没有找到正常营业时间配置");
-                    result.setYyFlag(0);
-                }
-            }
-            log.info("最终营业状态 - yyFlag: {}", result.getYyFlag());
-
-            // 根据当前是否是特殊营业时间,对storeBusinessInfos进行排序
-            if (ObjectUtils.isNotEmpty(storeBusinessInfos)) {
-                final boolean isSpecial = isCurrentSpecialBusinessTime;
-                List<StoreBusinessInfo> sortedBusinessInfos = storeBusinessInfos.stream()
-                        .sorted((a, b) -> {
-                            Integer typeA = a.getBusinessType() != null ? a.getBusinessType() : 0;
-                            Integer typeB = b.getBusinessType() != null ? b.getBusinessType() : 0;
-
-                            // 如果当前是特殊营业时间,businessType == 2 排在前面
-                            if (isSpecial) {
-                                if (typeA == 2 && typeB != 2) {
-                                    return -1; // a排在前面
-                                } else if (typeA != 2 && typeB == 2) {
-                                    return 1; // b排在前面
-                                }
-                            } else {
-                                // 如果当前不是特殊营业时间,businessType == 1 排在前面
-                                if (typeA == 1 && typeB != 1) {
-                                    return -1; // a排在前面
-                                } else if (typeA != 1 && typeB == 1) {
-                                    return 1; // b排在前面
-                                }
-                            }
-                            // 相同类型,保持原有顺序
-                            return 0;
-                        })
-                        .collect(Collectors.toList());
-
-                // 重新设置排序后的列表
-                result.setStoreBusinessInfos(sortedBusinessInfos);
-                if (!sortedBusinessInfos.isEmpty()) {
-                    result.setStoreBusinessInfo(sortedBusinessInfos.get(0));
-                }
-
-                log.info("营业时间列表排序完成 - 当前是特殊营业时间: {}, 排序后第一个businessType: {}",
-                        isSpecial, sortedBusinessInfos.isEmpty() ? "无" : sortedBusinessInfos.get(0).getBusinessType());
-            }
+            // 调用提取的营业时间判断方法
+            calculateBusinessStatus(storeId, storeBusinessInfos, result);
         } else {
             log.info("店铺营业状态不为0,不判断营业时间 - businessStatus: {}", storeInfo.getBusinessStatus());
         }
@@ -6084,6 +5881,395 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
     }
 
     @Override
+    public StoreBusinessStatusVo getStoreBusinessStatus(String storeId) {
+        log.info("StoreInfoServiceImpl.getStoreBusinessStatus?storeId={}", storeId);
+        StoreBusinessStatusVo result = new StoreBusinessStatusVo();
+        
+        StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
+        if (storeInfo == null) {
+            log.warn("店铺不存在,storeId={}", storeId);
+            return result;
+        }
+        
+        result.setBusinessStatus(storeInfo.getBusinessStatus());
+        
+        // 查询营业时间
+        List<StoreBusinessInfo> storeBusinessInfos = storeBusinessInfoMapper.selectList(
+                new LambdaQueryWrapper<StoreBusinessInfo>()
+                        .eq(StoreBusinessInfo::getStoreId, storeId)
+                        .eq(StoreBusinessInfo::getDeleteFlag, 0)
+        );
+        
+        // 回显所有营业时间信息(特殊营业时间和正常营业时间)
+        if (ObjectUtils.isNotEmpty(storeBusinessInfos)) {
+            result.setStoreBusinessInfo(storeBusinessInfos.get(0));
+            result.setStoreBusinessInfos(storeBusinessInfos);
+        }
+        
+        // 判断营业状态
+        if (storeInfo.getBusinessStatus() == 0) {
+            // 调用提取的营业时间判断方法
+            calculateBusinessStatus(storeId, storeBusinessInfos, result);
+        } else {
+            log.info("店铺营业状态不为0,不判断营业时间 - businessStatus: {}", storeInfo.getBusinessStatus());
+            result.setYyFlag(0);
+        }
+        
+        return result;
+    }
+
+    /**
+     * 计算店铺营业状态
+     * 判断当前时间是否在营业时间内,并设置营业状态和排序后的营业时间列表
+     *
+     * @param storeId 店铺ID
+     * @param storeBusinessInfos 营业时间列表
+     * @param result 结果对象(StoreInfoVo或StoreBusinessStatusVo)
+     */
+    private void calculateBusinessStatus(String storeId, List<StoreBusinessInfo> storeBusinessInfos, Object result) {
+        // 优先判断当前时间是否是特殊日期
+        LocalDate currentDate = LocalDate.now();
+        LocalTime currentTime = LocalTime.now();
+
+        log.info("开始判断营业状态 - 当前日期: {}, 当前时间: {}, 店铺ID: {}", currentDate, currentTime, storeId);
+
+        // 标记当前是否是特殊营业时间
+        boolean isCurrentSpecialBusinessTime = false;
+
+        // 查找特殊营业时间(businessType == 2)
+        StoreBusinessInfo specialBusinessInfo = storeBusinessInfos.stream()
+                .filter(item -> item.getBusinessType() != null && item.getBusinessType() == 2)
+                .filter(item -> {
+                    // 判断当前日期是否匹配特殊日期
+                    if (StringUtils.isEmpty(item.getBusinessDate())) {
+                        return false;
+                    }
+                    try {
+                        String businessDate = item.getBusinessDate().trim();
+
+                        // 移除开头的数字和空格(如 "2 元旦、春节、儿童节" -> "元旦、春节、儿童节")
+                        String holidayNames = businessDate.replaceAll("^\\d+\\s*", "").trim();
+
+                        // 判断是否包含节假日名称(支持所有特殊日期:元旦、春节、情人节、元宵节、清明节、劳动节、儿童节、端午节、七夕、中秋节、国庆节、冬至、平安夜、圣诞节)
+                        boolean isHolidayName = holidayNames.contains("元旦") || holidayNames.contains("春节") ||
+                            holidayNames.contains("情人节") || holidayNames.contains("元宵") ||
+                            holidayNames.contains("七夕") || holidayNames.contains("冬至") ||
+                            holidayNames.contains("平安夜") || holidayNames.contains("圣诞") ||
+                            holidayNames.contains("清明") || holidayNames.contains("劳动") ||
+                            holidayNames.contains("端午") || holidayNames.contains("中秋") ||
+                            holidayNames.contains("国庆") || holidayNames.contains("儿童");
+
+                        if (isHolidayName) {
+
+                            List<LocalDate[]> holidayRanges = getHolidayDateRanges(currentDate.getYear(), holidayNames);
+                            for (LocalDate[] range : holidayRanges) {
+                                if (range != null && range.length == 2) {
+                                    LocalDate startDate = range[0];
+                                    LocalDate endDate = range[1];
+                                    if (!currentDate.isBefore(startDate) && !currentDate.isAfter(endDate)) {
+                                        log.info("特殊日期判断(节假日) - 节假日: {}, 日期范围: {}至{}, 当前日期: {}, 是否在范围内: true",
+                                                holidayNames, startDate, endDate, currentDate);
+                                        return true;
+                                    }
+                                }
+                            }
+                            log.info("特殊日期判断(节假日) - 节假日: {}, 当前日期: {}, 是否在范围内: false",
+                                    holidayNames, currentDate);
+                            return false;
+                        }
+
+                        // 如果不是节假日名称,尝试解析日期格式(兼容旧格式)
+                        String[] dateFormats = {"yyyy-MM-dd", "yyyy/MM/dd", "yyyyMMdd"};
+
+                        // 先检查是否包含"至"(日期范围)
+                        if (businessDate.contains("至")) {
+                            String[] dateRange = businessDate.split("至");
+                            if (dateRange.length >= 2) {
+                                try {
+                                    LocalDate startDate = null;
+                                    LocalDate endDate = null;
+                                    for (String format : dateFormats) {
+                                        try {
+                                            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+                                            startDate = LocalDate.parse(dateRange[0].trim(), formatter);
+                                            endDate = LocalDate.parse(dateRange[1].trim(), formatter);
+                                            break;
+                                        } catch (Exception ignored) {
+                                        }
+                                    }
+                                    if (startDate != null && endDate != null) {
+                                        boolean inRange = !currentDate.isBefore(startDate) && !currentDate.isAfter(endDate);
+                                        log.info("特殊日期范围判断 - 日期范围: {}至{}, 当前日期: {}, 是否在范围内: {}",
+                                                startDate, endDate, currentDate, inRange);
+                                        return inRange;
+                                    }
+                                } catch (Exception e) {
+                                    log.warn("解析特殊日期范围失败: {}", businessDate, e);
+                                }
+                            }
+                        } else {
+                            // 尝试解析单个日期格式
+                            for (String format : dateFormats) {
+                                try {
+                                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+                                    LocalDate specialDate = LocalDate.parse(businessDate, formatter);
+                                    boolean matches = specialDate.equals(currentDate);
+                                    log.info("特殊日期判断 - 特殊日期: {}, 当前日期: {}, 是否匹配: {}",
+                                            specialDate, currentDate, matches);
+                                    if (matches) {
+                                        return true;
+                                    }
+                                } catch (Exception ignored) {
+                                    // 继续尝试下一个格式
+                                }
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.warn("解析特殊日期失败: {}", item.getBusinessDate(), e);
+                    }
+                    return false;
+                })
+                .findFirst()
+                .orElse(null);
+
+        // 如果找到匹配的特殊日期,判断是否在营业时间内
+        if (specialBusinessInfo != null) {
+            log.info("找到匹配的特殊日期营业时间 - 开始时间: {}, 结束时间: {}",
+                    specialBusinessInfo.getStartTime(), specialBusinessInfo.getEndTime());
+            if (StringUtils.isNotEmpty(specialBusinessInfo.getStartTime())
+                    && StringUtils.isNotEmpty(specialBusinessInfo.getEndTime())) {
+                try {
+                    String[] startArr = specialBusinessInfo.getStartTime().split(":");
+                    String[] endArr = specialBusinessInfo.getEndTime().split(":");
+                    if (startArr.length >= 2 && endArr.length >= 2) {
+                        LocalTime start = LocalTime.of(
+                                Integer.parseInt(startArr[0]),
+                                Integer.parseInt(startArr[1])
+                        );
+                        LocalTime end = LocalTime.of(
+                                Integer.parseInt(endArr[0]),
+                                Integer.parseInt(endArr[1])
+                        );
+
+                        boolean isInBusiness;
+                        // 判断是否是全天营业(00:00到00:00)
+                        LocalTime midnight = LocalTime.of(0, 0);
+                        if (start.equals(midnight) && end.equals(midnight)) {
+                            // 全天营业
+                            isInBusiness = true;
+                            log.info("特殊日期营业时间判断 - 全天营业(00:00-00:00),当前时间: {},是否在营业时间内: true", currentTime);
+                        } else {
+                            // 处理跨天营业
+                            if (start.isBefore(end) || start.equals(end)) {
+                                // 同一天营业:包含边界值
+                                isInBusiness = (currentTime.isAfter(start) || currentTime.equals(start))
+                                        && (currentTime.isBefore(end) || currentTime.equals(end));
+                            } else {
+                                // 跨天营业:包含边界值
+                                isInBusiness = (currentTime.isAfter(start) || currentTime.equals(start))
+                                        || (currentTime.isBefore(end) || currentTime.equals(end));
+                            }
+                            log.info("特殊日期营业时间判断 - 开始时间: {}, 结束时间: {}, 当前时间: {}, 是否在营业时间内: {}",
+                                    start, end, currentTime, isInBusiness);
+                        }
+
+                        setYyFlag(result, isInBusiness ? 1 : 0);
+                        isCurrentSpecialBusinessTime = isInBusiness;
+                    } else {
+                        log.warn("特殊日期营业时间格式错误 - 开始时间: {}, 结束时间: {}",
+                                specialBusinessInfo.getStartTime(), specialBusinessInfo.getEndTime());
+                        setYyFlag(result, 0);
+                        isCurrentSpecialBusinessTime = false;
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("解析特殊日期营业时间失败 - 开始时间: {}, 结束时间: {}",
+                            specialBusinessInfo.getStartTime(), specialBusinessInfo.getEndTime(), e);
+                    setYyFlag(result, 0);
+                    isCurrentSpecialBusinessTime = false;
+                }
+            } else {
+                log.warn("特殊日期营业时间为空 - 开始时间: {}, 结束时间: {}",
+                        specialBusinessInfo.getStartTime(), specialBusinessInfo.getEndTime());
+                setYyFlag(result, 0);
+                isCurrentSpecialBusinessTime = false;
+            }
+        } else {
+            // 如果不是特殊日期,判断正常营业时间(businessType == 1)
+            StoreBusinessInfo normalBusinessInfo = storeBusinessInfos.stream()
+                    .filter(item -> item.getBusinessType() != null && item.getBusinessType() == 1)
+                    .findFirst()
+                    .orElse(null);
+
+            if (normalBusinessInfo != null) {
+                log.info("判断正常营业时间 - 营业日期: {}, 开始时间: {}, 结束时间: {}",
+                        normalBusinessInfo.getBusinessDate(),
+                        normalBusinessInfo.getStartTime(),
+                        normalBusinessInfo.getEndTime());
+                // 获取当前星期并转换为业务格式
+                Calendar calendar = Calendar.getInstance();
+                int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); // 1=周日,2=周一...7=周六
+                // 映射数组:索引对应dayOfWeek-1,值为中文星期
+                String[] weekDaysCN = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
+                String day = weekDaysCN[dayOfWeek - 1];
+
+                log.info("当前星期: {}, 转换后: {}", dayOfWeek, day);
+
+                // 判断当前日期是否在营业日期范围内
+                if (StringUtils.isNotEmpty(normalBusinessInfo.getBusinessDate())
+                        && normalBusinessInfo.getBusinessDate().contains(day)) {
+                    log.info("当前星期在营业日期范围内");
+                    if (StringUtils.isNotEmpty(normalBusinessInfo.getStartTime())
+                            && StringUtils.isNotEmpty(normalBusinessInfo.getEndTime())) {
+                        try {
+                            String[] startArr = normalBusinessInfo.getStartTime().split(":");
+                            String[] endArr = normalBusinessInfo.getEndTime().split(":");
+                            if (startArr.length >= 2 && endArr.length >= 2) {
+                                LocalTime start = LocalTime.of(
+                                        Integer.parseInt(startArr[0]),
+                                        Integer.parseInt(startArr[1])
+                                );
+                                LocalTime end = LocalTime.of(
+                                        Integer.parseInt(endArr[0]),
+                                        Integer.parseInt(endArr[1])
+                                );
+
+                                boolean isInBusiness;
+                                // 判断是否是全天营业(00:00到00:00)
+                                LocalTime midnight = LocalTime.of(0, 0);
+                                if (start.equals(midnight) && end.equals(midnight)) {
+                                    // 全天营业
+                                    isInBusiness = true;
+                                    log.info("正常营业时间判断 - 全天营业(00:00-00:00),当前时间: {},是否在营业时间内: true", currentTime);
+                                } else {
+                                    // 处理跨天营业
+                                    if (start.isBefore(end) || start.equals(end)) {
+                                        // 同一天营业:包含边界值
+                                        isInBusiness = (currentTime.isAfter(start) || currentTime.equals(start))
+                                                && (currentTime.isBefore(end) || currentTime.equals(end));
+                                    } else {
+                                        // 跨天营业:包含边界值
+                                        isInBusiness = (currentTime.isAfter(start) || currentTime.equals(start))
+                                                || (currentTime.isBefore(end) || currentTime.equals(end));
+                                    }
+                                    log.info("正常营业时间判断 - 开始时间: {}, 结束时间: {}, 当前时间: {}, 是否在营业时间内: {}",
+                                            start, end, currentTime, isInBusiness);
+                                }
+
+                                setYyFlag(result, isInBusiness ? 1 : 0);
+                            } else {
+                                log.warn("正常营业时间格式错误 - 开始时间: {}, 结束时间: {}",
+                                        normalBusinessInfo.getStartTime(), normalBusinessInfo.getEndTime());
+                                setYyFlag(result, 0);
+                            }
+                        } catch (NumberFormatException e) {
+                            log.warn("解析正常营业时间失败 - 开始时间: {}, 结束时间: {}",
+                                    normalBusinessInfo.getStartTime(), normalBusinessInfo.getEndTime(), e);
+                            setYyFlag(result, 0);
+                        }
+                    } else {
+                        log.warn("正常营业时间为空 - 开始时间: {}, 结束时间: {}",
+                                normalBusinessInfo.getStartTime(), normalBusinessInfo.getEndTime());
+                        setYyFlag(result, 0);
+                    }
+                } else {
+                    log.info("当前星期不在营业日期范围内 - 营业日期: {}, 当前星期: {}",
+                            normalBusinessInfo.getBusinessDate(), day);
+                    setYyFlag(result, 0);
+                }
+            } else {
+                log.warn("没有找到正常营业时间配置");
+                setYyFlag(result, 0);
+            }
+        }
+        
+        Integer yyFlag = getYyFlag(result);
+        log.info("最终营业状态 - yyFlag: {}", yyFlag);
+
+        // 根据当前是否是特殊营业时间,对storeBusinessInfos进行排序
+        if (ObjectUtils.isNotEmpty(storeBusinessInfos)) {
+            final boolean isSpecial = isCurrentSpecialBusinessTime;
+            List<StoreBusinessInfo> sortedBusinessInfos = storeBusinessInfos.stream()
+                    .sorted((a, b) -> {
+                        Integer typeA = a.getBusinessType() != null ? a.getBusinessType() : 0;
+                        Integer typeB = b.getBusinessType() != null ? b.getBusinessType() : 0;
+
+                        // 如果当前是特殊营业时间,businessType == 2 排在前面
+                        if (isSpecial) {
+                            if (typeA == 2 && typeB != 2) {
+                                return -1; // a排在前面
+                            } else if (typeA != 2 && typeB == 2) {
+                                return 1; // b排在前面
+                            }
+                        } else {
+                            // 如果当前不是特殊营业时间,businessType == 1 排在前面
+                            if (typeA == 1 && typeB != 1) {
+                                return -1; // a排在前面
+                            } else if (typeA != 1 && typeB == 1) {
+                                return 1; // b排在前面
+                            }
+                        }
+                        // 相同类型,保持原有顺序
+                        return 0;
+                    })
+                    .collect(Collectors.toList());
+
+            // 重新设置排序后的列表
+            setStoreBusinessInfos(result, sortedBusinessInfos);
+            if (!sortedBusinessInfos.isEmpty()) {
+                setStoreBusinessInfo(result, sortedBusinessInfos.get(0));
+            }
+
+            log.info("营业时间列表排序完成 - 当前是特殊营业时间: {}, 排序后第一个businessType: {}",
+                    isSpecial, sortedBusinessInfos.isEmpty() ? "无" : sortedBusinessInfos.get(0).getBusinessType());
+        }
+    }
+
+    /**
+     * 设置yyFlag(支持StoreInfoVo和StoreBusinessStatusVo)
+     */
+    private void setYyFlag(Object result, Integer yyFlag) {
+        if (result instanceof StoreInfoVo) {
+            ((StoreInfoVo) result).setYyFlag(yyFlag);
+        } else if (result instanceof StoreBusinessStatusVo) {
+            ((StoreBusinessStatusVo) result).setYyFlag(yyFlag);
+        }
+    }
+
+    /**
+     * 获取yyFlag(支持StoreInfoVo和StoreBusinessStatusVo)
+     */
+    private Integer getYyFlag(Object result) {
+        if (result instanceof StoreInfoVo) {
+            return ((StoreInfoVo) result).getYyFlag();
+        } else if (result instanceof StoreBusinessStatusVo) {
+            return ((StoreBusinessStatusVo) result).getYyFlag();
+        }
+        return 0;
+    }
+
+    /**
+     * 设置storeBusinessInfo(支持StoreInfoVo和StoreBusinessStatusVo)
+     */
+    private void setStoreBusinessInfo(Object result, StoreBusinessInfo storeBusinessInfo) {
+        if (result instanceof StoreInfoVo) {
+            ((StoreInfoVo) result).setStoreBusinessInfo(storeBusinessInfo);
+        } else if (result instanceof StoreBusinessStatusVo) {
+            ((StoreBusinessStatusVo) result).setStoreBusinessInfo(storeBusinessInfo);
+        }
+    }
+
+    /**
+     * 设置storeBusinessInfos(支持StoreInfoVo和StoreBusinessStatusVo)
+     */
+    private void setStoreBusinessInfos(Object result, List<StoreBusinessInfo> storeBusinessInfos) {
+        if (result instanceof StoreInfoVo) {
+            ((StoreInfoVo) result).setStoreBusinessInfos(storeBusinessInfos);
+        } else if (result instanceof StoreBusinessStatusVo) {
+            ((StoreBusinessStatusVo) result).setStoreBusinessInfos(storeBusinessInfos);
+        }
+    }
+
+    @Override
     public List<LifeCouponVo> getStoreCouponList(String storeId) {
         // 获取店铺代金券列表
         LambdaUpdateWrapper<LifeCoupon> quanWrapper = new LambdaUpdateWrapper<>();

+ 823 - 60
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalStatisticsServiceImpl.java

@@ -48,55 +48,116 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     private final StorePriceMapper storePriceMapper;
     private final StoreUserMapper storeUserMapper;
     private final StoreOperationalStatisticsHistoryMapper statisticsHistoryMapper;
+    private final StoreTrackStatisticsMapper storeTrackStatisticsMapper;
 
     private static final String DATE_FORMAT = "yyyy-MM-dd";
+    private static final String STAT_TYPE_DAILY = "DAILY";
 
     @Override
     public StoreOperationalStatisticsVo getStatistics(Integer storeId, String startTime, String endTime) {
         log.info("StoreOperationalStatisticsServiceImpl.getStatistics - storeId={}, startTime={}, endTime={}", storeId, startTime, endTime);
+        return calculateStatistics(storeId, startTime, endTime);
+    }
 
-        StoreOperationalStatisticsVo statistics = new StoreOperationalStatisticsVo();
-
+    /**
+     * 计算统计数据(从store_track_statistics表查询并累加)
+     */
+    private StoreOperationalStatisticsVo calculateStatistics(Integer storeId, String startTime, String endTime) {
         try {
             // 解析时间范围
             SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
             Date startDate = sdf.parse(startTime);
             Date endDate = sdf.parse(endTime);
-            // 设置为当天的结束时间
-            Calendar endCal = Calendar.getInstance();
-            endCal.setTime(endDate);
-            endCal.set(Calendar.HOUR_OF_DAY, 23);
-            endCal.set(Calendar.MINUTE, 59);
-            endCal.set(Calendar.SECOND, 59);
-            endDate = endCal.getTime();
-
-            // 1. 流量数据统计
-            statistics.setTrafficData(getTrafficData(storeId, startDate, endDate));
-
-            // 2. 互动数据统计
-            statistics.setInteractionData(getInteractionData(storeId, startDate, endDate));
-
-            // 3. 优惠券数据统计
-            statistics.setCouponData(getCouponData(storeId, startDate, endDate));
-
-            // 4. 代金券数据统计(与优惠券类似,根据实际业务区分)
-            statistics.setVoucherData(getVoucherData(storeId, startDate, endDate));
-
-            // 5. 服务质量数据统计
-            statistics.setServiceQualityData(getServiceQualityData(storeId, startDate, endDate));
-
-            // 6. 价目表排名统计
-            statistics.setPriceListRanking(getPriceListRanking(storeId, startDate, endDate));
-
-            // 保存统计数据到历史表
-            saveStatisticsHistory(storeId, startTime, endTime, statistics);
-
+            
+            // 查询指定时间范围内的日统计数据
+            LambdaQueryWrapper<StoreTrackStatistics> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(StoreTrackStatistics::getStoreId, storeId)
+                    .eq(StoreTrackStatistics::getStatType, STAT_TYPE_DAILY)
+                    .ge(StoreTrackStatistics::getStatDate, startDate)
+                    .le(StoreTrackStatistics::getStatDate, endDate);
+            
+            List<StoreTrackStatistics> statisticsList = storeTrackStatisticsMapper.selectList(wrapper);
+            
+            if (statisticsList == null || statisticsList.isEmpty()) {
+                log.warn("未查询到统计数据 - storeId={}, startTime={}, endTime={}", storeId, startTime, endTime);
+                return new StoreOperationalStatisticsVo();
+            }
+            
+            // 累加所有统计数据
+            return aggregateStatistics(statisticsList);
+            
         } catch (ParseException e) {
-            log.error("StoreOperationalStatisticsServiceImpl.getStatistics - 时间解析错误", e);
+            log.error("StoreOperationalStatisticsServiceImpl.calculateStatistics - 时间解析错误", e);
             throw new RuntimeException("时间格式错误: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("计算统计数据失败 - storeId={}, startTime={}, endTime={}, error={}", 
+                    storeId, startTime, endTime, e.getMessage(), e);
+            throw new RuntimeException("计算统计数据失败: " + e.getMessage());
         }
-
-        return statistics;
+    }
+    
+    /**
+     * 累加统计数据
+     */
+    private StoreOperationalStatisticsVo aggregateStatistics(List<StoreTrackStatistics> statisticsList) {
+        StoreOperationalStatisticsVo result = new StoreOperationalStatisticsVo();
+        
+        // 初始化累加器
+        Map<String, Long> trafficAccumulator = new HashMap<>();
+        Map<String, Long> interactionAccumulator = new HashMap<>();
+        Map<String, Object> couponAccumulator = new HashMap<>();
+        Map<String, Object> voucherAccumulator = new HashMap<>();
+        Map<String, Object> serviceAccumulator = new HashMap<>();
+        Map<Integer, Map<String, Long>> priceRankingAccumulator = new HashMap<>();
+        
+        // 用于计算平均值的计数器
+        int trafficCount = 0;
+        int serviceCount = 0;
+        
+        // 遍历所有统计数据并累加
+        for (StoreTrackStatistics stat : statisticsList) {
+            // 累加流量数据
+            if (stat.getTrafficData() != null && !stat.getTrafficData().isEmpty()) {
+                aggregateTrafficData(stat.getTrafficData(), trafficAccumulator);
+                trafficCount++;
+            }
+            
+            // 累加互动数据
+            if (stat.getInteractionData() != null && !stat.getInteractionData().isEmpty()) {
+                aggregateInteractionData(stat.getInteractionData(), interactionAccumulator);
+            }
+            
+            // 累加优惠券数据
+            if (stat.getCouponData() != null && !stat.getCouponData().isEmpty()) {
+                aggregateCouponData(stat.getCouponData(), couponAccumulator);
+            }
+            
+            // 累加代金券数据
+            if (stat.getVoucherData() != null && !stat.getVoucherData().isEmpty()) {
+                aggregateVoucherData(stat.getVoucherData(), voucherAccumulator);
+            }
+            
+            // 累加服务质量数据
+            if (stat.getServiceData() != null && !stat.getServiceData().isEmpty()) {
+                aggregateServiceData(stat.getServiceData(), serviceAccumulator);
+                serviceCount++;
+            }
+            
+            // 累加价目表排名数据
+            if (stat.getPriceRankingData() != null && !stat.getPriceRankingData().isEmpty()) {
+                aggregatePriceRankingData(stat.getPriceRankingData(), priceRankingAccumulator);
+            }
+        }
+        
+        // 转换为VO对象
+        result.setTrafficData(convertToTrafficDataVo(trafficAccumulator, trafficCount));
+        result.setInteractionData(convertToInteractionDataVo(interactionAccumulator));
+        result.setCouponData(convertToCouponDataVo(couponAccumulator));
+        result.setVoucherData(convertToVoucherDataVo(voucherAccumulator));
+        result.setServiceQualityData(convertToServiceQualityDataVo(serviceAccumulator, serviceCount));
+        result.setPriceListRanking(convertToPriceListRankingVo(priceRankingAccumulator));
+        
+        return result;
     }
 
     @Override
@@ -111,9 +172,9 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
         comparison.setPreviousStartTime(previousStartTime);
         comparison.setPreviousEndTime(previousEndTime);
 
-        // 获取当期和上期的统计数据(getStatistics方法内部已经保存了历史记录)
-        StoreOperationalStatisticsVo currentStatistics = getStatistics(storeId, currentStartTime, currentEndTime);
-        StoreOperationalStatisticsVo previousStatistics = getStatistics(storeId, previousStartTime, previousEndTime);
+        // 获取当期和上期的统计数据
+        StoreOperationalStatisticsVo currentStatistics = calculateStatistics(storeId, currentStartTime, currentEndTime);
+        StoreOperationalStatisticsVo previousStatistics = calculateStatistics(storeId, previousStartTime, previousEndTime);
 
         // 构建对比数据
         comparison.setTrafficData(buildTrafficDataComparison(currentStatistics.getTrafficData(), previousStatistics.getTrafficData()));
@@ -122,35 +183,153 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
         comparison.setVoucherData(buildVoucherDataComparison(currentStatistics.getVoucherData(), previousStatistics.getVoucherData()));
         comparison.setServiceQualityData(buildServiceQualityDataComparison(currentStatistics.getServiceQualityData(), previousStatistics.getServiceQualityData()));
 
+        // 保存对比数据到历史表,获取历史记录ID
+        Integer historyId = saveStatisticsHistory(storeId, currentStartTime, currentEndTime, comparison);
+        
+        // 将历史记录ID设置到返回对象中,方便前端后续更新PDF URL
+        if (historyId != null) {
+            comparison.setHistoryId(historyId);
+        }
+
         return comparison;
     }
 
+
     /**
-     * 保存统计数据到历史表
+     * 将StoreOperationalStatisticsVo转换为符合埋点统计数据JSON格式
      */
-    private void saveStatisticsHistory(Integer storeId, String startTime, String endTime, StoreOperationalStatisticsVo statistics) {
-        try {
-            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
-            Date startDate = sdf.parse(startTime);
-            Date endDate = sdf.parse(endTime);
-
-            StoreOperationalStatisticsHistory history = new StoreOperationalStatisticsHistory();
-            history.setStoreId(storeId);
-            history.setStartTime(startDate);
-            history.setEndTime(endDate);
-            history.setQueryTime(new Date());
-            
-            // 将统计数据序列化为JSON字符串
-            String statisticsJson = JSON.toJSONString(statistics);
-            history.setStatisticsData(statisticsJson);
-
-            statisticsHistoryMapper.insert(history);
-            log.info("保存统计数据到历史表成功 - storeId={}, startTime={}, endTime={}", storeId, startTime, endTime);
-        } catch (Exception e) {
-            log.error("保存统计数据到历史表失败 - storeId={}, startTime={}, endTime={}, error={}", 
-                    storeId, startTime, endTime, e.getMessage(), e);
-            // 保存失败不影响主流程,只记录日志
+    private Map<String, Object> convertToTrackStatisticsFormat(StoreOperationalStatisticsVo statistics) {
+        Map<String, Object> result = new HashMap<>();
+        
+        // 1. 转换流量数据
+        if (statistics.getTrafficData() != null) {
+            StoreOperationalStatisticsVo.TrafficData traffic = statistics.getTrafficData();
+            Map<String, Object> trafficData = new HashMap<>();
+            trafficData.put("searchCount", traffic.getStoreSearchVolume() != null ? traffic.getStoreSearchVolume() : 0L);
+            trafficData.put("viewCount", traffic.getPageViews() != null ? traffic.getPageViews() : 0L);
+            trafficData.put("visitorCount", traffic.getVisitors() != null ? traffic.getVisitors() : 0L);
+            trafficData.put("newVisitorCount", traffic.getNewVisitors() != null ? traffic.getNewVisitors() : 0L);
+            // 访问时长从秒转换为毫秒
+            trafficData.put("totalDuration", traffic.getVisitDuration() != null ? traffic.getVisitDuration() * 1000 : 0L);
+            trafficData.put("avgDuration", traffic.getAvgVisitDuration() != null ? traffic.getAvgVisitDuration() * 1000 : 0L);
+            result.put("trafficData", trafficData);
+        }
+        
+        // 2. 转换互动数据
+        if (statistics.getInteractionData() != null) {
+            StoreOperationalStatisticsVo.InteractionData interaction = statistics.getInteractionData();
+            Map<String, Object> interactionData = new HashMap<>();
+            interactionData.put("collectCount", interaction.getStoreCollectionCount() != null ? interaction.getStoreCollectionCount() : 0L);
+            interactionData.put("shareCount", interaction.getStoreShareCount() != null ? interaction.getStoreShareCount() : 0L);
+            interactionData.put("checkinCount", interaction.getStoreCheckInCount() != null ? interaction.getStoreCheckInCount() : 0L);
+            interactionData.put("consultCount", interaction.getConsultMerchantCount() != null ? interaction.getConsultMerchantCount() : 0L);
+            interactionData.put("friendCount", interaction.getFriendsCount() != null ? interaction.getFriendsCount() : 0L);
+            interactionData.put("followCount", interaction.getFollowCount() != null ? interaction.getFollowCount() : 0L);
+            interactionData.put("fansCount", interaction.getFansCount() != null ? interaction.getFansCount() : 0L);
+            interactionData.put("postCount", interaction.getPostsPublishedCount() != null ? interaction.getPostsPublishedCount() : 0L);
+            interactionData.put("postLikeCount", interaction.getPostLikesCount() != null ? interaction.getPostLikesCount() : 0L);
+            interactionData.put("postCommentCount", interaction.getPostCommentsCount() != null ? interaction.getPostCommentsCount() : 0L);
+            interactionData.put("postRepostCount", interaction.getPostSharesCount() != null ? interaction.getPostSharesCount() : 0L);
+            interactionData.put("reportCount", interaction.getReportedCount() != null ? interaction.getReportedCount() : 0L);
+            interactionData.put("blockCount", interaction.getBlockedCount() != null ? interaction.getBlockedCount() : 0L);
+            result.put("interactionData", interactionData);
         }
+        
+        // 3. 转换优惠券数据
+        if (statistics.getCouponData() != null) {
+            StoreOperationalStatisticsVo.CouponData coupon = statistics.getCouponData();
+            Map<String, Object> couponData = new HashMap<>();
+            couponData.put("giveToFriendCount", coupon.getGiftToFriendsCount() != null ? coupon.getGiftToFriendsCount() : 0L);
+            couponData.put("giveToFriendAmount", coupon.getGiftToFriendsAmount() != null ? coupon.getGiftToFriendsAmount() : BigDecimal.ZERO);
+            couponData.put("giveToFriendUseCount", coupon.getGiftToFriendsUsedCount() != null ? coupon.getGiftToFriendsUsedCount() : 0L);
+            couponData.put("giveToFriendUseAmount", coupon.getGiftToFriendsUsedAmount() != null ? coupon.getGiftToFriendsUsedAmount() : BigDecimal.ZERO);
+            // 百分比转换:BigDecimal -> Double
+            if (coupon.getGiftToFriendsUsedAmountRatio() != null) {
+                couponData.put("giveToFriendUseAmountPercent", coupon.getGiftToFriendsUsedAmountRatio().doubleValue());
+            } else {
+                couponData.put("giveToFriendUseAmountPercent", 0.0);
+            }
+            couponData.put("friendGiveCount", coupon.getFriendsGiftCount() != null ? coupon.getFriendsGiftCount() : 0L);
+            couponData.put("friendGiveAmount", coupon.getFriendsGiftAmount() != null ? coupon.getFriendsGiftAmount() : BigDecimal.ZERO);
+            couponData.put("friendGiveUseCount", coupon.getFriendsGiftUsedCount() != null ? coupon.getFriendsGiftUsedCount() : 0L);
+            couponData.put("friendGiveUseAmount", coupon.getFriendsGiftUsedAmount() != null ? coupon.getFriendsGiftUsedAmount() : BigDecimal.ZERO);
+            if (coupon.getFriendsGiftUsedAmountRatio() != null) {
+                couponData.put("friendGiveUseAmountPercent", coupon.getFriendsGiftUsedAmountRatio().doubleValue());
+            } else {
+                couponData.put("friendGiveUseAmountPercent", 0.0);
+            }
+            result.put("couponData", couponData);
+        }
+        
+        // 4. 转换代金券数据
+        if (statistics.getVoucherData() != null) {
+            StoreOperationalStatisticsVo.VoucherData voucher = statistics.getVoucherData();
+            Map<String, Object> voucherData = new HashMap<>();
+            voucherData.put("giveToFriendCount", voucher.getGiftToFriendsCount() != null ? voucher.getGiftToFriendsCount() : 0L);
+            voucherData.put("giveToFriendAmount", voucher.getGiftToFriendsAmount() != null ? voucher.getGiftToFriendsAmount() : BigDecimal.ZERO);
+            voucherData.put("giveToFriendUseCount", voucher.getGiftToFriendsUsedCount() != null ? voucher.getGiftToFriendsUsedCount() : 0L);
+            voucherData.put("giveToFriendUseAmount", voucher.getGiftToFriendsUsedAmount() != null ? voucher.getGiftToFriendsUsedAmount() : BigDecimal.ZERO);
+            if (voucher.getGiftToFriendsUsedAmountRatio() != null) {
+                voucherData.put("giveToFriendUseAmountPercent", voucher.getGiftToFriendsUsedAmountRatio().doubleValue());
+            } else {
+                voucherData.put("giveToFriendUseAmountPercent", 0.0);
+            }
+            voucherData.put("friendGiveCount", voucher.getFriendsGiftCount() != null ? voucher.getFriendsGiftCount() : 0L);
+            voucherData.put("friendGiveAmount", voucher.getFriendsGiftAmount() != null ? voucher.getFriendsGiftAmount() : BigDecimal.ZERO);
+            voucherData.put("friendGiveUseCount", voucher.getFriendsGiftUsedCount() != null ? voucher.getFriendsGiftUsedCount() : 0L);
+            voucherData.put("friendGiveUseAmount", voucher.getFriendsGiftUsedAmount() != null ? voucher.getFriendsGiftUsedAmount() : BigDecimal.ZERO);
+            if (voucher.getFriendsGiftUsedAmountRatio() != null) {
+                voucherData.put("friendGiveUseAmountPercent", voucher.getFriendsGiftUsedAmountRatio().doubleValue());
+            } else {
+                voucherData.put("friendGiveUseAmountPercent", 0.0);
+            }
+            result.put("voucherData", voucherData);
+        }
+        
+        // 5. 转换服务质量数据
+        if (statistics.getServiceQualityData() != null) {
+            StoreOperationalStatisticsVo.ServiceQualityData service = statistics.getServiceQualityData();
+            Map<String, Object> serviceData = new HashMap<>();
+            serviceData.put("storeScore", service.getStoreRating() != null ? service.getStoreRating().doubleValue() : 0.0);
+            serviceData.put("tasteScore", service.getTasteRating() != null ? service.getTasteRating().doubleValue() : 0.0);
+            serviceData.put("environmentScore", service.getEnvironmentRating() != null ? service.getEnvironmentRating().doubleValue() : 0.0);
+            serviceData.put("serviceScore", service.getServiceRating() != null ? service.getServiceRating().doubleValue() : 0.0);
+            serviceData.put("ratingCount", service.getTotalReviews() != null ? service.getTotalReviews() : 0L);
+            serviceData.put("goodRatingCount", service.getPositiveReviews() != null ? service.getPositiveReviews() : 0L);
+            serviceData.put("midRatingCount", service.getNeutralReviews() != null ? service.getNeutralReviews() : 0L);
+            serviceData.put("badRatingCount", service.getNegativeReviews() != null ? service.getNegativeReviews() : 0L);
+            if (service.getNegativeReviewRatio() != null) {
+                serviceData.put("badRatingPercent", service.getNegativeReviewRatio().doubleValue());
+            } else {
+                serviceData.put("badRatingPercent", 0.0);
+            }
+            serviceData.put("appealCount", service.getNegativeReviewAppealsCount() != null ? service.getNegativeReviewAppealsCount() : 0L);
+            serviceData.put("appealSuccessCount", service.getNegativeReviewAppealsSuccessCount() != null ? service.getNegativeReviewAppealsSuccessCount() : 0L);
+            if (service.getNegativeReviewAppealsSuccessRatio() != null) {
+                serviceData.put("appealSuccessPercent", service.getNegativeReviewAppealsSuccessRatio().doubleValue());
+            } else {
+                serviceData.put("appealSuccessPercent", 0.0);
+            }
+            result.put("serviceData", serviceData);
+        }
+        
+        // 6. 转换价目表排名数据
+        if (statistics.getPriceListRanking() != null && !statistics.getPriceListRanking().isEmpty()) {
+            List<Map<String, Object>> priceRankingData = new ArrayList<>();
+            for (StoreOperationalStatisticsVo.PriceListRanking ranking : statistics.getPriceListRanking()) {
+                Map<String, Object> priceItem = new HashMap<>();
+                priceItem.put("priceId", ranking.getPriceId() != null ? ranking.getPriceId() : 0);
+                priceItem.put("viewCount", ranking.getPageViews() != null ? ranking.getPageViews().intValue() : 0);
+                priceItem.put("visitorCount", ranking.getVisitors() != null ? ranking.getVisitors().intValue() : 0);
+                priceItem.put("shareCount", ranking.getShares() != null ? ranking.getShares().intValue() : 0);
+                priceRankingData.add(priceItem);
+            }
+            result.put("priceRankingData", priceRankingData);
+        } else {
+            result.put("priceRankingData", new ArrayList<>());
+        }
+        
+        return result;
     }
 
     /**
@@ -439,6 +618,7 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
         for (StorePrice price : prices) {
             StoreOperationalStatisticsVo.PriceListRanking ranking = new StoreOperationalStatisticsVo.PriceListRanking();
             ranking.setRank(rank++);
+            ranking.setPriceId(price.getId());
             ranking.setPriceListItemName(price.getName());
             // TODO: 统计该价目表的浏览量、访客数、分享数
             ranking.setPageViews(0L);
@@ -456,6 +636,13 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     private StoreOperationalStatisticsComparisonVo.TrafficDataComparison buildTrafficDataComparison(
             StoreOperationalStatisticsVo.TrafficData current, StoreOperationalStatisticsVo.TrafficData previous) {
         StoreOperationalStatisticsComparisonVo.TrafficDataComparison comparison = new StoreOperationalStatisticsComparisonVo.TrafficDataComparison();
+        // 如果 previous 为 null,创建空对象避免 NullPointerException
+        if (previous == null) {
+            previous = new StoreOperationalStatisticsVo.TrafficData();
+        }
+        if (current == null) {
+            current = new StoreOperationalStatisticsVo.TrafficData();
+        }
         comparison.setStoreSearchVolume(buildComparisonData(current.getStoreSearchVolume(), previous.getStoreSearchVolume()));
         comparison.setPageViews(buildComparisonData(current.getPageViews(), previous.getPageViews()));
         comparison.setVisitors(buildComparisonData(current.getVisitors(), previous.getVisitors()));
@@ -471,6 +658,13 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     private StoreOperationalStatisticsComparisonVo.InteractionDataComparison buildInteractionDataComparison(
             StoreOperationalStatisticsVo.InteractionData current, StoreOperationalStatisticsVo.InteractionData previous) {
         StoreOperationalStatisticsComparisonVo.InteractionDataComparison comparison = new StoreOperationalStatisticsComparisonVo.InteractionDataComparison();
+        // 如果 previous 为 null,创建空对象避免 NullPointerException
+        if (previous == null) {
+            previous = new StoreOperationalStatisticsVo.InteractionData();
+        }
+        if (current == null) {
+            current = new StoreOperationalStatisticsVo.InteractionData();
+        }
         comparison.setStoreCollectionCount(buildComparisonData(current.getStoreCollectionCount(), previous.getStoreCollectionCount()));
         comparison.setStoreShareCount(buildComparisonData(current.getStoreShareCount(), previous.getStoreShareCount()));
         comparison.setStoreCheckInCount(buildComparisonData(current.getStoreCheckInCount(), previous.getStoreCheckInCount()));
@@ -493,6 +687,13 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     private StoreOperationalStatisticsComparisonVo.CouponDataComparison buildCouponDataComparison(
             StoreOperationalStatisticsVo.CouponData current, StoreOperationalStatisticsVo.CouponData previous) {
         StoreOperationalStatisticsComparisonVo.CouponDataComparison comparison = new StoreOperationalStatisticsComparisonVo.CouponDataComparison();
+        // 如果 previous 为 null,创建空对象避免 NullPointerException
+        if (previous == null) {
+            previous = new StoreOperationalStatisticsVo.CouponData();
+        }
+        if (current == null) {
+            current = new StoreOperationalStatisticsVo.CouponData();
+        }
         comparison.setGiftToFriendsCount(buildComparisonData(current.getGiftToFriendsCount(), previous.getGiftToFriendsCount()));
         comparison.setGiftToFriendsAmount(buildComparisonData(current.getGiftToFriendsAmount(), previous.getGiftToFriendsAmount()));
         comparison.setGiftToFriendsUsedCount(buildComparisonData(current.getGiftToFriendsUsedCount(), previous.getGiftToFriendsUsedCount()));
@@ -512,6 +713,13 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     private StoreOperationalStatisticsComparisonVo.VoucherDataComparison buildVoucherDataComparison(
             StoreOperationalStatisticsVo.VoucherData current, StoreOperationalStatisticsVo.VoucherData previous) {
         StoreOperationalStatisticsComparisonVo.VoucherDataComparison comparison = new StoreOperationalStatisticsComparisonVo.VoucherDataComparison();
+        // 如果 previous 为 null,创建空对象避免 NullPointerException
+        if (previous == null) {
+            previous = new StoreOperationalStatisticsVo.VoucherData();
+        }
+        if (current == null) {
+            current = new StoreOperationalStatisticsVo.VoucherData();
+        }
         comparison.setGiftToFriendsCount(buildComparisonData(current.getGiftToFriendsCount(), previous.getGiftToFriendsCount()));
         comparison.setGiftToFriendsAmount(buildComparisonData(current.getGiftToFriendsAmount(), previous.getGiftToFriendsAmount()));
         comparison.setGiftToFriendsUsedCount(buildComparisonData(current.getGiftToFriendsUsedCount(), previous.getGiftToFriendsUsedCount()));
@@ -531,6 +739,13 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     private StoreOperationalStatisticsComparisonVo.ServiceQualityDataComparison buildServiceQualityDataComparison(
             StoreOperationalStatisticsVo.ServiceQualityData current, StoreOperationalStatisticsVo.ServiceQualityData previous) {
         StoreOperationalStatisticsComparisonVo.ServiceQualityDataComparison comparison = new StoreOperationalStatisticsComparisonVo.ServiceQualityDataComparison();
+        // 如果 previous 为 null,创建空对象避免 NullPointerException
+        if (previous == null) {
+            previous = new StoreOperationalStatisticsVo.ServiceQualityData();
+        }
+        if (current == null) {
+            current = new StoreOperationalStatisticsVo.ServiceQualityData();
+        }
         comparison.setStoreRating(buildComparisonData(current.getStoreRating(), previous.getStoreRating()));
         comparison.setTasteRating(buildComparisonData(current.getTasteRating(), previous.getTasteRating()));
         comparison.setEnvironmentRating(buildComparisonData(current.getEnvironmentRating(), previous.getEnvironmentRating()));
@@ -607,6 +822,52 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     }
 
     @Override
+    public StoreOperationalStatisticsVo getStatisticsInTrackFormat(Integer storeId, String startTime, String endTime) {
+        log.info("StoreOperationalStatisticsServiceImpl.getStatisticsInTrackFormat - storeId={}, startTime={}, endTime={}", 
+                storeId, startTime, endTime);
+        
+        // 计算统计数据
+        StoreOperationalStatisticsVo statistics = calculateStatistics(storeId, startTime, endTime);
+        
+        // 不再保存统计数据到历史表,只有对比接口才会保存历史数据
+        
+        return statistics;
+    }
+    
+    /**
+     * 保存对比统计数据到历史表
+     * 仅在调用 getStatisticsComparison 接口时触发
+     * @return 历史记录ID,保存失败返回null
+     */
+    private Integer saveStatisticsHistory(Integer storeId, String currentStartTime, String currentEndTime, StoreOperationalStatisticsComparisonVo comparison) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
+            Date startDate = sdf.parse(currentStartTime);
+            Date endDate = sdf.parse(currentEndTime);
+
+            StoreOperationalStatisticsHistory history = new StoreOperationalStatisticsHistory();
+            history.setStoreId(storeId);
+            history.setStartTime(startDate);
+            history.setEndTime(endDate);
+            history.setQueryTime(new Date());
+            
+            // 将对比统计数据序列化为JSON字符串(包含当期、上期和变化率等完整对比数据)
+            String statisticsJson = JSON.toJSONString(comparison);
+            history.setStatisticsData(statisticsJson);
+
+            statisticsHistoryMapper.insert(history);
+            log.info("保存对比统计数据到历史表成功 - storeId={}, currentStartTime={}, currentEndTime={}, historyId={}", 
+                    storeId, currentStartTime, currentEndTime, history.getId());
+            return history.getId();
+        } catch (Exception e) {
+            log.error("保存对比统计数据到历史表失败 - storeId={}, currentStartTime={}, currentEndTime={}, error={}", 
+                    storeId, currentStartTime, currentEndTime, e.getMessage(), e);
+            // 保存失败不影响主流程,只记录日志
+            return null;
+        }
+    }
+
+    @Override
     public List<StoreOperationalStatisticsHistory> getHistoryList(Integer storeId) {
         log.info("StoreOperationalStatisticsServiceImpl.getHistoryList - storeId={}", storeId);
         
@@ -660,4 +921,506 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
             return false;
         }
     }
+
+    @Override
+    public boolean updateHistoryPdfUrl(Integer historyId, String pdfUrl) {
+        log.info("StoreOperationalStatisticsServiceImpl.updateHistoryPdfUrl - historyId={}, pdfUrl={}", historyId, pdfUrl);
+        
+        if (historyId == null || historyId <= 0) {
+            log.warn("更新历史统计记录PDF URL失败,历史记录ID无效 - historyId={}", historyId);
+            return false;
+        }
+        
+        if (pdfUrl == null || pdfUrl.trim().isEmpty()) {
+            log.warn("更新历史统计记录PDF URL失败,PDF URL为空 - historyId={}", historyId);
+            return false;
+        }
+        
+        try {
+            // 查询历史记录是否存在且未删除
+            StoreOperationalStatisticsHistory history = statisticsHistoryMapper.selectOne(
+                    new LambdaQueryWrapper<StoreOperationalStatisticsHistory>()
+                            .eq(StoreOperationalStatisticsHistory::getId, historyId)
+                            .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0));
+            
+            if (history == null) {
+                log.warn("更新历史统计记录PDF URL失败,历史记录不存在或已删除 - historyId={}", historyId);
+                return false;
+            }
+            
+            // 更新PDF URL
+            LambdaUpdateWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaUpdateWrapper<>();
+            wrapper.eq(StoreOperationalStatisticsHistory::getId, historyId)
+                   .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0)
+                   .set(StoreOperationalStatisticsHistory::getPdfUrl, pdfUrl.trim());
+            
+            int result = statisticsHistoryMapper.update(null, wrapper);
+            if (result > 0) {
+                log.info("更新历史统计记录PDF URL成功 - historyId={}, pdfUrl={}", historyId, pdfUrl);
+                return true;
+            } else {
+                log.warn("更新历史统计记录PDF URL失败,未更新任何记录 - historyId={}", historyId);
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("更新历史统计记录PDF URL失败 - historyId={}, pdfUrl={}, error={}", historyId, pdfUrl, e.getMessage(), e);
+            return false;
+        }
+    }
+    
+    // ==================== 累加和转换方法 ====================
+    
+    /**
+     * 累加流量数据
+     */
+    @SuppressWarnings("unchecked")
+    private void aggregateTrafficData(String trafficDataJson, Map<String, Long> accumulator) {
+        try {
+            Map<String, Object> data = (Map<String, Object>) JSON.parseObject(trafficDataJson, Map.class);
+            if (data == null) return;
+            
+            // 累加各个字段
+            accumulator.put("searchCount", accumulator.getOrDefault("searchCount", 0L) + 
+                    getLongValue(data, "searchCount"));
+            accumulator.put("viewCount", accumulator.getOrDefault("viewCount", 0L) + 
+                    getLongValue(data, "viewCount"));
+            accumulator.put("visitorCount", accumulator.getOrDefault("visitorCount", 0L) + 
+                    getLongValue(data, "visitorCount"));
+            accumulator.put("newVisitorCount", accumulator.getOrDefault("newVisitorCount", 0L) + 
+                    getLongValue(data, "newVisitorCount"));
+            accumulator.put("totalDuration", accumulator.getOrDefault("totalDuration", 0L) + 
+                    getLongValue(data, "totalDuration"));
+            accumulator.put("avgDuration", accumulator.getOrDefault("avgDuration", 0L) + 
+                    getLongValue(data, "avgDuration"));
+        } catch (Exception e) {
+            log.warn("解析流量数据失败: {}", trafficDataJson, e);
+        }
+    }
+    
+    /**
+     * 累加互动数据
+     */
+    @SuppressWarnings("unchecked")
+    private void aggregateInteractionData(String interactionDataJson, Map<String, Long> accumulator) {
+        try {
+            Map<String, Object> data = (Map<String, Object>) JSON.parseObject(interactionDataJson, Map.class);
+            if (data == null) return;
+            
+            accumulator.put("collectCount", accumulator.getOrDefault("collectCount", 0L) + 
+                    getLongValue(data, "collectCount"));
+            accumulator.put("shareCount", accumulator.getOrDefault("shareCount", 0L) + 
+                    getLongValue(data, "shareCount"));
+            accumulator.put("checkinCount", accumulator.getOrDefault("checkinCount", 0L) + 
+                    getLongValue(data, "checkinCount"));
+            accumulator.put("consultCount", accumulator.getOrDefault("consultCount", 0L) + 
+                    getLongValue(data, "consultCount"));
+            accumulator.put("friendCount", accumulator.getOrDefault("friendCount", 0L) + 
+                    getLongValue(data, "friendCount"));
+            accumulator.put("followCount", accumulator.getOrDefault("followCount", 0L) + 
+                    getLongValue(data, "followCount"));
+            accumulator.put("fansCount", accumulator.getOrDefault("fansCount", 0L) + 
+                    getLongValue(data, "fansCount"));
+            accumulator.put("postCount", accumulator.getOrDefault("postCount", 0L) + 
+                    getLongValue(data, "postCount"));
+            accumulator.put("postLikeCount", accumulator.getOrDefault("postLikeCount", 0L) + 
+                    getLongValue(data, "postLikeCount"));
+            accumulator.put("postCommentCount", accumulator.getOrDefault("postCommentCount", 0L) + 
+                    getLongValue(data, "postCommentCount"));
+            accumulator.put("postRepostCount", accumulator.getOrDefault("postRepostCount", 0L) + 
+                    getLongValue(data, "postRepostCount"));
+            accumulator.put("reportCount", accumulator.getOrDefault("reportCount", 0L) + 
+                    getLongValue(data, "reportCount"));
+            accumulator.put("blockCount", accumulator.getOrDefault("blockCount", 0L) + 
+                    getLongValue(data, "blockCount"));
+        } catch (Exception e) {
+            log.warn("解析互动数据失败: {}", interactionDataJson, e);
+        }
+    }
+    
+    /**
+     * 累加优惠券数据
+     */
+    @SuppressWarnings("unchecked")
+    private void aggregateCouponData(String couponDataJson, Map<String, Object> accumulator) {
+        try {
+            Map<String, Object> data = (Map<String, Object>) JSON.parseObject(couponDataJson, Map.class);
+            if (data == null) return;
+            
+            // 累加Long类型字段
+            accumulator.put("giveToFriendCount", getLongValue(accumulator, "giveToFriendCount") + 
+                    getLongValue(data, "giveToFriendCount"));
+            accumulator.put("giveToFriendUseCount", getLongValue(accumulator, "giveToFriendUseCount") + 
+                    getLongValue(data, "giveToFriendUseCount"));
+            accumulator.put("friendGiveCount", getLongValue(accumulator, "friendGiveCount") + 
+                    getLongValue(data, "friendGiveCount"));
+            accumulator.put("friendGiveUseCount", getLongValue(accumulator, "friendGiveUseCount") + 
+                    getLongValue(data, "friendGiveUseCount"));
+            
+            // 累加BigDecimal类型字段
+            accumulator.put("giveToFriendAmount", getBigDecimalValue(accumulator, "giveToFriendAmount")
+                    .add(getBigDecimalValue(data, "giveToFriendAmount")));
+            accumulator.put("giveToFriendUseAmount", getBigDecimalValue(accumulator, "giveToFriendUseAmount")
+                    .add(getBigDecimalValue(data, "giveToFriendUseAmount")));
+            accumulator.put("friendGiveAmount", getBigDecimalValue(accumulator, "friendGiveAmount")
+                    .add(getBigDecimalValue(data, "friendGiveAmount")));
+            accumulator.put("friendGiveUseAmount", getBigDecimalValue(accumulator, "friendGiveUseAmount")
+                    .add(getBigDecimalValue(data, "friendGiveUseAmount")));
+        } catch (Exception e) {
+            log.warn("解析优惠券数据失败: {}", couponDataJson, e);
+        }
+    }
+    
+    /**
+     * 累加代金券数据
+     */
+    @SuppressWarnings("unchecked")
+    private void aggregateVoucherData(String voucherDataJson, Map<String, Object> accumulator) {
+        try {
+            Map<String, Object> data = (Map<String, Object>) JSON.parseObject(voucherDataJson, Map.class);
+            if (data == null) return;
+            
+            // 累加Long类型字段
+            accumulator.put("giveToFriendCount", getLongValue(accumulator, "giveToFriendCount") + 
+                    getLongValue(data, "giveToFriendCount"));
+            accumulator.put("giveToFriendUseCount", getLongValue(accumulator, "giveToFriendUseCount") + 
+                    getLongValue(data, "giveToFriendUseCount"));
+            accumulator.put("friendGiveCount", getLongValue(accumulator, "friendGiveCount") + 
+                    getLongValue(data, "friendGiveCount"));
+            accumulator.put("friendGiveUseCount", getLongValue(accumulator, "friendGiveUseCount") + 
+                    getLongValue(data, "friendGiveUseCount"));
+            
+            // 累加BigDecimal类型字段
+            accumulator.put("giveToFriendAmount", getBigDecimalValue(accumulator, "giveToFriendAmount")
+                    .add(getBigDecimalValue(data, "giveToFriendAmount")));
+            accumulator.put("giveToFriendUseAmount", getBigDecimalValue(accumulator, "giveToFriendUseAmount")
+                    .add(getBigDecimalValue(data, "giveToFriendUseAmount")));
+            accumulator.put("friendGiveAmount", getBigDecimalValue(accumulator, "friendGiveAmount")
+                    .add(getBigDecimalValue(data, "friendGiveAmount")));
+            accumulator.put("friendGiveUseAmount", getBigDecimalValue(accumulator, "friendGiveUseAmount")
+                    .add(getBigDecimalValue(data, "friendGiveUseAmount")));
+        } catch (Exception e) {
+            log.warn("解析代金券数据失败: {}", voucherDataJson, e);
+        }
+    }
+    
+    /**
+     * 累加服务质量数据
+     */
+    @SuppressWarnings("unchecked")
+    private void aggregateServiceData(String serviceDataJson, Map<String, Object> accumulator) {
+        try {
+            Map<String, Object> data = (Map<String, Object>) JSON.parseObject(serviceDataJson, Map.class);
+            if (data == null) return;
+            
+            // 累加评分(需要计算平均值)
+            accumulator.put("storeScoreSum", getBigDecimalValue(accumulator, "storeScoreSum")
+                    .add(getBigDecimalValue(data, "storeScore")));
+            accumulator.put("tasteScoreSum", getBigDecimalValue(accumulator, "tasteScoreSum")
+                    .add(getBigDecimalValue(data, "tasteScore")));
+            accumulator.put("environmentScoreSum", getBigDecimalValue(accumulator, "environmentScoreSum")
+                    .add(getBigDecimalValue(data, "environmentScore")));
+            accumulator.put("serviceScoreSum", getBigDecimalValue(accumulator, "serviceScoreSum")
+                    .add(getBigDecimalValue(data, "serviceScore")));
+            
+            // 累加Long类型字段
+            accumulator.put("ratingCount", getLongValue(accumulator, "ratingCount") + 
+                    getLongValue(data, "ratingCount"));
+            accumulator.put("goodRatingCount", getLongValue(accumulator, "goodRatingCount") + 
+                    getLongValue(data, "goodRatingCount"));
+            accumulator.put("midRatingCount", getLongValue(accumulator, "midRatingCount") + 
+                    getLongValue(data, "midRatingCount"));
+            accumulator.put("badRatingCount", getLongValue(accumulator, "badRatingCount") + 
+                    getLongValue(data, "badRatingCount"));
+            accumulator.put("appealCount", getLongValue(accumulator, "appealCount") + 
+                    getLongValue(data, "appealCount"));
+            accumulator.put("appealSuccessCount", getLongValue(accumulator, "appealSuccessCount") + 
+                    getLongValue(data, "appealSuccessCount"));
+        } catch (Exception e) {
+            log.warn("解析服务质量数据失败: {}", serviceDataJson, e);
+        }
+    }
+    
+    /**
+     * 累加价目表排名数据
+     */
+    @SuppressWarnings("unchecked")
+    private void aggregatePriceRankingData(String priceRankingDataJson, Map<Integer, Map<String, Long>> accumulator) {
+        try {
+            List<Map<String, Object>> dataList = (List<Map<String, Object>>) (List<?>) JSON.parseArray(priceRankingDataJson);
+            if (dataList == null) return;
+            
+            for (Map<String, Object> item : dataList) {
+                Integer priceId = getIntegerValue(item, "priceId");
+                if (priceId == null) continue;
+                
+                Map<String, Long> priceData = accumulator.computeIfAbsent(priceId, k -> new HashMap<>());
+                priceData.put("viewCount", priceData.getOrDefault("viewCount", 0L) + 
+                        getLongValue(item, "viewCount"));
+                priceData.put("visitorCount", priceData.getOrDefault("visitorCount", 0L) + 
+                        getLongValue(item, "visitorCount"));
+                priceData.put("shareCount", priceData.getOrDefault("shareCount", 0L) + 
+                        getLongValue(item, "shareCount"));
+            }
+        } catch (Exception e) {
+            log.warn("解析价目表排名数据失败: {}", priceRankingDataJson, e);
+        }
+    }
+    
+    /**
+     * 转换为流量数据VO
+     */
+    private StoreOperationalStatisticsVo.TrafficData convertToTrafficDataVo(Map<String, Long> accumulator, int count) {
+        StoreOperationalStatisticsVo.TrafficData vo = new StoreOperationalStatisticsVo.TrafficData();
+        vo.setStoreSearchVolume(accumulator.getOrDefault("searchCount", 0L));
+        vo.setPageViews(accumulator.getOrDefault("viewCount", 0L));
+        vo.setVisitors(accumulator.getOrDefault("visitorCount", 0L));
+        vo.setNewVisitors(accumulator.getOrDefault("newVisitorCount", 0L));
+        
+        // 访问时长转换为秒(原数据是毫秒)
+        Long totalDuration = accumulator.getOrDefault("totalDuration", 0L);
+        vo.setVisitDuration(totalDuration / 1000);
+        
+        // 平均访问时长:如果有多个统计记录,需要重新计算平均值
+        Long avgDuration = accumulator.getOrDefault("avgDuration", 0L);
+        if (count > 0 && avgDuration > 0) {
+            vo.setAvgVisitDuration(avgDuration / 1000); // 转换为秒
+        } else {
+            vo.setAvgVisitDuration(0L);
+        }
+        
+        return vo;
+    }
+    
+    /**
+     * 转换为互动数据VO
+     */
+    private StoreOperationalStatisticsVo.InteractionData convertToInteractionDataVo(Map<String, Long> accumulator) {
+        StoreOperationalStatisticsVo.InteractionData vo = new StoreOperationalStatisticsVo.InteractionData();
+        vo.setStoreCollectionCount(accumulator.getOrDefault("collectCount", 0L));
+        vo.setStoreShareCount(accumulator.getOrDefault("shareCount", 0L));
+        vo.setStoreCheckInCount(accumulator.getOrDefault("checkinCount", 0L));
+        vo.setConsultMerchantCount(accumulator.getOrDefault("consultCount", 0L));
+        vo.setFriendsCount(accumulator.getOrDefault("friendCount", 0L));
+        vo.setFollowCount(accumulator.getOrDefault("followCount", 0L));
+        vo.setFansCount(accumulator.getOrDefault("fansCount", 0L));
+        vo.setPostsPublishedCount(accumulator.getOrDefault("postCount", 0L));
+        vo.setPostLikesCount(accumulator.getOrDefault("postLikeCount", 0L));
+        vo.setPostCommentsCount(accumulator.getOrDefault("postCommentCount", 0L));
+        vo.setPostSharesCount(accumulator.getOrDefault("postRepostCount", 0L));
+        vo.setReportedCount(accumulator.getOrDefault("reportCount", 0L));
+        vo.setBlockedCount(accumulator.getOrDefault("blockCount", 0L));
+        return vo;
+    }
+    
+    /**
+     * 转换为优惠券数据VO
+     */
+    private StoreOperationalStatisticsVo.CouponData convertToCouponDataVo(Map<String, Object> accumulator) {
+        StoreOperationalStatisticsVo.CouponData vo = new StoreOperationalStatisticsVo.CouponData();
+        vo.setGiftToFriendsCount(getLongValue(accumulator, "giveToFriendCount"));
+        vo.setGiftToFriendsAmount(getBigDecimalValue(accumulator, "giveToFriendAmount"));
+        vo.setGiftToFriendsUsedCount(getLongValue(accumulator, "giveToFriendUseCount"));
+        vo.setGiftToFriendsUsedAmount(getBigDecimalValue(accumulator, "giveToFriendUseAmount"));
+        
+        // 计算使用金额占比
+        BigDecimal giftAmount = getBigDecimalValue(accumulator, "giveToFriendAmount");
+        BigDecimal giftUsedAmount = getBigDecimalValue(accumulator, "giveToFriendUseAmount");
+        if (giftAmount.compareTo(BigDecimal.ZERO) > 0) {
+            vo.setGiftToFriendsUsedAmountRatio(giftUsedAmount.divide(giftAmount, 4, RoundingMode.HALF_UP)
+                    .multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
+        } else {
+            vo.setGiftToFriendsUsedAmountRatio(BigDecimal.ZERO);
+        }
+        
+        vo.setFriendsGiftCount(getLongValue(accumulator, "friendGiveCount"));
+        vo.setFriendsGiftAmount(getBigDecimalValue(accumulator, "friendGiveAmount"));
+        vo.setFriendsGiftUsedCount(getLongValue(accumulator, "friendGiveUseCount"));
+        vo.setFriendsGiftUsedAmount(getBigDecimalValue(accumulator, "friendGiveUseAmount"));
+        
+        // 计算好友赠送使用金额占比
+        BigDecimal friendAmount = getBigDecimalValue(accumulator, "friendGiveAmount");
+        BigDecimal friendUsedAmount = getBigDecimalValue(accumulator, "friendGiveUseAmount");
+        if (friendAmount.compareTo(BigDecimal.ZERO) > 0) {
+            vo.setFriendsGiftUsedAmountRatio(friendUsedAmount.divide(friendAmount, 4, RoundingMode.HALF_UP)
+                    .multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
+        } else {
+            vo.setFriendsGiftUsedAmountRatio(BigDecimal.ZERO);
+        }
+        
+        return vo;
+    }
+    
+    /**
+     * 转换为代金券数据VO
+     */
+    private StoreOperationalStatisticsVo.VoucherData convertToVoucherDataVo(Map<String, Object> accumulator) {
+        StoreOperationalStatisticsVo.VoucherData vo = new StoreOperationalStatisticsVo.VoucherData();
+        vo.setGiftToFriendsCount(getLongValue(accumulator, "giveToFriendCount"));
+        vo.setGiftToFriendsAmount(getBigDecimalValue(accumulator, "giveToFriendAmount"));
+        vo.setGiftToFriendsUsedCount(getLongValue(accumulator, "giveToFriendUseCount"));
+        vo.setGiftToFriendsUsedAmount(getBigDecimalValue(accumulator, "giveToFriendUseAmount"));
+        
+        // 计算使用金额占比
+        BigDecimal giftAmount = getBigDecimalValue(accumulator, "giveToFriendAmount");
+        BigDecimal giftUsedAmount = getBigDecimalValue(accumulator, "giveToFriendUseAmount");
+        if (giftAmount.compareTo(BigDecimal.ZERO) > 0) {
+            vo.setGiftToFriendsUsedAmountRatio(giftUsedAmount.divide(giftAmount, 4, RoundingMode.HALF_UP)
+                    .multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
+        } else {
+            vo.setGiftToFriendsUsedAmountRatio(BigDecimal.ZERO);
+        }
+        
+        vo.setFriendsGiftCount(getLongValue(accumulator, "friendGiveCount"));
+        vo.setFriendsGiftAmount(getBigDecimalValue(accumulator, "friendGiveAmount"));
+        vo.setFriendsGiftUsedCount(getLongValue(accumulator, "friendGiveUseCount"));
+        vo.setFriendsGiftUsedAmount(getBigDecimalValue(accumulator, "friendGiveUseAmount"));
+        
+        // 计算好友赠送使用金额占比
+        BigDecimal friendAmount = getBigDecimalValue(accumulator, "friendGiveAmount");
+        BigDecimal friendUsedAmount = getBigDecimalValue(accumulator, "friendGiveUseAmount");
+        if (friendAmount.compareTo(BigDecimal.ZERO) > 0) {
+            vo.setFriendsGiftUsedAmountRatio(friendUsedAmount.divide(friendAmount, 4, RoundingMode.HALF_UP)
+                    .multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
+        } else {
+            vo.setFriendsGiftUsedAmountRatio(BigDecimal.ZERO);
+        }
+        
+        return vo;
+    }
+    
+    /**
+     * 转换为服务质量数据VO
+     */
+    private StoreOperationalStatisticsVo.ServiceQualityData convertToServiceQualityDataVo(Map<String, Object> accumulator, int count) {
+        StoreOperationalStatisticsVo.ServiceQualityData vo = new StoreOperationalStatisticsVo.ServiceQualityData();
+        
+        // 计算平均评分
+        if (count > 0) {
+            BigDecimal storeScoreSum = getBigDecimalValue(accumulator, "storeScoreSum");
+            BigDecimal tasteScoreSum = getBigDecimalValue(accumulator, "tasteScoreSum");
+            BigDecimal environmentScoreSum = getBigDecimalValue(accumulator, "environmentScoreSum");
+            BigDecimal serviceScoreSum = getBigDecimalValue(accumulator, "serviceScoreSum");
+            
+            vo.setStoreRating(storeScoreSum.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP));
+            vo.setTasteRating(tasteScoreSum.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP));
+            vo.setEnvironmentRating(environmentScoreSum.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP));
+            vo.setServiceRating(serviceScoreSum.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP));
+        } else {
+            vo.setStoreRating(BigDecimal.ZERO);
+            vo.setTasteRating(BigDecimal.ZERO);
+            vo.setEnvironmentRating(BigDecimal.ZERO);
+            vo.setServiceRating(BigDecimal.ZERO);
+        }
+        
+        vo.setTotalReviews(getLongValue(accumulator, "ratingCount"));
+        vo.setPositiveReviews(getLongValue(accumulator, "goodRatingCount"));
+        vo.setNeutralReviews(getLongValue(accumulator, "midRatingCount"));
+        vo.setNegativeReviews(getLongValue(accumulator, "badRatingCount"));
+        
+        // 计算差评占比
+        Long ratingCount = getLongValue(accumulator, "ratingCount");
+        Long badRatingCount = getLongValue(accumulator, "badRatingCount");
+        if (ratingCount > 0) {
+            vo.setNegativeReviewRatio(BigDecimal.valueOf(badRatingCount)
+                    .divide(BigDecimal.valueOf(ratingCount), 4, RoundingMode.HALF_UP)
+                    .multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
+        } else {
+            vo.setNegativeReviewRatio(BigDecimal.ZERO);
+        }
+        
+        vo.setNegativeReviewAppealsCount(getLongValue(accumulator, "appealCount"));
+        vo.setNegativeReviewAppealsSuccessCount(getLongValue(accumulator, "appealSuccessCount"));
+        
+        // 计算差评申诉成功占比
+        Long appealCount = getLongValue(accumulator, "appealCount");
+        Long appealSuccessCount = getLongValue(accumulator, "appealSuccessCount");
+        if (appealCount > 0) {
+            vo.setNegativeReviewAppealsSuccessRatio(BigDecimal.valueOf(appealSuccessCount)
+                    .divide(BigDecimal.valueOf(appealCount), 4, RoundingMode.HALF_UP)
+                    .multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
+        } else {
+            vo.setNegativeReviewAppealsSuccessRatio(BigDecimal.ZERO);
+        }
+        
+        return vo;
+    }
+    
+    /**
+     * 转换为价目表排名数据VO
+     */
+    private List<StoreOperationalStatisticsVo.PriceListRanking> convertToPriceListRankingVo(Map<Integer, Map<String, Long>> accumulator) {
+        List<StoreOperationalStatisticsVo.PriceListRanking> result = new ArrayList<>();
+        
+        // 转换为列表并按浏览量降序排序
+        for (Map.Entry<Integer, Map<String, Long>> entry : accumulator.entrySet()) {
+            StoreOperationalStatisticsVo.PriceListRanking ranking = new StoreOperationalStatisticsVo.PriceListRanking();
+            ranking.setPriceId(entry.getKey());
+            Map<String, Long> data = entry.getValue();
+            ranking.setPageViews(data.getOrDefault("viewCount", 0L));
+            ranking.setVisitors(data.getOrDefault("visitorCount", 0L));
+            ranking.setShares(data.getOrDefault("shareCount", 0L));
+            
+            // 获取价目表名称(如果需要)
+            // ranking.setPriceListItemName(...);
+            
+            result.add(ranking);
+        }
+        
+        // 按浏览量降序排序
+        result.sort((a, b) -> Long.compare(b.getPageViews(), a.getPageViews()));
+        
+        // 设置排名
+        for (int i = 0; i < result.size(); i++) {
+            result.get(i).setRank(i + 1);
+        }
+        
+        return result;
+    }
+    
+    // ==================== 工具方法 ====================
+    
+    /**
+     * 从Map中获取Long值
+     */
+    private Long getLongValue(Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        if (value == null) return 0L;
+        if (value instanceof Long) return (Long) value;
+        if (value instanceof Number) return ((Number) value).longValue();
+        try {
+            return Long.parseLong(value.toString());
+        } catch (Exception e) {
+            return 0L;
+        }
+    }
+    
+    /**
+     * 从Map中获取Integer值
+     */
+    private Integer getIntegerValue(Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        if (value == null) return null;
+        if (value instanceof Integer) return (Integer) value;
+        if (value instanceof Number) return ((Number) value).intValue();
+        try {
+            return Integer.parseInt(value.toString());
+        } catch (Exception e) {
+            return null;
+        }
+    }
+    
+    /**
+     * 从Map中获取BigDecimal值
+     */
+    private BigDecimal getBigDecimalValue(Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        if (value == null) return BigDecimal.ZERO;
+        if (value instanceof BigDecimal) return (BigDecimal) value;
+        if (value instanceof Number) return BigDecimal.valueOf(((Number) value).doubleValue());
+        try {
+            return new BigDecimal(value.toString());
+        } catch (Exception e) {
+            return BigDecimal.ZERO;
+        }
+    }
 }

+ 31 - 1
alien-store/src/main/java/shop/alien/store/service/impl/StoreRenovationRequirementServiceImpl.java

@@ -419,6 +419,26 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
             dto.setAttachmentUrls(new ArrayList<>());
         }
 
+        // 填充商铺信息和商户头像
+        if (requirement.getStoreId() != null) {
+            // 查询商铺信息
+            StoreInfo storeInfo = storeInfoMapper.selectById(requirement.getStoreId());
+            if (storeInfo != null) {
+                dto.setStoreName(storeInfo.getStoreName());
+                dto.setStoreTel(storeInfo.getStoreTel());
+                dto.setStoreAddress(storeInfo.getStoreAddress());
+                dto.setStoreBlurb(storeInfo.getStoreBlurb());
+            }
+
+            // 查询商户头像(从store_user表获取head_img)
+            StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>()
+                    .eq(StoreUser::getStoreId, requirement.getStoreId())
+                    .eq(StoreUser::getDeleteFlag, 0));
+            if (storeUser != null && StringUtils.hasText(storeUser.getHeadImg())) {
+                dto.setStoreAvatar(storeUser.getHeadImg());
+            }
+        }
+
         return dto;
     }
 
@@ -503,7 +523,16 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
                 Map<Integer, StoreInfo> storeInfoMap = storeInfoList.stream()
                         .collect(Collectors.toMap(StoreInfo::getId, storeInfo -> storeInfo, (v1, v2) -> v1));
 
-                // 填充商铺信息到DTO
+                // 批量查询商户头像(store_user.head_img)
+                LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<StoreUser>()
+                        .in(StoreUser::getStoreId, storeIds)
+                        .eq(StoreUser::getDeleteFlag, 0);
+                List<StoreUser> storeUserList = storeUserMapper.selectList(userWrapper);
+                Map<Integer, String> storeAvatarMap = storeUserList.stream()
+                        .filter(u -> u.getStoreId() != null && u.getHeadImg() != null)
+                        .collect(Collectors.toMap(StoreUser::getStoreId, StoreUser::getHeadImg, (v1, v2) -> v1));
+
+                // 填充商铺信息(含头像)到DTO
                 dtoPage.getRecords().forEach(dto -> {
                     StoreInfo storeInfo = storeInfoMap.get(dto.getStoreId());
                     if (storeInfo != null) {
@@ -512,6 +541,7 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
                         dto.setStoreAddress(storeInfo.getStoreAddress());
                         dto.setStoreBlurb(storeInfo.getStoreBlurb());
                     }
+                    dto.setStoreAvatar(storeAvatarMap.get(dto.getStoreId()));
                 });
             }
 

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

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

+ 7 - 0
alien-store/src/main/java/shop/alien/store/util/CommonConstant.java

@@ -112,4 +112,11 @@ public class CommonConstant {
     public static final String RATING_LIKE = "11";
     public static final String COMMENT_LIKE = "12";
     public static final String DYNAMIC_LIKE = "13";
+
+
+    /**
+     * 上线状态,0:上线,2:下线
+     */
+    public static final Integer ONLINE_STATUS = 0;
+    public static final Integer OFFLINE_STATUS = 1;
 }

+ 33 - 0
alien-store/src/main/java/shop/alien/store/util/FileUploadUtil.java

@@ -45,6 +45,7 @@ public class FileUploadUtil {
     List<String> privacyFileType = Arrays.asList("htm","html");
     List<String> ohterFileType = Arrays.asList("xls","xlsx");
     List<String> appFileType = Arrays.asList("apk", "ipk", "wgt");
+    List<String> pdfFileType = Arrays.asList("pdf");
 
     /**
      * 上传文件
@@ -60,6 +61,8 @@ public class FileUploadUtil {
                 prefix = "image/";
             } else if (videoFileType.contains(fileNameAndType.get("type").toLowerCase())) {
                 prefix = "video/";
+            } else if (pdfFileType.contains(fileNameAndType.get("type").toLowerCase())) {
+                prefix = "pdf/";
             }
             // 去除文件名中的逗号,避免URL拼接时被错误分割
             String fileName = fileNameAndType.get("name").replaceAll(",", "");
@@ -92,6 +95,29 @@ public class FileUploadUtil {
     }
 
     /**
+     * 上传PDF文件
+     *
+     * @param multipartFile 文件名
+     * @return 文件路径
+     */
+    public String uploadPdf(MultipartFile multipartFile) {
+        try {
+            Map<String, String> fileNameAndType = FileUtil.getFileNameAndType(multipartFile);
+            if (!pdfFileType.contains(fileNameAndType.get("type").toLowerCase())) {
+                log.error("FileUpload.uploadPdf ERROR 该文件不是PDF格式文件");
+                return null;
+            }
+            String prefix = "pdf/";
+            // 去除文件名中的逗号,避免URL拼接时被错误分割
+            String fileName = fileNameAndType.get("name").replaceAll(",", "");
+            return aliOSSUtil.uploadFile(multipartFile, prefix + fileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type"));
+        } catch (Exception e) {
+            log.error("FileUpload.uploadPdf ERROR Msg={}", e.getMessage());
+            return null;
+        }
+    }
+
+    /**
      * 上传多个文件
      *
      * @param multipartRequest 多文件
@@ -155,6 +181,13 @@ public class FileUploadUtil {
                     // 去除文件名中的逗号,避免URL拼接时被错误分割
                     String privacyFileName = fileNameAndType.get("name").replaceAll(",", "");
                     filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + privacyFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
+                } else if (pdfFileType.contains(fileNameAndType.get("type").toLowerCase())) {
+                    uploadDir += "/pdf";
+                    prefix = "pdf/";
+                    log.info("FileUpload.uploadMoreFile 获取到PDF文件准备复制 {} {} {}", uploadDir, prefix, multipartFile.getOriginalFilename());
+                    // 去除文件名中的逗号,避免URL拼接时被错误分割
+                    String pdfFileName = fileNameAndType.get("name").replaceAll(",", "");
+                    filePathList.add(aliOSSUtil.uploadFile(multipartFile, prefix + pdfFileName + RandomCreateUtil.getRandomNum(6) + "." + fileNameAndType.get("type")));
                 } else if (ohterFileType.contains(fileNameAndType.get("type").toLowerCase())) {
                     uploadDir += "/other/";
                     prefix = "other/";

+ 242 - 0
alien-store/src/main/java/shop/alien/store/util/ImageToPdfUploadUtil.java

@@ -0,0 +1,242 @@
+package shop.alien.store.util;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.util.ali.AliOSSUtil;
+import shop.alien.util.common.RandomCreateUtil;
+import shop.alien.util.file.FileUtil;
+import shop.alien.util.pdf.ImageToPdfUtil;
+
+import org.springframework.util.StringUtils;
+
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 图片转PDF并上传工具类
+ * 接收前端上传的图片,转换为PDF后上传到OSS,返回PDF的线上URL
+ *
+ * @author system
+ * @since 2026-01-05
+ */
+@Slf4j
+@Component
+@RefreshScope
+@RequiredArgsConstructor
+public class ImageToPdfUploadUtil {
+
+    private final AliOSSUtil aliOSSUtil;
+
+    /**
+     * 支持的图片格式
+     */
+    private static final List<String> SUPPORTED_IMAGE_TYPES = Arrays.asList("jpg", "jpeg", "png", "bmp", "webp", "gif");
+
+    /**
+     * 将单张图片转换为PDF并上传到OSS(内存方式,不保存临时文件)
+     *
+     * @param imageFile 图片文件
+     * @return PDF的线上URL,失败返回null
+     */
+    public String imageToPdfAndUpload(MultipartFile imageFile) {
+        if (imageFile == null || imageFile.isEmpty()) {
+            log.error("ImageToPdfUploadUtil.imageToPdfAndUpload ERROR: 图片文件为空");
+            return null;
+        }
+
+        try {
+            // 验证文件类型
+            Map<String, String> fileNameAndType = FileUtil.getFileNameAndType(imageFile);
+            String fileType = fileNameAndType.get("type").toLowerCase();
+            if (!SUPPORTED_IMAGE_TYPES.contains(fileType)) {
+                log.error("ImageToPdfUploadUtil.imageToPdfAndUpload ERROR: 不支持的图片格式: {}", fileType);
+                return null;
+            }
+
+            // 直接从输入流读取图片并转换为PDF字节数组(内存方式)
+            byte[] pdfBytes = ImageToPdfUtil.imageToPdfBytes(imageFile.getInputStream());
+            if (pdfBytes == null || pdfBytes.length == 0) {
+                log.error("ImageToPdfUploadUtil.imageToPdfAndUpload ERROR: 图片转PDF失败");
+                return null;
+            }
+
+            // 生成OSS文件路径
+            String fileName = fileNameAndType.get("name").replaceAll(",", "");
+            String ossFilePath = "pdf/" + fileName + RandomCreateUtil.getRandomNum(6) + ".pdf";
+
+            // 将PDF字节数组转换为输入流并上传到OSS
+            ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes);
+            String pdfUrl = aliOSSUtil.uploadFile(pdfInputStream, ossFilePath);
+
+            return pdfUrl;
+        } catch (Exception e) {
+            log.error("ImageToPdfUploadUtil.imageToPdfAndUpload ERROR: {}", e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 将多张图片合并为一个PDF并上传到OSS(内存方式,不保存临时文件)
+     *
+     * @param imageFiles 图片文件列表
+     * @return PDF的线上URL,失败返回null
+     */
+    public String imagesToPdfAndUpload(List<MultipartFile> imageFiles) {
+        if (imageFiles == null || imageFiles.isEmpty()) {
+            log.error("ImageToPdfUploadUtil.imagesToPdfAndUpload ERROR: 图片文件列表为空");
+            return null;
+        }
+
+        try {
+            // 收集所有有效的图片输入流
+            List<java.io.InputStream> imageStreams = new ArrayList<>();
+            for (MultipartFile imageFile : imageFiles) {
+                if (imageFile == null || imageFile.isEmpty()) {
+                    continue;
+                }
+
+                // 验证文件类型
+                Map<String, String> fileNameAndType = FileUtil.getFileNameAndType(imageFile);
+                String fileType = fileNameAndType.get("type").toLowerCase();
+                if (!SUPPORTED_IMAGE_TYPES.contains(fileType)) {
+                    log.warn("ImageToPdfUploadUtil.imagesToPdfAndUpload WARN: 跳过不支持的图片格式: {}", fileType);
+                    continue;
+                }
+
+                // 直接使用输入流(内存方式)
+                imageStreams.add(imageFile.getInputStream());
+            }
+
+            if (imageStreams.isEmpty()) {
+                log.error("ImageToPdfUploadUtil.imagesToPdfAndUpload ERROR: 没有有效的图片文件");
+                return null;
+            }
+
+            // 将多张图片合并为一个PDF字节数组(内存方式)
+            byte[] pdfBytes = ImageToPdfUtil.imagesToPdfBytes(imageStreams);
+            if (pdfBytes == null || pdfBytes.length == 0) {
+                log.error("ImageToPdfUploadUtil.imagesToPdfAndUpload ERROR: 图片转PDF失败");
+                return null;
+            }
+
+            // 生成OSS文件路径
+            String ossFilePath = "pdf/merged_" + RandomCreateUtil.getRandomNum(6) + ".pdf";
+
+            // 将PDF字节数组转换为输入流并上传到OSS
+            ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes);
+            String pdfUrl = aliOSSUtil.uploadFile(pdfInputStream, ossFilePath);
+
+            return pdfUrl;
+        } catch (Exception e) {
+            log.error("ImageToPdfUploadUtil.imagesToPdfAndUpload ERROR: {}", e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 将单张图片转换为PDF(A4页面)并上传到OSS(内存方式,不保存临时文件)
+     *
+     * @param imageFile 图片文件
+     * @return PDF的线上URL,失败返回null
+     */
+    public String imageToPdfA4AndUpload(MultipartFile imageFile) {
+        if (imageFile == null || imageFile.isEmpty()) {
+            log.error("ImageToPdfUploadUtil.imageToPdfA4AndUpload ERROR: 图片文件为空");
+            return null;
+        }
+
+        try {
+            // 验证文件类型
+            Map<String, String> fileNameAndType = FileUtil.getFileNameAndType(imageFile);
+            String fileType = fileNameAndType.get("type").toLowerCase();
+            if (!SUPPORTED_IMAGE_TYPES.contains(fileType)) {
+                log.error("ImageToPdfUploadUtil.imageToPdfA4AndUpload ERROR: 不支持的图片格式: {}", fileType);
+                return null;
+            }
+
+            // 直接从输入流读取图片并转换为PDF字节数组(A4页面,内存方式)
+            byte[] pdfBytes = ImageToPdfUtil.imageToPdfA4Bytes(imageFile.getInputStream());
+            if (pdfBytes == null || pdfBytes.length == 0) {
+                log.error("ImageToPdfUploadUtil.imageToPdfA4AndUpload ERROR: 图片转PDF失败");
+                return null;
+            }
+
+            // 生成OSS文件路径
+            String fileName = fileNameAndType.get("name").replaceAll(",", "");
+            String ossFilePath = "pdf/" + fileName + RandomCreateUtil.getRandomNum(6) + ".pdf";
+
+            // 将PDF字节数组转换为输入流并上传到OSS
+            ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes);
+            String pdfUrl = aliOSSUtil.uploadFile(pdfInputStream, ossFilePath);
+
+            return pdfUrl;
+        } catch (Exception e) {
+            log.error("ImageToPdfUploadUtil.imageToPdfA4AndUpload ERROR: {}", e.getMessage(), e);
+            return null;
+        }
+    }
+
+    /**
+     * 将Base64编码的图片转换为PDF并上传到OSS(内存方式,不保存临时文件)
+     *
+     * @param base64Image Base64编码的图片字符串(支持带或不带data:image前缀)
+     * @return PDF的线上URL,失败返回null
+     */
+    public String base64ToPdfAndUpload(String base64Image) {
+        if (!StringUtils.hasText(base64Image)) {
+            log.error("ImageToPdfUploadUtil.base64ToPdfAndUpload ERROR: Base64图片字符串为空");
+            return null;
+        }
+
+        try {
+            // 处理Base64字符串:移除可能的前缀(如 data:image/png;base64,)
+            String base64Data = base64Image.trim();
+            if (base64Data.contains(",")) {
+                base64Data = base64Data.substring(base64Data.indexOf(",") + 1);
+            }
+
+            // 解码Base64为字节数组
+            byte[] imageBytes;
+            try {
+                imageBytes = Base64.getDecoder().decode(base64Data);
+            } catch (IllegalArgumentException e) {
+                log.error("ImageToPdfUploadUtil.base64ToPdfAndUpload ERROR: Base64解码失败: {}", e.getMessage());
+                return null;
+            }
+
+            if (imageBytes == null || imageBytes.length == 0) {
+                log.error("ImageToPdfUploadUtil.base64ToPdfAndUpload ERROR: Base64解码后图片数据为空");
+                return null;
+            }
+
+            // 将字节数组转换为输入流
+            ByteArrayInputStream imageInputStream = new ByteArrayInputStream(imageBytes);
+
+            // 转换为PDF字节数组(内存方式)
+            byte[] pdfBytes = ImageToPdfUtil.imageToPdfBytes(imageInputStream);
+            if (pdfBytes == null || pdfBytes.length == 0) {
+                log.error("ImageToPdfUploadUtil.base64ToPdfAndUpload ERROR: 图片转PDF失败");
+                return null;
+            }
+
+            // 生成OSS文件路径
+            String ossFilePath = "pdf/base64_" + RandomCreateUtil.getRandomNum(6) + ".pdf";
+
+            // 将PDF字节数组转换为输入流并上传到OSS
+            ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes);
+            String pdfUrl = aliOSSUtil.uploadFile(pdfInputStream, ossFilePath);
+
+            return pdfUrl;
+        } catch (Exception e) {
+            log.error("ImageToPdfUploadUtil.base64ToPdfAndUpload ERROR: {}", e.getMessage(), e);
+            return null;
+        }
+    }
+}

+ 63 - 0
alien-util/src/main/java/shop/alien/util/ali/AliOSSUtil.java

@@ -15,6 +15,7 @@ import org.springframework.web.multipart.MultipartFile;
 import shop.alien.util.file.FileUtil;
 
 import java.io.File;
+import java.io.InputStream;
 
 /**
  * 阿里云oss工具类
@@ -136,4 +137,66 @@ public class AliOSSUtil {
         }
     }
 
+    /**
+     * oss上传文件(从输入流)
+     *
+     * @param inputStream 输入流
+     * @param ossFilePath oss中文件全路径(image/xxx.jpg)
+     * @return filePath
+     */
+    public String uploadFile(InputStream inputStream, String ossFilePath) {
+        // 验证参数
+        if (inputStream == null) {
+            log.error("AliOSSUtil.uploadFile ERROR: inputStream is null");
+            return null;
+        }
+        
+        if (StringUtils.isEmpty(ossFilePath)) {
+            log.error("AliOSSUtil.uploadFile ERROR: ossFilePath is empty");
+            return null;
+        }
+        
+        // 验证ossFilePath格式 - 不能以/开头
+        if (ossFilePath.startsWith("/")) {
+            log.error("AliOSSUtil.uploadFile ERROR: ossFilePath cannot start with '/': {}", ossFilePath);
+            return null;
+        }
+        
+        if (ossFilePath.contains("..") || ossFilePath.contains("\\")) {
+            log.error("AliOSSUtil.uploadFile ERROR: invalid ossFilePath format: {}", ossFilePath);
+            return null;
+        }
+
+        // 创建OSSClient实例
+        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
+        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
+        OSS ossClient = new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
+        try {
+            // 创建PutObjectRequest对象(使用输入流)
+            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, ossFilePath, inputStream);
+            // 上传文件
+            PutObjectResult result = ossClient.putObject(putObjectRequest);
+            String eTag = result.getETag();
+            if (StringUtils.isNotEmpty(eTag)) {
+                return "https://" + bucketName + "." + endPoint + "/" + ossFilePath;
+            }
+            return null;
+        } catch (Exception e) {
+            log.error("AliOSSUtil.uploadFile ERROR: {}", e.getMessage(), e);
+            return null;
+        } finally {
+            // 关闭输入流
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (Exception e) {
+                    log.warn("AliOSSUtil.uploadFile WARN: 关闭输入流失败: {}", e.getMessage());
+                }
+            }
+            if (ossClient != null) {
+                ossClient.shutdown();
+            }
+        }
+    }
+
 }

+ 682 - 0
alien-util/src/main/java/shop/alien/util/pdf/ImageToPdfUtil.java

@@ -0,0 +1,682 @@
+package shop.alien.util.pdf;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 图片转PDF工具类
+ * 支持单张图片转PDF、多张图片合并为一个PDF
+ * 支持从本地文件、URL、字节数组转换
+ *
+ * @author system
+ * @since 2026-01-05
+ */
+public class ImageToPdfUtil {
+
+    /**
+     * 将单张图片文件转换为PDF
+     *
+     * @param imagePath 图片文件路径
+     * @param pdfPath   PDF文件保存路径
+     * @return 是否转换成功
+     */
+    public static boolean imageToPdf(String imagePath, String pdfPath) {
+        return imageToPdf(new File(imagePath), new File(pdfPath));
+    }
+
+    /**
+     * 将单张图片文件转换为PDF
+     *
+     * @param imageFile 图片文件
+     * @param pdfFile   PDF文件
+     * @return 是否转换成功
+     */
+    public static boolean imageToPdf(File imageFile, File pdfFile) {
+        if (imageFile == null || !imageFile.exists()) {
+            throw new IllegalArgumentException("图片文件不存在: " + (imageFile != null ? imageFile.getPath() : "null"));
+        }
+
+        try (InputStream imageStream = new FileInputStream(imageFile)) {
+            return imageToPdf(imageStream, pdfFile);
+        } catch (IOException e) {
+            throw new RuntimeException("读取图片文件失败: " + imageFile.getPath(), e);
+        }
+    }
+
+    /**
+     * 将图片输入流转换为PDF
+     *
+     * @param imageStream 图片输入流
+     * @param pdfFile     PDF文件
+     * @return 是否转换成功
+     */
+    public static boolean imageToPdf(InputStream imageStream, File pdfFile) {
+        if (imageStream == null) {
+            throw new IllegalArgumentException("图片输入流不能为空");
+        }
+        if (pdfFile == null) {
+            throw new IllegalArgumentException("PDF文件不能为空");
+        }
+
+        // 确保PDF文件目录存在
+        File parentDir = pdfFile.getParentFile();
+        if (parentDir != null && !parentDir.exists()) {
+            parentDir.mkdirs();
+        }
+
+        try (PDDocument document = new PDDocument()) {
+            BufferedImage bufferedImage = ImageIO.read(imageStream);
+            if (bufferedImage == null) {
+                throw new IllegalArgumentException("无法读取图片,可能不是有效的图片格式");
+            }
+
+            // 创建PDF页面,使用图片的尺寸
+            PDPage page = new PDPage(new PDRectangle(bufferedImage.getWidth(), bufferedImage.getHeight()));
+            document.addPage(page);
+
+            // 将图片添加到PDF页面
+            PDImageXObject pdImage = PDImageXObject.createFromByteArray(
+                    document, imageToByteArray(bufferedImage), "image");
+
+            try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
+                contentStream.drawImage(pdImage, 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());
+            }
+
+            document.save(pdfFile);
+            return true;
+        } catch (IOException e) {
+            throw new RuntimeException("图片转PDF失败", e);
+        }
+    }
+
+    /**
+     * 将图片URL转换为PDF
+     *
+     * @param imageUrl 图片URL
+     * @param pdfPath  PDF文件保存路径
+     * @return 是否转换成功
+     */
+    public static boolean imageUrlToPdf(String imageUrl, String pdfPath) {
+        return imageUrlToPdf(imageUrl, new File(pdfPath));
+    }
+
+    /**
+     * 将图片URL转换为PDF
+     *
+     * @param imageUrl 图片URL
+     * @param pdfFile  PDF文件
+     * @return 是否转换成功
+     */
+    public static boolean imageUrlToPdf(String imageUrl, File pdfFile) {
+        if (imageUrl == null || imageUrl.trim().isEmpty()) {
+            throw new IllegalArgumentException("图片URL不能为空");
+        }
+
+        HttpURLConnection connection = null;
+        try {
+            URL url = new URL(imageUrl);
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setConnectTimeout(10000); // 10秒连接超时
+            connection.setReadTimeout(30000);    // 30秒读取超时
+            connection.setRequestMethod("GET");
+            connection.setRequestProperty("User-Agent", "Mozilla/5.0");
+
+            InputStream imageStream = connection.getInputStream();
+            try {
+                return imageToPdf(imageStream, pdfFile);
+            } finally {
+                if (imageStream != null) {
+                    imageStream.close();
+                }
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("从URL下载图片失败: " + imageUrl, e);
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    /**
+     * 将图片字节数组转换为PDF
+     *
+     * @param imageBytes 图片字节数组
+     * @param pdfPath    PDF文件保存路径
+     * @return 是否转换成功
+     */
+    public static boolean imageBytesToPdf(byte[] imageBytes, String pdfPath) {
+        return imageBytesToPdf(imageBytes, new File(pdfPath));
+    }
+
+    /**
+     * 将图片字节数组转换为PDF
+     *
+     * @param imageBytes 图片字节数组
+     * @param pdfFile    PDF文件
+     * @return 是否转换成功
+     */
+    public static boolean imageBytesToPdf(byte[] imageBytes, File pdfFile) {
+        if (imageBytes == null || imageBytes.length == 0) {
+            throw new IllegalArgumentException("图片字节数组不能为空");
+        }
+
+        ByteArrayInputStream imageStream = new ByteArrayInputStream(imageBytes);
+        try {
+            return imageToPdf(imageStream, pdfFile);
+        } finally {
+            try {
+                imageStream.close();
+            } catch (IOException e) {
+                // ByteArrayInputStream的close()实际上不会抛出异常,但为了编译通过需要捕获
+            }
+        }
+    }
+
+    /**
+     * 将多张图片合并为一个PDF
+     *
+     * @param imagePaths 图片文件路径列表
+     * @param pdfPath    PDF文件保存路径
+     * @return 是否转换成功
+     */
+    public static boolean imagesToPdf(List<String> imagePaths, String pdfPath) {
+        return imagesToPdfFromPaths(imagePaths, new File(pdfPath));
+    }
+
+    /**
+     * 将多张图片合并为一个PDF(从文件路径)
+     *
+     * @param imagePaths 图片文件路径列表
+     * @param pdfFile    PDF文件
+     * @return 是否转换成功
+     */
+    public static boolean imagesToPdfFromPaths(List<String> imagePaths, File pdfFile) {
+        if (imagePaths == null || imagePaths.isEmpty()) {
+            throw new IllegalArgumentException("图片路径列表不能为空");
+        }
+
+        List<File> imageFiles = new ArrayList<>();
+        for (String imagePath : imagePaths) {
+            imageFiles.add(new File(imagePath));
+        }
+        return imagesToPdfFromFiles(imageFiles, pdfFile);
+    }
+
+    /**
+     * 将多张图片合并为一个PDF(从文件对象)
+     *
+     * @param imageFiles 图片文件列表
+     * @param pdfFile    PDF文件
+     * @return 是否转换成功
+     */
+    public static boolean imagesToPdfFromFiles(List<File> imageFiles, File pdfFile) {
+        if (imageFiles == null || imageFiles.isEmpty()) {
+            throw new IllegalArgumentException("图片文件列表不能为空");
+        }
+        if (pdfFile == null) {
+            throw new IllegalArgumentException("PDF文件不能为空");
+        }
+
+        // 确保PDF文件目录存在
+        File parentDir = pdfFile.getParentFile();
+        if (parentDir != null && !parentDir.exists()) {
+            parentDir.mkdirs();
+        }
+
+        try (PDDocument document = new PDDocument()) {
+            for (File imageFile : imageFiles) {
+                if (imageFile == null || !imageFile.exists()) {
+                    continue; // 跳过不存在的文件
+                }
+
+                try (FileInputStream imageStream = new FileInputStream(imageFile)) {
+                    BufferedImage bufferedImage = ImageIO.read(imageStream);
+                    if (bufferedImage == null) {
+                        continue; // 跳过无法读取的图片
+                    }
+
+                    // 创建PDF页面,使用图片的尺寸
+                    PDPage page = new PDPage(new PDRectangle(bufferedImage.getWidth(), bufferedImage.getHeight()));
+                    document.addPage(page);
+
+                    // 将图片添加到PDF页面
+                    PDImageXObject pdImage = PDImageXObject.createFromByteArray(
+                            document, imageToByteArray(bufferedImage), "image");
+
+                    try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
+                        contentStream.drawImage(pdImage, 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());
+                    }
+                } catch (IOException e) {
+                    // 记录错误但继续处理其他图片
+                    System.err.println("处理图片失败: " + imageFile.getPath() + ", 错误: " + e.getMessage());
+                }
+            }
+
+            if (document.getNumberOfPages() == 0) {
+                throw new RuntimeException("没有成功添加任何图片到PDF");
+            }
+
+            document.save(pdfFile);
+            return true;
+        } catch (IOException e) {
+            throw new RuntimeException("多张图片转PDF失败", e);
+        }
+    }
+
+    /**
+     * 将多张图片URL合并为一个PDF
+     *
+     * @param imageUrls 图片URL列表
+     * @param pdfPath   PDF文件保存路径
+     * @return 是否转换成功
+     */
+    public static boolean imageUrlsToPdf(List<String> imageUrls, String pdfPath) {
+        return imageUrlsToPdf(imageUrls, new File(pdfPath));
+    }
+
+    /**
+     * 将多张图片URL合并为一个PDF
+     *
+     * @param imageUrls 图片URL列表
+     * @param pdfFile   PDF文件
+     * @return 是否转换成功
+     */
+    public static boolean imageUrlsToPdf(List<String> imageUrls, File pdfFile) {
+        if (imageUrls == null || imageUrls.isEmpty()) {
+            throw new IllegalArgumentException("图片URL列表不能为空");
+        }
+
+        // 确保PDF文件目录存在
+        File parentDir = pdfFile.getParentFile();
+        if (parentDir != null && !parentDir.exists()) {
+            parentDir.mkdirs();
+        }
+
+        try (PDDocument document = new PDDocument()) {
+            for (String imageUrl : imageUrls) {
+                if (imageUrl == null || imageUrl.trim().isEmpty()) {
+                    continue; // 跳过空的URL
+                }
+
+                try {
+                    URL url = new URL(imageUrl);
+                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+                    connection.setConnectTimeout(10000);
+                    connection.setReadTimeout(30000);
+                    connection.setRequestMethod("GET");
+                    connection.setRequestProperty("User-Agent", "Mozilla/5.0");
+
+                    InputStream imageStream = connection.getInputStream();
+                    try {
+                        BufferedImage bufferedImage = ImageIO.read(imageStream);
+                        if (bufferedImage == null) {
+                            continue; // 跳过无法读取的图片
+                        }
+
+                        // 创建PDF页面,使用图片的尺寸
+                        PDPage page = new PDPage(new PDRectangle(bufferedImage.getWidth(), bufferedImage.getHeight()));
+                        document.addPage(page);
+
+                        // 将图片添加到PDF页面
+                        PDImageXObject pdImage = PDImageXObject.createFromByteArray(
+                                document, imageToByteArray(bufferedImage), "image");
+
+                        try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
+                            contentStream.drawImage(pdImage, 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());
+                        }
+                    } finally {
+                        if (imageStream != null) {
+                            imageStream.close();
+                        }
+                    }
+                } catch (IOException e) {
+                    // 记录错误但继续处理其他图片
+                    System.err.println("处理图片URL失败: " + imageUrl + ", 错误: " + e.getMessage());
+                }
+            }
+
+            if (document.getNumberOfPages() == 0) {
+                throw new RuntimeException("没有成功添加任何图片到PDF");
+            }
+
+            document.save(pdfFile);
+            return true;
+        } catch (IOException e) {
+            throw new RuntimeException("多张图片URL转PDF失败", e);
+        }
+    }
+
+    /**
+     * 将图片转换为字节数组
+     *
+     * @param bufferedImage 图片对象
+     * @return 图片字节数组
+     */
+    private static byte[] imageToByteArray(BufferedImage bufferedImage) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        String format = "png"; // 默认使用PNG格式
+        ImageIO.write(bufferedImage, format, baos);
+        return baos.toByteArray();
+    }
+
+    /**
+     * 将图片输入流转换为PDF字节数组(内存方式,不保存文件)
+     *
+     * @param imageStream 图片输入流
+     * @return PDF字节数组
+     */
+    public static byte[] imageToPdfBytes(InputStream imageStream) {
+        if (imageStream == null) {
+            throw new IllegalArgumentException("图片输入流不能为空");
+        }
+
+        try (PDDocument document = new PDDocument();
+             ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream()) {
+            BufferedImage bufferedImage = ImageIO.read(imageStream);
+            if (bufferedImage == null) {
+                throw new IllegalArgumentException("无法读取图片,可能不是有效的图片格式");
+            }
+
+            // 创建PDF页面,使用图片的尺寸
+            PDPage page = new PDPage(new PDRectangle(bufferedImage.getWidth(), bufferedImage.getHeight()));
+            document.addPage(page);
+
+            // 将图片添加到PDF页面
+            PDImageXObject pdImage = PDImageXObject.createFromByteArray(
+                    document, imageToByteArray(bufferedImage), "image");
+
+            try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
+                contentStream.drawImage(pdImage, 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());
+            }
+
+            // 将PDF保存到字节数组
+            document.save(pdfOutputStream);
+            return pdfOutputStream.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException("图片转PDF失败", e);
+        }
+    }
+
+    /**
+     * 将图片输入流转换为PDF字节数组(A4页面大小,内存方式)
+     *
+     * @param imageStream 图片输入流
+     * @return PDF字节数组
+     */
+    public static byte[] imageToPdfA4Bytes(InputStream imageStream) {
+        if (imageStream == null) {
+            throw new IllegalArgumentException("图片输入流不能为空");
+        }
+
+        try (PDDocument document = new PDDocument();
+             ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream()) {
+            BufferedImage bufferedImage = ImageIO.read(imageStream);
+            if (bufferedImage == null) {
+                throw new IllegalArgumentException("无法读取图片,可能不是有效的图片格式");
+            }
+
+            // 创建A4页面
+            PDPage page = new PDPage(PDRectangle.A4);
+            document.addPage(page);
+
+            // 计算图片在A4页面上的尺寸(保持宽高比)
+            float pageWidth = PDRectangle.A4.getWidth();
+            float pageHeight = PDRectangle.A4.getHeight();
+            float imageWidth = bufferedImage.getWidth();
+            float imageHeight = bufferedImage.getHeight();
+
+            // 计算缩放比例,使图片适应A4页面
+            float scaleX = pageWidth / imageWidth;
+            float scaleY = pageHeight / imageHeight;
+            float scale = Math.min(scaleX, scaleY);
+
+            float scaledWidth = imageWidth * scale;
+            float scaledHeight = imageHeight * scale;
+
+            // 居中显示
+            float x = (pageWidth - scaledWidth) / 2;
+            float y = (pageHeight - scaledHeight) / 2;
+
+            // 将图片添加到PDF页面
+            PDImageXObject pdImage = PDImageXObject.createFromByteArray(
+                    document, imageToByteArray(bufferedImage), "image");
+
+            try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
+                contentStream.drawImage(pdImage, x, y, scaledWidth, scaledHeight);
+            }
+
+            // 将PDF保存到字节数组
+            document.save(pdfOutputStream);
+            return pdfOutputStream.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException("图片转PDF失败", e);
+        }
+    }
+
+    /**
+     * 将单张图片转换为PDF(A4页面大小,图片自适应)
+     *
+     * @param imagePath 图片文件路径
+     * @param pdfPath   PDF文件保存路径
+     * @return 是否转换成功
+     */
+    public static boolean imageToPdfA4(String imagePath, String pdfPath) {
+        return imageToPdfA4(new File(imagePath), new File(pdfPath));
+    }
+
+    /**
+     * 将单张图片转换为PDF(A4页面大小,图片自适应)
+     *
+     * @param imageFile 图片文件
+     * @param pdfFile   PDF文件
+     * @return 是否转换成功
+     */
+    public static boolean imageToPdfA4(File imageFile, File pdfFile) {
+        if (imageFile == null || !imageFile.exists()) {
+            throw new IllegalArgumentException("图片文件不存在: " + (imageFile != null ? imageFile.getPath() : "null"));
+        }
+
+        // 确保PDF文件目录存在
+        File parentDir = pdfFile.getParentFile();
+        if (parentDir != null && !parentDir.exists()) {
+            parentDir.mkdirs();
+        }
+
+        try (PDDocument document = new PDDocument()) {
+            BufferedImage bufferedImage = ImageIO.read(imageFile);
+            if (bufferedImage == null) {
+                throw new IllegalArgumentException("无法读取图片,可能不是有效的图片格式");
+            }
+
+            // 创建A4页面
+            PDPage page = new PDPage(PDRectangle.A4);
+            document.addPage(page);
+
+            // 计算图片在A4页面上的尺寸(保持宽高比)
+            float pageWidth = PDRectangle.A4.getWidth();
+            float pageHeight = PDRectangle.A4.getHeight();
+            float imageWidth = bufferedImage.getWidth();
+            float imageHeight = bufferedImage.getHeight();
+
+            // 计算缩放比例,使图片适应A4页面
+            float scaleX = pageWidth / imageWidth;
+            float scaleY = pageHeight / imageHeight;
+            float scale = Math.min(scaleX, scaleY);
+
+            float scaledWidth = imageWidth * scale;
+            float scaledHeight = imageHeight * scale;
+
+            // 居中显示
+            float x = (pageWidth - scaledWidth) / 2;
+            float y = (pageHeight - scaledHeight) / 2;
+
+            // 将图片添加到PDF页面
+            PDImageXObject pdImage = PDImageXObject.createFromByteArray(
+                    document, imageToByteArray(bufferedImage), "image");
+
+            try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
+                contentStream.drawImage(pdImage, x, y, scaledWidth, scaledHeight);
+            }
+
+            document.save(pdfFile);
+            return true;
+        } catch (IOException e) {
+            throw new RuntimeException("图片转PDF失败", e);
+        }
+    }
+
+    /**
+     * 将多张图片合并为一个PDF(A4页面大小,每张图片一页,图片自适应)
+     *
+     * @param imageFiles 图片文件列表
+     * @param pdfFile    PDF文件
+     * @return 是否转换成功
+     */
+    public static boolean imagesToPdfA4(List<File> imageFiles, File pdfFile) {
+        if (imageFiles == null || imageFiles.isEmpty()) {
+            throw new IllegalArgumentException("图片文件列表不能为空");
+        }
+        if (pdfFile == null) {
+            throw new IllegalArgumentException("PDF文件不能为空");
+        }
+
+        // 确保PDF文件目录存在
+        File parentDir = pdfFile.getParentFile();
+        if (parentDir != null && !parentDir.exists()) {
+            parentDir.mkdirs();
+        }
+
+        try (PDDocument document = new PDDocument()) {
+            float pageWidth = PDRectangle.A4.getWidth();
+            float pageHeight = PDRectangle.A4.getHeight();
+
+            for (File imageFile : imageFiles) {
+                if (imageFile == null || !imageFile.exists()) {
+                    continue; // 跳过不存在的文件
+                }
+
+                try (FileInputStream imageStream = new FileInputStream(imageFile)) {
+                    BufferedImage bufferedImage = ImageIO.read(imageStream);
+                    if (bufferedImage == null) {
+                        continue; // 跳过无法读取的图片
+                    }
+
+                    // 创建A4页面
+                    PDPage page = new PDPage(PDRectangle.A4);
+                    document.addPage(page);
+
+                    // 计算图片在A4页面上的尺寸(保持宽高比)
+                    float imageWidth = bufferedImage.getWidth();
+                    float imageHeight = bufferedImage.getHeight();
+
+                    // 计算缩放比例,使图片适应A4页面
+                    float scaleX = pageWidth / imageWidth;
+                    float scaleY = pageHeight / imageHeight;
+                    float scale = Math.min(scaleX, scaleY);
+
+                    float scaledWidth = imageWidth * scale;
+                    float scaledHeight = imageHeight * scale;
+
+                    // 居中显示
+                    float x = (pageWidth - scaledWidth) / 2;
+                    float y = (pageHeight - scaledHeight) / 2;
+
+                    // 将图片添加到PDF页面
+                    PDImageXObject pdImage = PDImageXObject.createFromByteArray(
+                            document, imageToByteArray(bufferedImage), "image");
+
+                    try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
+                        contentStream.drawImage(pdImage, x, y, scaledWidth, scaledHeight);
+                    }
+                } catch (IOException e) {
+                    // 记录错误但继续处理其他图片
+                    System.err.println("处理图片失败: " + imageFile.getPath() + ", 错误: " + e.getMessage());
+                }
+            }
+
+            if (document.getNumberOfPages() == 0) {
+                throw new RuntimeException("没有成功添加任何图片到PDF");
+            }
+
+            document.save(pdfFile);
+            return true;
+        } catch (IOException e) {
+            throw new RuntimeException("多张图片转PDF失败", e);
+        }
+    }
+
+    /**
+     * 将多张图片输入流合并为一个PDF字节数组(内存方式,不保存文件)
+     *
+     * @param imageStreams 图片输入流列表
+     * @return PDF字节数组
+     */
+    public static byte[] imagesToPdfBytes(List<InputStream> imageStreams) {
+        if (imageStreams == null || imageStreams.isEmpty()) {
+            throw new IllegalArgumentException("图片输入流列表不能为空");
+        }
+
+        try (PDDocument document = new PDDocument();
+             ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream()) {
+            
+            for (InputStream imageStream : imageStreams) {
+                if (imageStream == null) {
+                    continue; // 跳过空的输入流
+                }
+
+                try {
+                    BufferedImage bufferedImage = ImageIO.read(imageStream);
+                    if (bufferedImage == null) {
+                        continue; // 跳过无法读取的图片
+                    }
+
+                    // 创建PDF页面,使用图片的尺寸
+                    PDPage page = new PDPage(new PDRectangle(bufferedImage.getWidth(), bufferedImage.getHeight()));
+                    document.addPage(page);
+
+                    // 将图片添加到PDF页面
+                    PDImageXObject pdImage = PDImageXObject.createFromByteArray(
+                            document, imageToByteArray(bufferedImage), "image");
+
+                    try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
+                        contentStream.drawImage(pdImage, 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());
+                    }
+                } catch (IOException e) {
+                    // 记录错误但继续处理其他图片
+                    System.err.println("处理图片输入流失败: " + e.getMessage());
+                } finally {
+                    // 关闭输入流
+                    if (imageStream != null) {
+                        try {
+                            imageStream.close();
+                        } catch (IOException e) {
+                            // 忽略关闭异常
+                        }
+                    }
+                }
+            }
+
+            if (document.getNumberOfPages() == 0) {
+                throw new RuntimeException("没有成功添加任何图片到PDF");
+            }
+
+            // 将PDF保存到字节数组
+            document.save(pdfOutputStream);
+            return pdfOutputStream.toByteArray();
+        } catch (IOException e) {
+            throw new RuntimeException("多张图片转PDF失败", e);
+        }
+    }
+}