Ver código fonte

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

dujian 2 meses atrás
pai
commit
7fc0d801fd
92 arquivos alterados com 6325 adições e 496 exclusões
  1. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCoupon.java
  2. 8 1
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponFriendRuleDetail.java
  3. 5 1
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponStoreFriend.java
  4. 8 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponUnavailableRules.java
  5. 7 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponUser.java
  6. 93 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreContract.java
  7. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreCuisine.java
  8. 1 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java
  9. 5 0
      alien-entity/src/main/java/shop/alien/entity/store/StorePrice.java
  10. 17 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineTypeResponseDto.java
  11. 4 1
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeDiscountCouponStoreFriendDto.java
  12. 35 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreContractQueryDto.java
  13. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerConsultationOrderVO.java
  14. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponFriendRuleDetailVo.java
  15. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponFriendRuleVo.java
  16. 7 1
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponVo.java
  17. 31 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreContractVo.java
  18. 9 1
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java
  19. 4 4
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/DiscountCouponPlatformVo.java
  20. 25 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/OperationalActivityUpdateResultVo.java
  21. 6 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseDetailVo.java
  22. 6 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseVo.java
  23. 21 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityCasePreviewVo.java
  24. 6 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupVO.java
  25. 3 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityVO.java
  26. 23 0
      alien-entity/src/main/java/shop/alien/mapper/CommonRatingMapper.java
  27. 26 18
      alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponFriendRuleDetailMapper.java
  28. 27 0
      alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponMapper.java
  29. 40 6
      alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponStoreFriendMapper.java
  30. 6 5
      alien-entity/src/main/java/shop/alien/mapper/LifeUserMapper.java
  31. 3 3
      alien-entity/src/main/java/shop/alien/mapper/LifeUserViolationMapper.java
  32. 36 0
      alien-entity/src/main/java/shop/alien/mapper/StoreContractMapper.java
  33. 13 2
      alien-entity/src/main/java/shop/alien/mapper/StoreImgMapper.java
  34. 5 3
      alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivityAchievementMapper.java
  35. 10 0
      alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivitySignupMapper.java
  36. 17 0
      alien-entity/src/main/resources/mapper/CommonRatingMapper.xml
  37. 4 3
      alien-entity/src/main/resources/mapper/LifeDiscountCouponFriendRuleDetailMapper.xml
  38. 24 4
      alien-entity/src/main/resources/mapper/LifeDiscountCouponMapper.xml
  39. 2 1
      alien-entity/src/main/resources/mapper/LifeDiscountCouponStoreFriendMapper.xml
  40. 2 2
      alien-entity/src/main/resources/mapper/LifeDiscountCouponUnavailableRulesMapper.xml
  41. 3 1
      alien-entity/src/main/resources/mapper/OrderReviewMapper.xml
  42. 84 0
      alien-entity/src/main/resources/mapper/StoreContractMapper.xml
  43. 17 14
      alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivityAchievementMapper.xml
  44. 1 0
      alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivityMapper.xml
  45. 9 0
      alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivitySignupMapper.xml
  46. 75 1
      alien-job/src/main/java/shop/alien/job/store/StoreOperationalActivityJob.java
  47. 76 1
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java
  48. 1 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/LifeCouponPlatformController.java
  49. 5 2
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java
  50. 259 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/PlatformStoreOperationalStatisticsController.java
  51. 128 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StoreContractController.java
  52. 6 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/LifeCouponPlatformService.java
  53. 79 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/PlatformStoreOperationalStatisticsService.java
  54. 73 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StoreContractService.java
  55. 108 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/LifeCouponPlatformServiceImpl.java
  56. 8 4
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/LifeDiscountCouponPlatformServiceImpl.java
  57. 136 19
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java
  58. 9 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivitySignupServiceImpl.java
  59. 1488 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/PlatformStoreOperationalStatisticsServiceImpl.java
  60. 243 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreContractServiceImpl.java
  61. 80 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/util/AiAuthTokenUtil.java
  62. 831 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/util/PlatformStatisticsComparisonImageUtil.java
  63. BIN
      alien-store-platform/src/main/resources/fonts/NotoSansCJKsc-Regular.otf
  64. 78 0
      alien-store/src/main/java/shop/alien/store/controller/AiUserSessionController.java
  65. 13 15
      alien-store/src/main/java/shop/alien/store/controller/AliController.java
  66. 48 7
      alien-store/src/main/java/shop/alien/store/controller/BarPerformanceController.java
  67. 68 17
      alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponController.java
  68. 39 24
      alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponStoreFriendController.java
  69. 149 5
      alien-store/src/main/java/shop/alien/store/controller/StoreCuisineController.java
  70. 22 0
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  71. 30 9
      alien-store/src/main/java/shop/alien/store/controller/StoreOperationalActivityController.java
  72. 124 7
      alien-store/src/main/java/shop/alien/store/controller/StorePriceController.java
  73. 2 2
      alien-store/src/main/java/shop/alien/store/dto/StoreOperationalActivityCaseQueryDto.java
  74. 8 2
      alien-store/src/main/java/shop/alien/store/service/BarPerformanceService.java
  75. 9 3
      alien-store/src/main/java/shop/alien/store/service/LifeDiscountCouponService.java
  76. 13 7
      alien-store/src/main/java/shop/alien/store/service/LifeDiscountCouponStoreFriendService.java
  77. 105 18
      alien-store/src/main/java/shop/alien/store/service/LifeStoreService.java
  78. 16 4
      alien-store/src/main/java/shop/alien/store/service/StoreOperationalActivityAchievementService.java
  79. 20 2
      alien-store/src/main/java/shop/alien/store/service/impl/BarPerformanceServiceImpl.java
  80. 43 12
      alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java
  81. 62 3
      alien-store/src/main/java/shop/alien/store/service/impl/LicenseAuditAsyncService.java
  82. 4 4
      alien-store/src/main/java/shop/alien/store/service/impl/LifeBlacklistServiceImpl.java
  83. 321 45
      alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponServiceImpl.java
  84. 339 173
      alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponStoreFriendServiceImpl.java
  85. 137 14
      alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java
  86. 5 4
      alien-store/src/main/java/shop/alien/store/service/impl/LifeUserViolationServiceImpl.java
  87. 4 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreCuisineServiceImpl.java
  88. 19 12
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  89. 20 4
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityAchievementServiceImpl.java
  90. 15 1
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityServiceImpl.java
  91. 190 2
      alien-store/src/main/java/shop/alien/store/service/impl/TrackEventServiceImpl.java
  92. 145 0
      alien-store/src/main/java/shop/alien/store/util/ai/AiUserSessionUtil.java

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCoupon.java

@@ -77,7 +77,7 @@ public class LifeDiscountCoupon extends Model<LifeDiscountCoupon> {
     @TableField(value = "minimum_spending_amount", fill = FieldFill.UPDATE)
     private BigDecimal minimumSpendingAmount;
 
-    @ApiModelProperty(value = "类型   1-优惠券  2-红包 3-平台优惠券")
+    @ApiModelProperty(value = "类型   1-优惠券  2-红包 3-平台优惠券 4代金券")
     @TableField("type")
     private Integer type;
 

+ 8 - 1
alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponFriendRuleDetail.java

@@ -29,13 +29,20 @@ public class LifeDiscountCouponFriendRuleDetail {
     private Integer ruleId;
 
     /**
-     * 优惠券id
+     * 优惠券id(优惠券时使用,对应 life_discount_coupon.id)
      */
     @ApiModelProperty(value = "优惠券id")
     @TableField("coupon_id")
     private Integer couponId;
 
     /**
+     * 代金券id(代金券时使用,对应 life_coupon.id)
+     */
+    @ApiModelProperty(value = "代金券id")
+    @TableField("voucher_id")
+    private String voucherId;
+
+    /**
      * 好友店铺id
      */
     @ApiModelProperty(value = "好友店铺id")

+ 5 - 1
alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponStoreFriend.java

@@ -36,10 +36,14 @@ public class LifeDiscountCouponStoreFriend extends Model<LifeDiscountCouponStore
     @TableField("store_user_id")
     private Integer storeUserId;
 
-    @ApiModelProperty(value = "券id")
+    @ApiModelProperty(value = "券id,优惠券时使用(life_discount_coupon.id)")
     @TableField("coupon_id")
     private Integer couponId;
 
+    @ApiModelProperty(value = "代金券id,代金券时使用(life_coupon.id)")
+    @TableField("voucher_id")
+    private String voucherId;
+
     @ApiModelProperty(value = "好友店铺id")
     @TableField("friend_store_user_id")
     private Integer friendStoreUserId;

+ 8 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponUnavailableRules.java

@@ -35,6 +35,14 @@ public class LifeDiscountCouponUnavailableRules extends Model<LifeDiscountCoupon
     @TableField("discount_coupon_id")
     private Integer discountCouponId;
 
+    @ApiModelProperty(value = "代金券id(life_coupon.id,代金券时使用)")
+    @TableField("voucher_id")
+    private String voucherId;
+
+    @ApiModelProperty(value = "类型 默认0 代金券为1")
+    @TableField("coupon_type")
+    private Integer couponType;
+
     @ApiModelProperty(value = "禁用规则类型(周规则限制:weekday_unavailable,节日规则限制:holiday_unavailable)")
     @TableField("unavailable_rule_type")
     private String unavailableRuleType;

+ 7 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponUser.java

@@ -85,6 +85,13 @@ public class LifeDiscountCouponUser extends Model<LifeDiscountCouponUser> {
      @TableField("welfare_id")
      private Integer welfareId;
 
+    /**
+     * 代金券id(代金券时使用,对应 life_coupon.id)
+     */
+    @ApiModelProperty(value = "代金券id")
+    @TableField("voucher_id")
+    private String voucherId;
+
     @Override
     protected Serializable pkVal() {
         return this.id;

+ 93 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreContract.java

@@ -0,0 +1,93 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * 合同表
+ *
+ * @author alien
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@EqualsAndHashCode(callSuper = false)
+@TableName("store_contract")
+@ApiModel(value = "StoreContract对象", description = "合同表")
+public class StoreContract extends Model<StoreContract> {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "店铺ID")
+    @TableField("store_id")
+    private Long storeId;
+
+    @ApiModelProperty(value = "商家店铺名称")
+    @TableField("store_name")
+    private String storeName;
+
+    @ApiModelProperty(value = "经营板块")
+    @TableField("business_segment")
+    private String businessSegment;
+
+    @ApiModelProperty(value = "商家姓名")
+    @TableField("merchant_name")
+    private String merchantName;
+
+    @ApiModelProperty(value = "联系电话")
+    @TableField("contact_phone")
+    private String contactPhone;
+
+    @ApiModelProperty(value = "签署状态(已签署,未签署,已到期)")
+    @TableField("signing_status")
+    private String signingStatus;
+
+    @ApiModelProperty(value = "合同URL")
+    @TableField("contract_url")
+    private String contractUrl;
+
+    @ApiModelProperty(value = "印章URL")
+    @TableField("seal_url")
+    private String sealUrl;
+
+    @ApiModelProperty(value = "签署时间")
+    @TableField("signing_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signingTime;
+
+    @ApiModelProperty(value = "生效时间")
+    @TableField("effective_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date effectiveTime;
+
+    @ApiModelProperty(value = "到期时间")
+    @TableField("expiry_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date expiryTime;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "更新时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "删除标识, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+}
+

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreCuisine.java

@@ -132,6 +132,12 @@ public class StoreCuisine {
     @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date updatedTime;
+
+    @ApiModelProperty(value = "AI审核时间")
+    @TableField(value = "audit_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditTime;
+
 }
 
 

+ 1 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java

@@ -359,4 +359,5 @@ public class StoreInfo {
     @TableField("tableware_fee")
     private Integer tablewareFee;
 
+
 }

+ 5 - 0
alien-entity/src/main/java/shop/alien/entity/store/StorePrice.java

@@ -121,6 +121,11 @@ public class StorePrice {
     @ApiModelProperty("门店名称")
     @TableField(exist = false)
     private String storeName;
+
+    @ApiModelProperty(value = "AI审核时间")
+    @TableField(value = "audit_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditTime;
 }
 
 

+ 17 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineTypeResponseDto.java

@@ -1,10 +1,12 @@
 package shop.alien.entity.store.dto;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.math.BigDecimal;
+import java.util.Date;
 
 /**
  * 根据类型获取美食详情响应 DTO
@@ -91,6 +93,21 @@ public class CuisineTypeResponseDto {
 
         @ApiModelProperty("门店名称")
         private String storeName;
+
+        @ApiModelProperty("提交时间")
+        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+        private Date createTime;
+
+        @ApiModelProperty("审核时间")
+        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+        private Date auditTime;
+
+        @ApiModelProperty("审核状态")
+        private Integer auditStatus;
+
+        @ApiModelProperty("上下架状态")
+        private Integer shelfStatus;
+
     }
 }
 

+ 4 - 1
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeDiscountCouponStoreFriendDto.java

@@ -33,9 +33,12 @@ public class LifeDiscountCouponStoreFriendDto extends Model<LifeDiscountCouponSt
     @ApiModelProperty(value = "店铺用户id")
     private Integer storeUserId;
 
-    @ApiModelProperty(value = "券id")
+    @ApiModelProperty(value = "券id,优惠券时传(对应 life_discount_coupon.id)")
     private Integer couponId;
 
+    @ApiModelProperty(value = "代金券id,代金券时传(对应 life_coupon.id)")
+    private String voucherId;
+
     @ApiModelProperty(value = "好友店铺id")
     private Integer friendStoreUserId;
 

+ 35 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreContractQueryDto.java

@@ -0,0 +1,35 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 合同查询DTO
+ *
+ * @author alien
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreContractQueryDto对象", description = "合同查询DTO")
+public class StoreContractQueryDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "合同名称(模糊查询)")
+    private String contractName;
+
+    @ApiModelProperty(value = "合同状态(0:未签署, 1:已签署)")
+    private Integer status;
+
+    @ApiModelProperty(value = "店铺ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "页码", required = true)
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "每页数量", required = true)
+    private Integer pageSize = 10;
+}
+

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

@@ -255,6 +255,9 @@ public class LawyerConsultationOrderVO implements Serializable {
     @ApiModelProperty(value = "再次举报状态,0-不可以再次举报,1-可以再次举报")
     private  String repeatViolationStatus;
 
+    @ApiModelProperty(value = "是否允许再次评论,true-允许,false-不允许(评论被举报且举报通过,评论已删除)")
+    private Boolean canCommentAgain;
+
     /**
      * 获取执业年限(根据执业开始日期自动计算)
      * 返回当前时间减去执业开始时间的年数

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

@@ -22,4 +22,7 @@ public class LifeDiscountCouponFriendRuleDetailVo extends LifeDiscountCouponFrie
     private Integer couponNum;
 
     private Integer friendStoreUserId;
+
+    /** 代金券id(type=4 时返回,对应 life_coupon.id) */
+    private String voucherId;
 }

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponFriendRuleVo.java

@@ -42,6 +42,12 @@ public class LifeDiscountCouponFriendRuleVo extends LifeDiscountCouponFriendRule
     @ApiModelProperty(value = "优惠券id")
     private Integer couponId;
 
+    @ApiModelProperty(value = "代金券id,type=4 时有值")
+    private String voucherId;
+
+    @ApiModelProperty(value = "类型:1=优惠券,4=代金券")
+    private Integer type;
+
     @ApiModelProperty(value = "面值")
     private BigDecimal nominalValue;
 

+ 7 - 1
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponVo.java

@@ -45,6 +45,9 @@ public class LifeDiscountCouponVo {
     @ApiModelProperty(value = "券id")
     private Integer couponId;
 
+    @ApiModelProperty(value = "代金券id(来自life_coupon时使用)")
+    private String voucherId;
+
     @ApiModelProperty(value = "是否可领")
     private boolean canReceived;
 
@@ -84,9 +87,12 @@ public class LifeDiscountCouponVo {
     @ApiModelProperty(value = "类型   1-优惠券  2-红包")
     private Integer type;
 
-    @ApiModelProperty(value = "状态(0:进行中,1:已结束,2:未开始,3:活动暂停)")
+    @ApiModelProperty(value = "状态(0:进行中,1:已结束,2:未开始,3:已暂停,4:已售罄,5:草稿)")
     private Integer status;
 
+    @ApiModelProperty(value = "优惠券状态文案,用于前端展示:进行中/已结束/未开始/已暂停/已售罄/草稿")
+    private String statusDesc;
+
     @ApiModelProperty(value = "收藏可领(0:否,1:是)")
     private Integer attentionCanReceived;
 

+ 31 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreContractVo.java

@@ -0,0 +1,31 @@
+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.StoreContract;
+
+/**
+ * 合同 VO
+ *
+ * @author alien
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreContractVo对象", description = "合同VO")
+public class StoreContractVo extends StoreContract {
+
+    @ApiModelProperty(value = "状态名称")
+    private String statusName;
+
+    @ApiModelProperty(value = "店铺名称")
+    private String storeName;
+
+    @ApiModelProperty(value = "签署人名称")
+    private String signedUserName;
+
+    private String status;
+}
+

+ 9 - 1
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java

@@ -59,10 +59,14 @@ public class StoreOperationalActivity {
     @TableField("reward_type")
     private String rewardType;
 
-    @ApiModelProperty(value = "优惠券ID,关联优惠券表")
+    @ApiModelProperty(value = "优惠券ID,关联优惠券表 life_discount_coupon")
     @TableField("coupon_id")
     private Integer couponId;
 
+//    @ApiModelProperty(value = "代金券ID,关联代金券表 life_coupon(评论有礼时可二选一或同时配置)")
+//    @TableField("voucher_id")
+//    private Integer voucherId;
+
     @ApiModelProperty(value = "优惠券发放数量")
     @TableField("coupon_quantity")
     private Integer couponQuantity;
@@ -140,5 +144,9 @@ public class StoreOperationalActivity {
     @ApiModelProperty(value = "上传图片类型:0-本地上传, 1-AI生成")
     @TableField("upload_img_type")
     private Integer uploadImgType;
+
+    @ApiModelProperty(value = "审核状态:0-待审核, 1-审核通过, 2-审核拒绝")
+    @TableField("audit_status")
+    private Integer auditStatus;
 }
 

+ 4 - 4
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/DiscountCouponPlatformVo.java

@@ -6,15 +6,15 @@ import lombok.Data;
 import shop.alien.entity.store.vo.LifeDiscountCouponVo;
 
 /**
- * 优惠券
+ * 优惠券/代金券列表(统一用 LifeDiscountCouponVo 格式,代金券 type=4)
  */
 @Data
 @JsonInclude
 public class DiscountCouponPlatformVo {
 
-    // 代金券列表
-    public IPage<LifeCouponPlatformDto> couponList;
-    // 优惠券列表
+    /** 代金券列表(couponType=1 时返回,与优惠券同结构,type=4) */
+    public IPage<LifeDiscountCouponVo> couponList;
+    /** 优惠券列表(couponType!=1 时返回) */
     public IPage<LifeDiscountCouponVo> discountList;
 
 }

+ 25 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/OperationalActivityUpdateResultVo.java

@@ -0,0 +1,25 @@
+package shop.alien.entity.storePlatform.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 运营活动更新接口返回对象(仅增加返回字段,不改变原有逻辑)
+ *
+ * @author system
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel(value = "OperationalActivityUpdateResultVo", description = "运营活动更新返回对象")
+public class OperationalActivityUpdateResultVo {
+
+    @ApiModelProperty(value = "提示信息")
+    private String message;
+
+    @ApiModelProperty(value = "优惠券类型:1-优惠券, 2-红包, 3-平台优惠券, 4-代金券")
+    private Integer couponType;
+}

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseDetailVo.java

@@ -59,6 +59,12 @@ public class StoreOperationalActivityAchievementCaseDetailVo {
     @ApiModelProperty(value = "上传情况:0-未上传, 1-已上传")
     private Integer hasResult;
 
+    @ApiModelProperty(value = "报名人姓名(signupName)")
+    private String signupName;
+
+    @ApiModelProperty(value = "报名人手机号(signupPhone)")
+    private String signupPhone;
+
     @ApiModelProperty(value = "成果列表")
     private List<StoreOperationalActivityAchievementCaseItemVo> achievementList;
 }

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseVo.java

@@ -72,4 +72,10 @@ public class StoreOperationalActivityAchievementCaseVo {
 
     @ApiModelProperty(value = "用户头像")
     private String userImage;
+
+    @ApiModelProperty(value = "报名人姓名(signupName)")
+    private String signupName;
+
+    @ApiModelProperty(value = "报名人手机号(signupPhone)")
+    private String signupPhone;
 }

+ 21 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityCasePreviewVo.java

@@ -0,0 +1,21 @@
+package shop.alien.entity.storePlatform.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 案例卡片概览返回:列表默认一条,外层带总数
+ */
+@Data
+@ApiModel(value = "StoreOperationalActivityCasePreviewVo", description = "案例卡片概览(列表+总数)")
+public class StoreOperationalActivityCasePreviewVo {
+
+    @ApiModelProperty(value = "案例列表,默认返回一条")
+    private List<StoreOperationalActivityAchievementCaseVo> list;
+
+    @ApiModelProperty(value = "案例总数")
+    private Long total;
+}

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupVO.java

@@ -46,6 +46,12 @@ public class StoreOperationalActivitySignupVO {
     @ApiModelProperty(value = "手机号")
     private String phone;
 
+    @ApiModelProperty(value = "报名人姓名(signupname)")
+    private String signupName;
+
+    @ApiModelProperty(value = "报名人手机号(signupphone)")
+    private String signupPhone;
+
     @ApiModelProperty(value = "所属活动")
     private String activityName;
 

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityVO.java

@@ -63,5 +63,8 @@ public class StoreOperationalActivityVO extends StoreOperationalActivity {
 
     @ApiModelProperty(value = "上传图片类型:0-本地上传, 1-AI生成")
     private Integer uploadImgType;
+
+    @ApiModelProperty(value = "优惠券类型:1-优惠券, 2-红包, 3-平台优惠券, 4-代金券")
+    private Integer couponType;
 }
 

+ 23 - 0
alien-entity/src/main/java/shop/alien/mapper/CommonRatingMapper.java

@@ -1,5 +1,6 @@
 package shop.alien.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -72,5 +73,27 @@ public interface CommonRatingMapper extends BaseMapper<CommonRating> {
     int logicDeleteById(@Param("id") Integer id);
 
     IPage<CommonRating> getMyRatingList(Page<CommonRating> page, @Param(Constants.WRAPPER) QueryWrapper<CommonRating> wrapper);
+
+    /**
+     * 分页查询评价列表(支持 wrapper 条件,如 business_id、business_type、audit_status、is_show 等)
+     *
+     * @param page    分页参数
+     * @param wrapper 查询条件,例如:
+     *                wrapper.eq(CommonRating::getBusinessId, businessId);
+     *                wrapper.eq(CommonRating::getBusinessType, businessType);
+     *                wrapper.eq(CommonRating::getAuditStatus, 1);
+     *                wrapper.eq(CommonRating::getIsShow, 1);
+     * @return 分页结果
+     */
+    Integer getRatingWithNoReply( @Param(Constants.WRAPPER) Wrapper<CommonRating> wrapper);
+
+    /**
+     * 统计该用户在该店铺下已通过审核的好评次数(用于好评送券参与次数限制)
+     * @param userId 用户ID
+     * @param storeId 店铺ID(business_id)
+     * @return 已通过的好评条数(business_type=1, audit_status=1, score>=4.5)
+     */
+    @Select("SELECT COUNT(*) FROM common_rating WHERE business_type = 1 AND business_id = #{storeId} AND user_id = #{userId} AND audit_status = 1 AND score >= 4.5 AND delete_flag = 0")
+    int countPassedGoodRatingsByUserAndStore(@Param("userId") Long userId, @Param("storeId") Integer storeId);
 }
 

+ 26 - 18
alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponFriendRuleDetailMapper.java

@@ -25,25 +25,33 @@ public interface LifeDiscountCouponFriendRuleDetailMapper extends BaseMapper<Lif
     @Select("select si.store_name storeName,ldc.name couponName,ldc.id couponId,sum(ldcsf.single_qty) couponNum,ldcsf.friend_store_user_id friendStoreUserId from life_discount_coupon_store_friend ldcsf left join life_discount_coupon ldc on ldcsf.coupon_id = ldc.id left join store_user su on ldcsf.friend_store_user_id = su.id left join store_info si on su.store_id = si.id ${ew.customSqlSegment}")
     List<LifeDiscountCouponFriendRuleDetailVo> getReceivedFriendCouponList(@Param(Constants.WRAPPER) QueryWrapper<LifeDiscountCouponFriendRuleDetailVo> queryWrapper);
 
-    @Select("SELECT\n" +
-            "\ta.*,\n" +
-            "\t(select MAX( end_get_date ) from life_discount_coupon c left join life_discount_coupon_friend_rule_detail b on c.id = b.coupon_id where b.rule_id = a.id) endDate \n" +
-            "FROM\n" +
-            "\tlife_discount_coupon_friend_rule a\n" +
-            "\t where a.store_id = #{storeId} and a.delete_flag = 0 order by endDate")
-    List<LifeDiscountCouponFriendRuleVo> getRuleList(@Param("storeId")String storeId);
-
-    @Select("SELECT a.*, c.name as couponName, c.id as couponId,si.store_name storeName, " +
-            "(SELECT\n" +
-            "\t\tsum( single_qty ) \n" +
-            "\tFROM\n" +
-            "\t\tlife_discount_coupon_store_friend b\n" +
-            "\tWHERE\n" +
-            "\t\tb.friend_store_user_id = a.friend_store_user_id \n" +
-            "\t\tAND b.coupon_id = a.coupon_id \n" +
-            "\t\tAND b.store_user_id = a.store_id) couponNum " +
+    /** 查询收到的代金券列表(life_coupon,voucher_id 不为空) */
+    @Select("select si.store_name storeName,lc.name couponName,lc.id voucherId,sum(ldcsf.single_qty) couponNum,ldcsf.friend_store_user_id friendStoreUserId from life_discount_coupon_store_friend ldcsf left join life_coupon lc on ldcsf.voucher_id = lc.id left join store_user su on ldcsf.friend_store_user_id = su.id left join store_info si on su.store_id = si.id ${ew.customSqlSegment}")
+    List<LifeDiscountCouponFriendRuleDetailVo> getReceivedFriendVoucherList(@Param(Constants.WRAPPER) QueryWrapper<LifeDiscountCouponFriendRuleDetailVo> queryWrapper);
+
+    /** 一条 SQL 同时查规则下的优惠券(type=1)与代金券(type=4),按 type 区分 */
+    @Select("SELECT a.id, a.store_id, a.ac_name, a.money_low, a.money_high, a.delete_flag, " +
+            "CASE WHEN d.voucher_id IS NOT NULL THEN 4 ELSE 1 END AS type, " +
+            "COALESCE(c.end_get_date, v.end_date) AS endDate, " +
+            "COALESCE(c.name, v.name) AS couponName, " +
+            "c.id AS couponId, " +
+            "d.voucher_id AS voucherId " +
+            "FROM life_discount_coupon_friend_rule a " +
+            "INNER JOIN life_discount_coupon_friend_rule_detail d ON d.rule_id = a.id " +
+            "LEFT JOIN life_discount_coupon c ON d.coupon_id = c.id " +
+            "LEFT JOIN life_coupon v ON d.voucher_id = v.id " +
+            "WHERE a.store_id = #{storeId} AND a.delete_flag = 0 " +
+            "ORDER BY endDate DESC")
+    List<LifeDiscountCouponFriendRuleVo> getRuleList(@Param("storeId") String storeId);
+
+    @Select("SELECT a.*, c.name as couponName, c.id as couponId, " +
+            "si_friend.store_name AS storeName, " +
+            "(SELECT sum(single_qty) FROM life_discount_coupon_store_friend b " +
+            "WHERE b.friend_store_user_id = a.friend_store_user_id AND b.coupon_id = a.coupon_id AND b.store_user_id = a.store_id) couponNum " +
             "FROM life_discount_coupon_friend_rule_detail a " +
-            "LEFT JOIN life_discount_coupon c ON a.coupon_id = c.id LEFT JOIN store_info si on c.store_id = si.id " +
+            "LEFT JOIN life_discount_coupon c ON a.coupon_id = c.id " +
+            "LEFT JOIN store_user su ON a.friend_store_user_id = su.store_id "+
+            "LEFT JOIN store_info si_friend ON su.store_id = si_friend.id " +
             "WHERE a.rule_id = #{ruleId}")
     List<LifeDiscountCouponFriendRuleDetailVo> getDetailList(@Param("ruleId")String ruleId);
 }

+ 27 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponMapper.java

@@ -1,5 +1,6 @@
 package shop.alien.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -7,8 +8,12 @@ import com.baomidou.mybatisplus.core.toolkit.Constants;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
+import shop.alien.entity.store.LifeCoupon;
 import shop.alien.entity.store.LifeDiscountCoupon;
 import shop.alien.entity.store.vo.LifeDiscountCouponPlatformVo;
+import shop.alien.entity.store.vo.LifeDiscountCouponVo;
+
+import java.util.List;
 
 /**
  * <p>
@@ -48,4 +53,26 @@ public interface LifeDiscountCouponMapper extends BaseMapper<LifeDiscountCoupon>
      */
     @Update("UPDATE life_discount_coupon SET single_qty = #{singleQty} where id= #{id}")
     Integer updateCouponById(@Param("id") Integer id, @Param("singleQty") Integer singleQty);
+
+
+    List<LifeDiscountCouponVo> getList(String storeId,int couponStatus,Integer type);
+
+    /**
+     * 关联 life_coupon 查询代金券(仅 type=4):按店铺、券状态、有库存
+     *
+     * @param storeId      店铺ID
+     * @param couponStatus 券状态
+     * @return 符合条件的 life_discount_coupon 列表
+     */
+    List<LifeDiscountCoupon> selectListJoinLifeCoupon(@Param("storeId") String storeId, @Param("couponStatus") Integer couponStatus);
+
+    /**
+     * 单表查询 life_discount_coupon(type 不为 4 时使用)
+     *
+     * @param storeId      店铺ID
+     * @param couponStatus 券状态
+     * @param type         类型,可为 null(不按 type 过滤)
+     * @return 符合条件的 life_discount_coupon 列表
+     */
+    List<LifeDiscountCoupon> selectListSingleTable(@Param("storeId") String storeId, @Param("couponStatus") Integer couponStatus, @Param("type") Integer type);
 }

+ 40 - 6
alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponStoreFriendMapper.java

@@ -35,11 +35,14 @@ public interface LifeDiscountCouponStoreFriendMapper extends BaseMapper<LifeDisc
             "${ew.customSqlSegment}")
     IPage<LifeDiscountCouponVo> selectPage(IPage<LifeDiscountCouponStoreFriendVo> iPage, @Param(Constants.WRAPPER) QueryWrapper<LifeDiscountCouponStoreFriendVo> friendLifeDiscountCouponQueryWrapper);
 
+    /** 好友赠我 - 优惠券 */
     @Select("select ldcsf.created_time endDate,ldc.nominal_value nominalValue,ldc.minimum_spending_amount minimumSpendingAmount,img.img_url imgUrl,\n" +
             "si.store_name storeName,\n" +
             "ldc.name couponName,\n" +
             "ldc.id couponId,\n" +
-            "ldcsf.single_qty couponNum\n" +
+            "ldcsf.single_qty couponNum,\n" +
+            "1 as type,\n" +
+            "null as voucherId\n" +
             "from  life_discount_coupon_store_friend ldcsf\n" +
             "left join life_discount_coupon ldc\n" +
             "on ldc.id = ldcsf.coupon_id and ldc.delete_flag = 0\n" +
@@ -50,22 +53,53 @@ public interface LifeDiscountCouponStoreFriendMapper extends BaseMapper<LifeDisc
             "${ew.customSqlSegment}")
     List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(@Param(Constants.WRAPPER) QueryWrapper<LifeDiscountCouponFriendRuleVo> lifeDiscountCouponFriendRuleVoQueryWrapper);
 
-    //我赠好友
+    /** 好友赠我 - 代金券(type=4) */
+    @Select("select ldcsf.created_time endDate,CAST(lc.price AS DECIMAL(10,2)) nominalValue,null minimumSpendingAmount,img.img_url imgUrl,\n" +
+            "si.store_name storeName,\n" +
+            "lc.name couponName,\n" +
+            "null couponId,\n" +
+            "ldcsf.single_qty couponNum,\n" +
+            "4 as type,\n" +
+            "lc.id voucherId\n" +
+            "from life_discount_coupon_store_friend ldcsf\n" +
+            "left join life_coupon lc on lc.id = ldcsf.voucher_id and lc.delete_flag = 0\n" +
+            "left join store_info si on si.id = lc.store_id and si.delete_flag = 0\n" +
+            "left join store_user su on si.id = su.store_id\n" +
+            "left join store_img img on si.id = img.store_id and img.img_type = 1 and img.delete_flag = 0\n" +
+            "${ew.customSqlSegment}")
+    List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponListVoucher(@Param(Constants.WRAPPER) QueryWrapper<LifeDiscountCouponFriendRuleVo> lifeDiscountCouponFriendRuleVoQueryWrapper);
+
+    /** 我赠好友 - 优惠券 */
     @Select("select ldcsf.created_time endDate,ldc.nominal_value nominalValue,ldc.minimum_spending_amount minimumSpendingAmount,img.img_url imgUrl,\n" +
             "si.store_name storeName,\n" +
             "ldc.name couponName,\n" +
             "ldc.id couponId,\n" +
-            "ldcsf.single_qty couponNum\n" +
+            "ldcsf.single_qty couponNum,\n" +
+            "1 as type,\n" +
+            "null as voucherId\n" +
             "from  life_discount_coupon_store_friend ldcsf\n" +
             "left join life_discount_coupon ldc\n" +
             "on ldc.id = ldcsf.coupon_id and ldc.delete_flag = 0\n" +
-
             "left join store_info si\n" +
             "on si.id = ldcsf.store_user_id and si.delete_flag = 0\n" +
-
             "left join store_user su on ldcsf.store_user_id = su.store_id\n" +
-
             "left join store_img img on si.id = img.store_id and img.img_type = 1 and img.delete_flag = 0\n" +
             "${ew.customSqlSegment}")
     List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponListwzhy(@Param(Constants.WRAPPER) QueryWrapper<LifeDiscountCouponFriendRuleVo> lifeDiscountCouponFriendRuleVoQueryWrapper);
+
+    /** 我赠好友 - 代金券(type=4) */
+    @Select("select ldcsf.created_time endDate,CAST(lc.price AS DECIMAL(10,2)) nominalValue,null minimumSpendingAmount,img.img_url imgUrl,\n" +
+            "si.store_name storeName,\n" +
+            "lc.name couponName,\n" +
+            "null couponId,\n" +
+            "ldcsf.single_qty couponNum,\n" +
+            "4 as type,\n" +
+            "lc.id voucherId\n" +
+            "from life_discount_coupon_store_friend ldcsf\n" +
+            "left join life_coupon lc on lc.id = ldcsf.voucher_id and lc.delete_flag = 0\n" +
+            "left join store_info si on si.id = lc.store_id and si.delete_flag = 0\n" +
+            "left join store_user su on ldcsf.store_user_id = su.store_id\n" +
+            "left join store_img img on si.id = img.store_id and img.img_type = 1 and img.delete_flag = 0\n" +
+            "${ew.customSqlSegment}")
+    List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponListwzhyVoucher(@Param(Constants.WRAPPER) QueryWrapper<LifeDiscountCouponFriendRuleVo> lifeDiscountCouponFriendRuleVoQueryWrapper);
 }

+ 6 - 5
alien-entity/src/main/java/shop/alien/mapper/LifeUserMapper.java

@@ -35,12 +35,13 @@ public interface LifeUserMapper extends BaseMapper<LifeUser> {
     LifeFansVo getUserInfoByPhoneIdList(@Param("phoneId") String phoneId);
 
     @Select("SELECT * FROM ( " +
-            " SELECT store.id, concat( 'store_', USER.phone ) phoneId, USER.head_img imgUrl, " +
-            " IF(store.id IS NULL, USER.account_blurb, store.store_blurb) blurb, " +
-            " IF(store.id IS NULL, USER.nick_name, store.store_name) storeUserName, " +
+            " SELECT USER.id, concat( 'store_', USER.phone ) phoneId, USER.head_img imgUrl, " +
+            " store.store_blurb blurb, " +
+            " store.store_name storeUserName, " +
             " USER.nick_name accountName " +
-            " FROM store_user USER LEFT JOIN store_info store ON USER.store_id = store.id " +
-            " WHERE USER.delete_flag = 0 AND (store.id IS NULL OR store.delete_flag = 0) " +
+            " FROM store_user USER " +
+            " INNER JOIN store_info store ON USER.store_id = store.id " +
+            " WHERE USER.delete_flag = 0 AND store.delete_flag = 0 " +
             " UNION ALL SELECT id, concat( 'user_', user_phone ) phoneId, user_image imgUrl, jianjie blurb, user_name NAME, user_name accountName " +
             " FROM life_user WHERE delete_flag = 0 ) a " +
             " ${ew.customSqlSegment}")

+ 3 - 3
alien-entity/src/main/java/shop/alien/mapper/LifeUserViolationMapper.java

@@ -133,17 +133,17 @@ public interface LifeUserViolationMapper extends BaseMapper<LifeUserViolation> {
             " FROM life_user_violation luv " +
             " LEFT JOIN userInfo ui ON ui.type = luv.reporting_user_type AND ui.id = luv.reporting_user_id " +
             " left join store_img img on luv.id = img.store_id and img.delete_flag = 0 " +
-            " where luv.delete_flag = 0 and luv.report_context_type in ('1', '2', '3') " +
+            " where ifnull(luv.delete_flag, 0) = 0 and luv.report_context_type in ('0', '1', '2', '3') " +
             " union all " +
             " select " +
-            " luv.id, lu.user_name nick_name, lu.user_phone phone, luv.report_context_type, sd.dict_detail violation_type_name,  " +
+            " luv.id, lu.user_name nick_name, lu.user_phone phone, luv.report_context_type, ifnull(sd.dict_detail, '其他原因') violation_type_name,  " +
             " case when luv.report_context_type = '4' then '商品' else '用户' end report_context_name,  " +
             " luv.processing_status, case when luv.processing_status = 0 then '待处理' when luv.processing_status = 1 then '已通过' else '已驳回' end processing_name, " +
             " luv.processing_time, luv.created_time, '' image, luv.updated_time " +
             " from life_user_violation luv " +
             " left join life_user lu on luv.reporting_user_id = lu.id and lu.delete_flag = 0 " +
             " left join store_dictionary sd on luv.dict_type = sd.type_name and luv.dict_id = sd.dict_id and sd.delete_flag = 0 " +
-            " where luv.report_context_type in ('4', '5') and sd.delete_flag = 0 " +
+            " where ifnull(luv.delete_flag, 0) = 0 and luv.report_context_type in ('4', '5') " +
             " ) " +
             " select * from violationInfo " +
             " ${ew.customSqlSegment}" +

+ 36 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreContractMapper.java

@@ -0,0 +1,36 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.StoreContract;
+import shop.alien.entity.store.vo.StoreContractVo;
+
+/**
+ * 合同 Mapper 接口
+ *
+ * @author alien
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StoreContractMapper extends BaseMapper<StoreContract> {
+
+    /**
+     * 分页查询合同列表
+     *
+     * @param page 分页对象
+     * @param contractName 合同名称(模糊查询)
+     * @param status 合同状态(0:未签署, 1:已签署)
+     * @param storeId 店铺ID
+     * @return 合同列表
+     */
+    IPage<StoreContractVo> selectContractPage(
+            Page<StoreContractVo> page,
+            @Param("contractName") String contractName,
+            @Param("status") Integer status,
+            @Param("storeId") Integer storeId
+    );
+}
+

+ 13 - 2
alien-entity/src/main/java/shop/alien/mapper/StoreImgMapper.java

@@ -1,17 +1,17 @@
 package shop.alien.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.toolkit.Constants;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
 import shop.alien.entity.store.StoreImg;
-import shop.alien.entity.store.vo.StoreDynamicDiscountInviteLogVo;
 import shop.alien.entity.store.vo.StoreImgTypeVo;
 
 import java.util.List;
-import java.util.Map;
 
 /**
  * 门店图片 Mapper 接口
@@ -81,4 +81,15 @@ public interface StoreImgMapper extends BaseMapper<StoreImg> {
                                                    @Param("imgType") Integer imgType,
                                                    @Param("albumIds") List<Integer> albumIds);
 
+    @Select("SELECT * FROM store_img ${ew.customSqlSegment}")
+    StoreImg selectDeleImg(@Param(Constants.WRAPPER) LambdaQueryWrapper<StoreImg> storeImgLambdaQueryWrapper);
+
+    /**
+     * 更新删除状态
+     *
+     * @param set 更新条件
+     * @return 更新行数
+     */
+    @Update("UPDATE store_img SET delete_flag = 0 WHERE id = #{id}")
+    int updateDelete(@Param("id") String id );
 }

+ 5 - 3
alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivityAchievementMapper.java

@@ -24,11 +24,13 @@ public interface StoreOperationalActivityAchievementMapper extends BaseMapper<St
      * 分页查询案例列表(同一用户同一活动最新成果)。
      *
      * @param page           分页
+     * @param storeId        店铺ID(必填)
      * @param activityStatus 活动状态
      * @return 分页列表
      */
     IPage<StoreOperationalActivityAchievementCaseVo> selectCasePage(
             IPage<?> page,
+            @Param("storeId") Integer storeId,
             @Param("activityStatus") Integer activityStatus);
 
     /**
@@ -52,17 +54,17 @@ public interface StoreOperationalActivityAchievementMapper extends BaseMapper<St
                                                                         @Param("userId") Integer userId);
 
     /**
-     * 商家端分页查询案例列表(按商户ID、上传情况、活动名称筛选)
+     * 商家端分页查询案例列表(按商户ID、活动状态、活动名称筛选)
      *
      * @param page 分页
      * @param storeId 商户ID
-     * @param hasResult 上传情况:0-未上传, 1-已上传
+     * @param activityStatus 活动状态
      * @param activityName 活动名称(模糊查询)
      * @return 分页列表
      */
     IPage<StoreOperationalActivityAchievementCaseVo> selectStoreCasePage(
             IPage<?> page,
             @Param("storeId") Integer storeId,
-            @Param("hasResult") Integer hasResult,
+            @Param("activityStatus") Integer activityStatus,
             @Param("activityName") String activityName);
 }

+ 10 - 0
alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivitySignupMapper.java

@@ -52,6 +52,16 @@ public interface StoreOperationalActivitySignupMapper extends BaseMapper<StoreOp
                                            @Param("userId") Integer userId);
 
     /**
+     * 统计同一活动同一手机号已报名数量(未删除,待审核或通过)。
+     *
+     * @param activityId 活动ID
+     * @param phone      手机号
+     * @return 报名数量
+     */
+    Integer countByActivityIdAndPhone(@Param("activityId") Integer activityId,
+                                      @Param("phone") String phone);
+
+    /**
      * 统计用户是否已报名(待审核/通过)。
      *
      * @param activityId 活动ID

+ 17 - 0
alien-entity/src/main/resources/mapper/CommonRatingMapper.xml

@@ -38,5 +38,22 @@
         ${ew.customSqlSegment}
     </select>
 
+    <!-- 分页查询评价列表,条件由 wrapper 传入(如 business_id、business_type、audit_status=1、is_show=1 及 score、NOT EXISTS 等) -->
+    <select id="getRatingWithNoReply" resultType="Integer">
+        SELECT COUNT(*)
+        FROM common_rating
+        ${ew.customSqlSegment}
+        AND NOT EXISTS (
+        SELECT 1
+        FROM common_comment cc
+        WHERE cc.source_type = 1
+        AND cc.source_id = common_rating.id
+        AND cc.comment_type = 2
+        AND cc.is_show = 1
+        AND cc.audit_status = 1
+        AND cc.delete_flag = 0
+        );
+    </select>
+
 </mapper>
 

+ 4 - 3
alien-entity/src/main/resources/mapper/LifeDiscountCouponFriendRuleDetailMapper.xml

@@ -8,17 +8,18 @@
             <id property="id" column="id" />
             <result property="ruleId" column="rule_id" />
             <result property="couponId" column="coupon_id" />
+            <result property="voucherId" column="voucher_id" />
     </resultMap>
 
     <sql id="Base_Column_List">
-        id,rule_id,coupon_id
+        id,rule_id,coupon_id,voucher_id
     </sql>
 
     <insert id="insertList" parameterType="java.util.List">
-        INSERT INTO life_discount_coupon_friend_rule_detail (rule_id, coupon_id, friend_store_user_id,store_id)
+        INSERT INTO life_discount_coupon_friend_rule_detail (rule_id, coupon_id, voucher_id, friend_store_user_id, store_id)
         VALUES
         <foreach collection="list" item="item" separator=",">
-            (#{item.ruleId}, #{item.couponId}, #{item.friendStoreUserId}, #{item.storeId})
+            (#{item.ruleId}, #{item.couponId}, #{item.voucherId}, #{item.friendStoreUserId}, #{item.storeId})
         </foreach>
     </insert>
 

+ 24 - 4
alien-entity/src/main/resources/mapper/LifeDiscountCouponMapper.xml

@@ -27,9 +27,29 @@
         <result column="updated_user_id" property="updatedUserId" />
     </resultMap>
 
-    <!-- 通用查询结果列 -->
-    <sql id="Base_Column_List">
-        id, store_id, name, nominal_value, expiration_date, start_date, end_date, single_qty, supplementary_instruction, status, image_path, get_status, restricted_quantity, minimum_spending_amount, type, approval_comments, delete_flag, created_time, updated_time, created_user_id, updated_user_id
-    </sql>
+
+
+    <!-- 关联 life_coupon 查询代金券(仅 type=4):按 storeId、couponStatus、single_qty!=0,返回 dis 表以匹配 BaseResultMap -->
+    <select id="selectListJoinLifeCoupon" resultMap="BaseResultMap">
+        SELECT dis.*
+        FROM life_discount_coupon dis
+        JOIN life_coupon c ON dis.store_id = c.store_id
+        WHERE dis.store_id = #{storeId}
+          AND dis.type = 4
+          AND dis.coupon_status = #{couponStatus}
+          AND dis.single_qty != 0
+    </select>
+
+    <!-- 单表查询 life_discount_coupon:type 不为 4 时使用 -->
+    <select id="selectListSingleTable" resultMap="BaseResultMap">
+        SELECT *
+        FROM life_discount_coupon
+        WHERE store_id = #{storeId}
+          AND coupon_status = #{couponStatus}
+          AND single_qty != 0
+        <if test="type != null">
+          AND type = #{type}
+        </if>
+    </select>
 
 </mapper>

+ 2 - 1
alien-entity/src/main/resources/mapper/LifeDiscountCouponStoreFriendMapper.xml

@@ -7,6 +7,7 @@
         <id column="id" property="id" />
         <result column="store_user_id" property="storeUserId" />
         <result column="coupon_id" property="couponId" />
+        <result column="voucher_id" property="voucherId" />
         <result column="friend_store_user_id" property="friendStoreUserId" />
         <result column="delete_flag" property="deleteFlag" />
         <result column="created_time" property="createdTime" />
@@ -17,7 +18,7 @@
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, store_user_id, coupon_id, friend_store_user_id, delete_flag, created_time, updated_time, created_user_id, updated_user_id
+        id, store_user_id, coupon_id, voucher_id, friend_store_user_id, delete_flag, created_time, updated_time, created_user_id, updated_user_id
     </sql>
 
 </mapper>

+ 2 - 2
alien-entity/src/main/resources/mapper/LifeDiscountCouponUnavailableRulesMapper.xml

@@ -6,7 +6,7 @@
     <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeDiscountCouponUnavailableRules">
         <id column="id" property="id" />
         <result column="discount_coupon_id" property="discountCouponId" />
-        <result column="discount_coupon" property="discountCoupon" />
+        <result column="voucher_id" property="voucherId" />
         <result column="unavailable_rule_type" property="unavailableRuleType" />
         <result column="unavailable_rule_value" property="unavailableRuleValue" />
         <result column="delete_flag" property="deleteFlag" />
@@ -18,7 +18,7 @@
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, discount_coupon_id, discount_coupon, unavailable_rule_type, unavailable_rule_value, delete_flag, created_time, updated_time, created_user_id, updated_user_id
+        id, discount_coupon_id, voucher_id, unavailable_rule_type, unavailable_rule_value, delete_flag, created_time, updated_time, created_user_id, updated_user_id
     </sql>
 
 </mapper>

+ 3 - 1
alien-entity/src/main/resources/mapper/OrderReviewMapper.xml

@@ -69,6 +69,7 @@
             AND llr.type = '7' 
             AND CONVERT(llr.dianzan_id, CHAR) = CONVERT(#{currentUserId}, CHAR)
             AND llr.delete_flag = 0
+        LEFT JOIN comment_appeals ca ON ca.comment_id = orv.id AND ca.delete_flag = 0
         WHERE orv.delete_flag = 0
         <if test="orderId != null">
             AND orv.order_id = #{orderId}
@@ -80,7 +81,8 @@
             AND orv.user_id = #{userId}
         </if>
         <if test="filterAppealId != null and filterAppealId == true">
-            AND (orv.appeal_id IS NULL OR orv.appeal_id = '')
+            <!-- 只过滤掉申诉通过(status=1)的评价,申诉被驳回(status=2)或待处理(status=0)的评价应该可以查询到 -->
+            AND (ca.id IS NULL OR ca.status != 1)
         </if>
         ORDER BY orv.created_time DESC
     </select>

+ 84 - 0
alien-entity/src/main/resources/mapper/StoreContractMapper.xml

@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.StoreContractMapper">
+
+    <!-- 通用结果映射 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.StoreContract">
+        <id column="id" property="id" />
+        <result column="store_id" property="storeId" />
+        <result column="store_name" property="storeName" />
+        <result column="business_segment" property="businessSegment" />
+        <result column="merchant_name" property="merchantName" />
+        <result column="contact_phone" property="contactPhone" />
+        <result column="signing_status" property="signingStatus" />
+        <result column="contract_url" property="contractUrl" />
+        <result column="seal_url" property="sealUrl" />
+        <result column="signing_time" property="signingTime" />
+        <result column="effective_time" property="effectiveTime" />
+        <result column="expiry_time" property="expiryTime" />
+        <result column="created_time" property="createdTime" />
+        <result column="updated_time" property="updatedTime" />
+        <result column="delete_flag" property="deleteFlag" />
+    </resultMap>
+
+    <!-- VO结果映射 -->
+    <resultMap id="StoreContractVoResultMap" type="shop.alien.entity.store.vo.StoreContractVo">
+        <id column="id" property="id" />
+        <result column="store_id" property="storeId" />
+        <result column="store_name" property="storeName" />
+        <result column="business_segment" property="businessSegment" />
+        <result column="merchant_name" property="merchantName" />
+        <result column="contact_phone" property="contactPhone" />
+        <result column="signing_status" property="signingStatus" />
+        <result column="contract_url" property="contractUrl" />
+        <result column="seal_url" property="sealUrl" />
+        <result column="signing_time" property="signingTime" />
+        <result column="effective_time" property="effectiveTime" />
+        <result column="expiry_time" property="expiryTime" />
+        <result column="created_time" property="createdTime" />
+        <result column="updated_time" property="updatedTime" />
+        <result column="delete_flag" property="deleteFlag" />
+    </resultMap>
+
+    <!-- 基础字段 -->
+    <sql id="Base_Column_List">
+        id, store_id, store_name, business_segment, merchant_name, contact_phone,
+        signing_status, contract_url, seal_url, signing_time, effective_time,
+        expiry_time, created_time, updated_time, delete_flag
+    </sql>
+
+    <!-- 分页查询合同列表 -->
+    <select id="selectContractPage" resultMap="StoreContractVoResultMap">
+        SELECT
+            cm.id,
+            cm.store_id,
+            cm.store_name,
+            cm.business_segment,
+            cm.merchant_name,
+            cm.contact_phone,
+            cm.signing_status,
+            cm.contract_url,
+            cm.seal_url,
+            cm.signing_time,
+            cm.effective_time,
+            cm.expiry_time,
+            cm.created_time,
+            cm.updated_time,
+            cm.delete_flag
+        FROM store_contract cm
+        WHERE cm.delete_flag = 0
+        <if test="contractName != null and contractName != ''">
+            AND cm.store_name LIKE CONCAT('%', #{contractName}, '%')
+        </if>
+        <if test="status != null">
+            AND cm.signing_status = #{status}
+        </if>
+        <if test="storeId != null">
+            AND cm.store_id = #{storeId}
+        </if>
+        ORDER BY cm.created_time DESC
+    </select>
+
+</mapper>

+ 17 - 14
alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivityAchievementMapper.xml

@@ -12,11 +12,12 @@
             act.end_time AS endTime,
             CASE
                 WHEN act.status = 7 THEN 7
-                WHEN act.status IN (4, 5) AND act.end_time IS NOT NULL AND NOW() > act.end_time THEN 7
-                WHEN act.status IN (4, 5) AND (act.start_time IS NULL OR (NOW() >= act.start_time AND act.end_time > NOW())) THEN 5
+                WHEN act.status IN (4, 5) AND act.end_time IS NOT NULL AND DATE(NOW()) > DATE(act.end_time) THEN 7
+                WHEN act.status IN (4, 5) AND (act.start_time IS NULL OR (DATE(NOW()) >=  DATE(act.start_time) AND  DATE(act.end_time) > DATE(NOW()))) THEN 5
                 ELSE act.status
             END AS activityStatus,
             u.user_name AS nickName,
+            u.user_image AS userImage,
             su.head_img AS storeUserHeadImg,
             su.nick_name AS storeUserNickName,
             SUBSTRING_INDEX(ach.media_urls, ',', 1) AS firstMediaUrl,
@@ -44,6 +45,8 @@
             AND su.delete_flag = 0
         WHERE ach.delete_flag = 0
           AND act.delete_flag = 0
+          AND act.store_id = #{storeId}
+        AND act.status IN (4, 5, 7)
         <if test="activityStatus != null">
             <if test="activityStatus == 5">
                 AND act.status IN (4, 5)
@@ -67,7 +70,9 @@
             signup.phone AS phone,
             signup.created_time AS createdTime,
             u.user_image AS userImage,
-            u.user_phone AS userPhone
+            u.user_phone AS userPhone,
+            signup.user_name AS signupName,
+            signup.phone AS signupPhone
         FROM store_operational_activity act
         LEFT JOIN life_user u ON u.id = #{userId}
         LEFT JOIN store_operational_activity_signup signup ON signup.activity_id = #{activityId}
@@ -110,6 +115,8 @@
             u.user_name AS userName,
             u.user_image AS userImage,
             u.user_phone AS userPhone,
+            signup.user_name AS signupName,
+            signup.phone AS signupPhone,
             CASE
                 WHEN (act.result_media_url IS NOT NULL AND act.result_media_url != '')
                     OR (act.result_text IS NOT NULL AND act.result_text != '') THEN 1
@@ -141,17 +148,13 @@
         WHERE ach.delete_flag = 0
           AND act.delete_flag = 0
           AND act.store_id = #{storeId}
-        <if test="hasResult != null">
-            <choose>
-                <when test="hasResult == 0">
-                    AND ((act.result_media_url IS NULL OR act.result_media_url = '')
-                    AND (act.result_text IS NULL OR act.result_text = ''))
-                </when>
-                <when test="hasResult == 1">
-                    AND ((act.result_media_url IS NOT NULL AND act.result_media_url != '')
-                    OR (act.result_text IS NOT NULL AND act.result_text != ''))
-                </when>
-            </choose>
+        <if test="activityStatus != null">
+            <if test="activityStatus == 5">
+                AND act.status IN (4, 5)
+            </if>
+            <if test="activityStatus != 5">
+                AND act.status = #{activityStatus}
+            </if>
         </if>
         <if test="activityName != null and activityName != ''">
             AND act.activity_name LIKE CONCAT('%', #{activityName}, '%')

+ 1 - 0
alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivityMapper.xml

@@ -14,6 +14,7 @@
         <result column="activity_rule" property="activityRule" />
         <result column="reward_type" property="rewardType" />
         <result column="coupon_id" property="couponId" />
+<!--        <result column="voucher_id" property="voucherId" />-->
         <result column="coupon_quantity" property="couponQuantity" />
         <result column="status" property="status" />
         <result column="delete_flag" property="deleteFlag" />

+ 9 - 0
alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivitySignupMapper.xml

@@ -60,6 +60,15 @@
         LIMIT 1
     </select>
 
+    <select id="countByActivityIdAndPhone" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND phone = #{phone}
+          AND delete_flag = 0
+          AND status IN (0, 2)
+    </select>
+
     <select id="countSignedUpByActivityAndUser" resultType="java.lang.Integer">
         SELECT COUNT(1)
         FROM store_operational_activity_signup

+ 75 - 1
alien-job/src/main/java/shop/alien/job/store/StoreOperationalActivityJob.java

@@ -7,7 +7,9 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 import shop.alien.entity.storePlatform.StoreOperationalActivity;
+import shop.alien.entity.storePlatform.StoreOperationalActivitySignup;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivitySignupMapper;
 
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -27,6 +29,7 @@ import java.util.List;
 public class StoreOperationalActivityJob {
 
     private final StoreOperationalActivityMapper activityMapper;
+    private final StoreOperationalActivitySignupMapper signupMapper;
 
     /**
      * 营销活动状态更新任务
@@ -75,7 +78,7 @@ public class StoreOperationalActivityJob {
 
             // 遍历活动,判断状态
             for (StoreOperationalActivity activity : activities) {
-                Date startTime = activity.getSignupStartTime() != null ? activity.getSignupStartTime() : activity.getStartTime();
+                Date startTime = activity.getStartTime();
                 Date endTime = activity.getEndTime();
                 Integer currentStatus = activity.getStatus();
 
@@ -136,10 +139,81 @@ public class StoreOperationalActivityJob {
                 log.info("【定时任务】未开始活动更新完成,活动ID列表: {},更新数量: {}", notStartActivityIds, updateCount);
             }
 
+            // 处理报名时间已过但未审核通过的报名记录
+            // 使用当前实际时间(包含时分秒)进行比较
+            processExpiredSignups(new Date());
+
             log.info("【定时任务】营销活动状态更新任务执行完成");
         } catch (Exception e) {
             log.error("【定时任务】营销活动状态更新任务执行异常", e);
         }
     }
+
+    /**
+     * 处理报名时间已过但未审核通过的报名记录
+     * 如果报名人员没有在报名时间内审核通过,一律改为审核拒绝
+     *
+     * @param now 当前时间
+     */
+    private void processExpiredSignups(Date now) {
+        log.info("【定时任务】开始处理报名时间已过的待审核报名记录...");
+        
+        try {
+            // 查询所有有报名结束时间的活动
+            LambdaQueryWrapper<StoreOperationalActivity> activityWrapper = new LambdaQueryWrapper<>();
+            activityWrapper.isNotNull(StoreOperationalActivity::getSignupEndTime)
+                    .eq(StoreOperationalActivity::getDeleteFlag, 0);
+            
+            List<StoreOperationalActivity> activities = activityMapper.selectList(activityWrapper);
+            log.info("【定时任务】查询到有报名结束时间的活动数量: {}", activities.size());
+
+            int totalRejectedCount = 0;
+
+            for (StoreOperationalActivity activity : activities) {
+                Date signupEndTime = activity.getSignupEndTime();
+                if (signupEndTime == null) {
+                    continue;
+                }
+
+                // 如果当前时间 > 报名结束时间,说明报名时间已过
+                if (now.compareTo(signupEndTime) > 0) {
+                    // 查询该活动下所有待审核(status=0)的报名记录
+                    LambdaQueryWrapper<StoreOperationalActivitySignup> signupWrapper = new LambdaQueryWrapper<>();
+                    signupWrapper.eq(StoreOperationalActivitySignup::getActivityId, activity.getId())
+                            .eq(StoreOperationalActivitySignup::getStatus, 0) // 0-待审核
+                            .eq(StoreOperationalActivitySignup::getDeleteFlag, 0);
+                    
+                    List<StoreOperationalActivitySignup> pendingSignups = signupMapper.selectList(signupWrapper);
+                    
+                    if (!pendingSignups.isEmpty()) {
+                        // 批量更新为拒绝状态
+                        List<Integer> signupIds = new ArrayList<>();
+                        for (StoreOperationalActivitySignup signup : pendingSignups) {
+                            signupIds.add(signup.getId());
+                        }
+                        
+                        LambdaUpdateWrapper<StoreOperationalActivitySignup> updateWrapper = new LambdaUpdateWrapper<>();
+                        updateWrapper.in(StoreOperationalActivitySignup::getId, signupIds)
+                                .set(StoreOperationalActivitySignup::getStatus, 1) // 1-拒绝
+                                .set(StoreOperationalActivitySignup::getRejectReason, "报名时间已过,未在规定时间内审核通过")
+                                .set(StoreOperationalActivitySignup::getAuditTime, now);
+                        
+                        int updateCount = signupMapper.update(null, updateWrapper);
+                        totalRejectedCount += updateCount;
+                        log.info("【定时任务】活动ID: {} 报名时间已过,将 {} 条待审核报名记录改为拒绝", 
+                                activity.getId(), updateCount);
+                    }
+                }
+            }
+
+            if (totalRejectedCount > 0) {
+                log.info("【定时任务】报名时间已过的待审核报名记录处理完成,共拒绝 {} 条", totalRejectedCount);
+            } else {
+                log.info("【定时任务】没有需要处理的报名时间已过的待审核报名记录");
+            }
+        } catch (Exception e) {
+            log.error("【定时任务】处理报名时间已过的待审核报名记录异常", e);
+        }
+    }
 }
 

+ 76 - 1
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java

@@ -2194,7 +2194,10 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
         // 查询 comment_appeals 表,comment_id=lawyer_order_review.id
         Map<Integer, CommentAppeal> reviewIdToAppealMap = queryCommentAppealsByReviewIds(reviewIds);
 
-        // 为每个订单设置 commonStatus
+        // 直接根据订单ID查询申诉记录(包括已删除评论对应的申诉)
+        Map<Integer, CommentAppeal> orderIdToAppealMap = queryCommentAppealsByOrderIds(orderIds);
+
+        // 为每个订单设置 commonStatus 和 canCommentAgain
         for (LawyerConsultationOrderVO orderVO : voPage.getRecords()) {
             if (orderVO == null || orderVO.getId() == null) {
                 continue;
@@ -2203,6 +2206,29 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
             String commonStatus = calculateCommonStatus(
                     orderVO.getId(), orderIdToReviewMap, reviewIdToAppealMap);
             orderVO.setCommonStatus(commonStatus);
+
+            // 判断是否允许再次评论
+            // 规则:
+            // 1. 如果订单已经有评价(评价存在且未删除),不允许再次评论
+            // 2. 如果订单没有评价,但有申诉记录且申诉状态为1(已通过),说明评论被举报且举报通过(评论被删除),不允许再次评论
+            // 3. 其他情况,允许评论
+            OrderReview existingReview = orderIdToReviewMap.get(orderVO.getId());
+            CommentAppeal appeal = orderIdToAppealMap.get(orderVO.getId());
+            
+            if (existingReview != null) {
+                // 订单已经有评价(评价存在且未删除),不允许再次评论
+                orderVO.setCanCommentAgain(false);
+                log.debug("订单已有评价,不允许再次评论,orderId={}, reviewId={}", 
+                        orderVO.getId(), existingReview.getId());
+            } else if (appeal != null && appeal.getStatus() != null && appeal.getStatus() == 1) {
+                // 订单没有评价,但有申诉记录且申诉状态为1(已通过),说明评论被举报且举报通过(评论被删除),不允许再次评论
+                orderVO.setCanCommentAgain(false);
+                log.debug("订单评论被举报且举报通过,不允许再次评论,orderId={}, appealStatus={}", 
+                        orderVO.getId(), appeal.getStatus());
+            } else {
+                // 其他情况(没有评价且没有申诉记录,或申诉未通过),允许评论
+                orderVO.setCanCommentAgain(true);
+            }
         }
 
         log.debug("设置订单评价状态完成,订单数量={}", voPage.getRecords().size());
@@ -2315,6 +2341,55 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
     }
 
     /**
+     * 根据订单ID列表查询 comment_appeals 记录
+     * <p>
+     * 查询条件:order_id=订单ID
+     * 用于判断订单是否曾经有评论被申诉且申诉通过(评论被删除)
+     * </p>
+     *
+     * @param orderIds 订单ID列表
+     * @return 订单ID到申诉记录的映射,key为订单ID,value为申诉记录
+     */
+    private Map<Integer, CommentAppeal> queryCommentAppealsByOrderIds(List<Integer> orderIds) {
+        if (CollectionUtils.isEmpty(orderIds)) {
+            return Collections.emptyMap();
+        }
+
+        try {
+            // 构建查询条件
+            LambdaQueryWrapper<CommentAppeal> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(CommentAppeal::getOrderId, orderIds)
+                    .eq(CommentAppeal::getDeleteFlag, DELETE_FLAG_NOT_DELETED);
+
+            List<CommentAppeal> appealList = commentAppealMapper.selectList(queryWrapper);
+
+            // 构建映射:key为订单ID,value为申诉记录
+            // 如果一个订单有多个申诉记录,只取第一个(通常一个订单应该只有一个申诉记录)
+            Map<Integer, CommentAppeal> resultMap = new HashMap<>();
+
+            for (CommentAppeal appeal : appealList) {
+                if (appeal == null || appeal.getOrderId() == null) {
+                    continue;
+                }
+
+                // 如果已经存在该订单的申诉记录,保留第一个(先查询到的)
+                if (!resultMap.containsKey(appeal.getOrderId())) {
+                    resultMap.put(appeal.getOrderId(), appeal);
+                }
+            }
+
+            log.debug("根据订单ID查询 comment_appeals 记录成功,orderIds={},查询到记录数={}",
+                    orderIds, resultMap.size());
+
+            return resultMap;
+        } catch (Exception e) {
+            log.error("根据订单ID查询 comment_appeals 记录异常,orderIds={},异常信息:{}",
+                    orderIds, e.getMessage(), e);
+            return Collections.emptyMap();
+        }
+    }
+
+    /**
      * 计算订单的评价状态
      * <p>
      * 根据查询结果计算订单的 commonStatus 值:

+ 1 - 1
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/LifeCouponPlatformController.java

@@ -261,7 +261,7 @@ public class LifeCouponPlatformController {
         }
         DiscountCouponPlatformVo vo = new DiscountCouponPlatformVo();
         if (lifeCouponPlatformVo.getCouponType() == 1) {
-            vo.setCouponList(lifeCouponService.getCouponList(lifeCouponPlatformVo.getPageNum(), lifeCouponPlatformVo.getPageSize(), lifeCouponPlatformVo.getStoreId(),
+            vo.setCouponList(lifeCouponService.getCouponListAsDiscountVo(lifeCouponPlatformVo.getPageNum(), lifeCouponPlatformVo.getPageSize(), lifeCouponPlatformVo.getStoreId(),
                     lifeCouponPlatformVo.getStatus(), lifeCouponPlatformVo.getName()));
         } else {
             vo.setDiscountList(lifeDiscountCouponPlatformService.getStoreAllCouponList(lifeCouponPlatformVo.getStoreId(), userLoginInfo,

+ 5 - 2
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java

@@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
+import shop.alien.entity.storePlatform.vo.OperationalActivityUpdateResultVo;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityDTO;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
 import shop.alien.storeplatform.service.OperationalActivityService;
@@ -62,12 +63,14 @@ public class OperationalActivityController {
     @ApiOperation("更新运营活动")
     @ApiOperationSupport(order = 2)
     @PostMapping("/update")
-    public R<String> updateActivity(@RequestBody StoreOperationalActivityDTO dto) {
+    public R<OperationalActivityUpdateResultVo> updateActivity(@RequestBody StoreOperationalActivityDTO dto) {
         log.info("OperationalActivityController.updateActivity: dto={}", dto);
         try {
             int result = activityService.updateActivity(dto);
             if (result > 0) {
-                return R.success("活动更新成功");
+                StoreOperationalActivityVO vo = activityService.queryActivityById(dto.getId());
+                Integer couponType = (vo != null) ? vo.getCouponType() : null;
+                return R.data(new OperationalActivityUpdateResultVo("活动更新成功", couponType));
             }
             return R.fail("活动更新失败");
         } catch (Exception e) {

+ 259 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/PlatformStoreOperationalStatisticsController.java

@@ -0,0 +1,259 @@
+package shop.alien.storeplatform.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreOperationalStatisticsHistory;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsVo;
+import shop.alien.storeplatform.service.PlatformStoreOperationalStatisticsService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 商家经营数据统计Controller
+ *
+ * @author system
+ * @version 1.0
+ * @date 2026/01/05
+ */
+@Slf4j
+@Api(tags = {"商家经营数据统计"})
+@CrossOrigin
+@RestController
+@RequestMapping("/platform/operational/statistics")
+@RequiredArgsConstructor
+public class PlatformStoreOperationalStatisticsController {
+    private final PlatformStoreOperationalStatisticsService platformStoreOperationalStatisticsService;
+
+    @GetMapping("/getPlatformStatistics")
+    public R<StoreOperationalStatisticsVo> getPlatformStatistics(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("startTime") String startTime,
+            @RequestParam("endTime") String endTime) {
+        log.info("StoreOperationalStatisticsController.getStatistics - storeId={}, startTime={}, endTime={}", storeId, startTime, endTime);
+        try {
+            StoreOperationalStatisticsVo statistics = platformStoreOperationalStatisticsService.getPlatformStatisticsInTrackFormat(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("获取商家经营统计数据对比")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "storeId",
+                    value = "店铺ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "currentStartTime",
+                    value = "当期开始时间(格式:yyyy-MM-dd)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "currentEndTime",
+                    value = "当期结束时间(格式:yyyy-MM-dd)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "previousStartTime",
+                    value = "上期开始时间(格式:yyyy-MM-dd)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "previousEndTime",
+                    value = "上期结束时间(格式:yyyy-MM-dd)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true
+            )
+    })
+    @GetMapping("/getPlatformStatisticsComparison")
+    public R<StoreOperationalStatisticsComparisonVo> getPlatformStatisticsComparison(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("currentStartTime") String currentStartTime,
+            @RequestParam("currentEndTime") String currentEndTime,
+            @RequestParam("previousStartTime") String previousStartTime,
+            @RequestParam("previousEndTime") String previousEndTime) {
+        log.info("StoreOperationalStatisticsController.getStatisticsComparison - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}",
+                storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
+        StoreOperationalStatisticsComparisonVo comparison = platformStoreOperationalStatisticsService.getPlatformStatisticsComparison(
+                storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
+        return R.data(comparison);
+    }
+
+    @ApiOperation("根据ID查询历史统计记录详情")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "id",
+                    value = "历史记录ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            )
+    })
+    @GetMapping("/history/platformDetail")
+    public R<StoreOperationalStatisticsHistory> getPlatformHistoryById(@RequestParam("id") Integer id) {
+        log.info("StoreOperationalStatisticsController.getPlatformHistoryById - id={}", id);
+        try {
+            if (id == null || id <= 0) {
+                return R.fail("ID不能为空且必须大于0");
+            }
+
+            StoreOperationalStatisticsHistory history = platformStoreOperationalStatisticsService.getPlatformHistoryById(id);
+            return R.data(history);
+        } catch (Exception e) {
+            log.error("查询历史统计记录详情失败 - id={}, error={}", id, e.getMessage(), e);
+            return R.fail("查询失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("查询历史统计记录列表(分页)")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "storeId",
+                    value = "店铺ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "page",
+                    value = "页数(默认1)",
+                    dataType = "int",
+                    paramType = "query"
+            ),
+            @ApiImplicitParam(
+                    name = "size",
+                    value = "页容(默认10)",
+                    dataType = "int",
+                    paramType = "query"
+            ),
+            @ApiImplicitParam(
+                    name = "created_time",
+                    value = "查询日期(yyyy-MM-dd),可为空;传入时仅返回 query_time 为该日期的记录",
+                    dataType = "String",
+                    paramType = "query"
+            )
+    })
+    @GetMapping("/history/platformList")
+    public R<IPage<StoreOperationalStatisticsHistory>> getHistoryList(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "10") int size,
+            @RequestParam(value = "created_time", required = false) String createdTime) {
+        log.info("StoreOperationalStatisticsController.getHistoryList - storeId={}, page={}, size={}, created_time={}", storeId, page, size, createdTime);
+        try {
+            int pageNum = page > 0 ? page : 1;
+            int pageSize = size > 0 ? size : 10;
+            IPage<StoreOperationalStatisticsHistory> historyPage = platformStoreOperationalStatisticsService.getPlatformHistoryList(storeId, pageNum, pageSize, createdTime);
+            return R.data(historyPage);
+        } catch (Exception e) {
+            log.error("查询历史统计记录列表失败 - storeId={}, page={}, size={}, error={}", storeId, page, size, e.getMessage(), e);
+            return R.fail("查询失败: " + e.getMessage());
+        }
+    }
+
+
+    @ApiOperation("批量删除历史统计记录")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "ids",
+                    value = "历史记录ID列表(逗号分隔)",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true,
+                    example = "1,2,3"
+            )
+    })
+    @DeleteMapping("/history/batchPlatformDelete")
+    public R<String> batchPlatformDeleteHistory(@RequestParam("ids") String ids) {
+        log.info("StoreOperationalStatisticsController.batchDeleteHistory - ids={}", ids);
+        try {
+            if (ids == null || ids.trim().isEmpty()) {
+                return R.fail("ID列表不能为空");
+            }
+
+            // 解析ID列表
+            String[] idArray = ids.split(",");
+            List<Integer> idList = new ArrayList<>();
+            for (String idStr : idArray) {
+                try {
+                    int id = Integer.parseInt(idStr.trim());
+                    if (id > 0) {
+                        idList.add(id);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无效的ID格式: {}", idStr);
+                }
+            }
+
+            if (idList.isEmpty()) {
+                return R.fail("没有有效的ID");
+            }
+
+            boolean result = platformStoreOperationalStatisticsService.batchPlatformDeleteHistory(idList);
+            if (result) {
+                return R.success("批量删除成功");
+            } else {
+                return R.fail("批量删除失败");
+            }
+        } catch (Exception e) {
+            log.error("批量删除历史统计记录失败 - ids={}, error={}", ids, e.getMessage(), e);
+            return R.fail("批量删除失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据历史记录ID生成统计数据对比PDF报告并上传到OSS")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "storeId",
+                    value = "店铺ID",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            ),
+            @ApiImplicitParam(
+                    name = "historyId",
+                    value = "历史记录ID(从 store_operational_statistics_history 表取 statistics_data 解析为对比数据)",
+                    dataType = "Integer",
+                    paramType = "query",
+                    required = true
+            )
+    })
+    @GetMapping("/generateStatisticsComparisonPdfByHistoryId")
+    public R<String> generateStatisticsComparisonPdfByHistoryId(
+            @RequestParam("storeId") Integer storeId,
+            @RequestParam("historyId") Integer historyId) {
+        log.info("StoreOperationalStatisticsController.generateStatisticsComparisonPdfByHistoryId - storeId={}, historyId={}", storeId, historyId);
+        try {
+            String pdfUrl = platformStoreOperationalStatisticsService.generateStatisticsComparisonPdfByHistoryId(storeId, historyId);
+            return R.data(pdfUrl);
+        } catch (Exception e) {
+            log.error("根据历史记录生成统计数据对比PDF失败 - storeId={}, historyId={}, error={}", storeId, historyId, e.getMessage(), e);
+            return R.fail("生成PDF报告失败: " + e.getMessage());
+        }
+    }
+
+}

+ 128 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StoreContractController.java

@@ -0,0 +1,128 @@
+package shop.alien.storeplatform.controller;
+
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreContract;
+import shop.alien.entity.store.dto.StoreContractQueryDto;
+import shop.alien.entity.store.vo.StoreContractVo;
+import shop.alien.storeplatform.service.StoreContractService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+/**
+ * 合同管理控制器
+ *
+ * @author alien
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"商家端-合同管理"})
+@ApiSort(4)
+@CrossOrigin
+@RestController
+@RequestMapping("/contract")
+@RequiredArgsConstructor
+public class StoreContractController {
+
+    private final StoreContractService storeContractService;
+
+    @ApiOperation("分页查询合同列表")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/list")
+    public R<IPage<StoreContractVo>> getContractList(@RequestBody StoreContractQueryDto queryDto) {
+        log.info("StoreContractController.getContractList?queryDto={}", queryDto);
+        return storeContractService.getContractList(queryDto);
+    }
+
+    @ApiOperation("根据合同名称搜索合同列表(分页)")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "int", paramType = "query", required = false, defaultValue = "1"),
+            @ApiImplicitParam(name = "pageSize", value = "每页数量", dataType = "int", paramType = "query", required = false, defaultValue = "10"),
+            @ApiImplicitParam(name = "contractName", value = "合同名称(模糊查询)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "status", value = "合同状态(0:未签署, 1:已签署)", dataType = "int", paramType = "query", required = false),
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "int", paramType = "query", required = false)
+    })
+    @GetMapping("/list")
+    public R<IPage<StoreContractVo>> getContractListByQuery(
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "contractName", required = false) String contractName,
+            @RequestParam(value = "status", required = false) Integer status,
+            @RequestParam(value = "storeId", required = false) Integer storeId) {
+        log.info("StoreContractController.getContractListByQuery?pageNum={}, pageSize={}, contractName={}, status={}, storeId={}",
+                pageNum, pageSize, contractName, status, storeId);
+
+        StoreContractQueryDto queryDto = new StoreContractQueryDto();
+        queryDto.setPageNum(pageNum);
+        queryDto.setPageSize(pageSize);
+        queryDto.setContractName(contractName);
+        queryDto.setStatus(status);
+        queryDto.setStoreId(storeId);
+
+        return storeContractService.getContractList(queryDto);
+    }
+
+    @ApiOperation("查询合同详情")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "合同ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/detail")
+    public R<StoreContractVo> getContractDetail(@RequestParam("id") Integer id) {
+        log.info("StoreContractController.getContractDetail?id={}", id);
+        return storeContractService.getContractDetail(id);
+    }
+
+    @ApiOperation("新增合同")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/add")
+    public R<StoreContract> addContract(@RequestBody StoreContract contract) {
+        log.info("StoreContractController.addContract?contract={}", contract);
+        return storeContractService.addContract(contract);
+    }
+
+    @ApiOperation("更新合同")
+    @ApiOperationSupport(order = 5)
+    @PostMapping("/update")
+    public R<StoreContract> updateContract(@RequestBody StoreContract contract) {
+        log.info("StoreContractController.updateContract?contract={}", contract);
+        return storeContractService.updateContract(contract);
+    }
+
+    @ApiOperation("签署合同")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "合同ID", dataType = "long", paramType = "query", required = true)
+    })
+    @PostMapping("/sign")
+    public R<Boolean> signContract(@RequestParam("id") Long id) {
+        log.info("StoreContractController.signContract?id={}", id);
+        return storeContractService.signContract(id);
+    }
+
+    @ApiOperation("删除合同")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "合同ID", dataType = "int", paramType = "query", required = true)
+    })
+    @PostMapping("/delete")
+    public R<Boolean> deleteContract(@RequestParam("id") Integer id) {
+        log.info("StoreContractController.deleteContract?id={}", id);
+        return storeContractService.deleteContract(id);
+    }
+
+    @ApiOperation("获取合同签署状态")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "store_id", value = "店铺ID", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/signingStatus")
+    public R<String> getSigningStatus(@RequestParam("store_id") Integer storeId) {
+        log.info("StoreContractController.getSigningStatus?storeId={}", storeId);
+        return storeContractService.getSigningStatus(storeId);
+    }
+}
+

+ 6 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/LifeCouponPlatformService.java

@@ -6,6 +6,7 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.EssentialHolidayComparison;
 import shop.alien.entity.store.LifeCoupon;
 import shop.alien.entity.store.vo.LifeCouponStatusVo;
+import shop.alien.entity.store.vo.LifeDiscountCouponVo;
 import shop.alien.entity.storePlatform.vo.LifeCouponPlatformDto;
 
 import java.util.Map;
@@ -70,6 +71,11 @@ public interface LifeCouponPlatformService extends IService<LifeCoupon> {
     IPage<LifeCouponPlatformDto> getCouponList(int page, int size, String storeId, String status, String name);
 
     /**
+     * 代金券列表,返回与优惠券一致的数据结构(LifeDiscountCouponVo,type=4),供 getCouponList 接口 couponType=1 使用
+     */
+    IPage<LifeDiscountCouponVo> getCouponListAsDiscountVo(int page, int size, String storeId, String status, String name);
+
+    /**
      * 更新优惠券状态
      * 修改优惠券的状态,如上架、下架等
      *

+ 79 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/PlatformStoreOperationalStatisticsService.java

@@ -0,0 +1,79 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.store.StoreOperationalStatisticsHistory;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsVo;
+
+import java.util.List;
+
+/**
+ * 商家经营数据统计服务接口
+ *
+ * @author system
+ * @version 1.0
+ * @date 2026/01/05
+ */
+public interface PlatformStoreOperationalStatisticsService {
+
+
+    /**
+     * 获取商家经营统计数据(符合埋点统计数据JSON格式)
+     *
+     * @param storeId   店铺ID
+     * @param startTime 开始时间(格式:yyyy-MM-dd)
+     * @param endTime   结束时间(格式:yyyy-MM-dd)
+     * @return 经营统计数据
+     */
+    StoreOperationalStatisticsVo getPlatformStatisticsInTrackFormat(Integer storeId, String startTime, String endTime);
+
+    /**
+     * 获取商家经营统计数据对比
+     *
+     * @param storeId           店铺ID
+     * @param currentStartTime  当期开始时间(格式:yyyy-MM-dd)
+     * @param currentEndTime    当期结束时间(格式:yyyy-MM-dd)
+     * @param previousStartTime 上期开始时间(格式:yyyy-MM-dd)
+     * @param previousEndTime   上期结束时间(格式:yyyy-MM-dd)
+     * @return 经营统计数据对比
+     */
+    StoreOperationalStatisticsComparisonVo getPlatformStatisticsComparison(Integer storeId, String currentStartTime, String currentEndTime,
+                                                                   String previousStartTime, String previousEndTime);
+
+
+    /**
+     * 根据ID查询历史统计记录详情
+     *
+     * @param id 历史记录ID
+     * @return 历史统计记录详情
+     */
+    StoreOperationalStatisticsHistory getPlatformHistoryById(Integer id);
+
+    /**
+     * 查询历史统计记录列表(分页)
+     *
+     * @param storeId    店铺ID
+     * @param page       页码
+     * @param size       每页大小
+     * @param createdTime 查询日期(yyyy-MM-dd),可为空;传入时仅返回 query_time 为该日期的记录
+     * @return 历史统计记录分页列表
+     */
+    IPage<StoreOperationalStatisticsHistory> getPlatformHistoryList(Integer storeId, Integer page, Integer size, String createdTime);
+
+    /**
+     * 批量删除历史统计记录(逻辑删除)
+     *
+     * @param ids 历史记录ID列表
+     * @return 是否成功
+     */
+    boolean batchPlatformDeleteHistory(List<Integer> ids);
+
+    /**
+     * 根据历史记录ID生成统计数据对比PDF报告并上传到OSS(对比数据从 history 的 statistics_data 解析)
+     *
+     * @param storeId   店铺ID
+     * @param historyId 历史记录ID
+     * @return PDF的OSS URL
+     */
+    String generateStatisticsComparisonPdfByHistoryId(Integer storeId, Integer historyId);
+}

+ 73 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StoreContractService.java

@@ -0,0 +1,73 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreContract;
+import shop.alien.entity.store.dto.StoreContractQueryDto;
+import shop.alien.entity.store.vo.StoreContractVo;
+
+/**
+ * 合同服务接口
+ *
+ * @author alien
+ * @since 2025-01-XX
+ */
+public interface StoreContractService {
+
+    /**
+     * 分页查询合同列表
+     *
+     * @param queryDto 查询条件
+     * @return 分页结果
+     */
+    R<IPage<StoreContractVo>> getContractList(StoreContractQueryDto queryDto);
+
+    /**
+     * 根据ID查询合同详情
+     *
+     * @param id 合同ID
+     * @return 合同详情
+     */
+    R<StoreContractVo> getContractDetail(Integer id);
+
+    /**
+     * 新增合同
+     *
+     * @param contract 合同信息
+     * @return 操作结果
+     */
+    R<StoreContract> addContract(StoreContract contract);
+
+    /**
+     * 更新合同
+     *
+     * @param contract 合同信息
+     * @return 操作结果
+     */
+    R<StoreContract> updateContract(StoreContract contract);
+
+    /**
+     * 签署合同
+     *
+     * @param id 合同ID
+     * @return 操作结果
+     */
+    R<Boolean> signContract(Long id);
+
+    /**
+     * 删除合同(逻辑删除)
+     *
+     * @param id 合同ID
+     * @return 操作结果
+     */
+    R<Boolean> deleteContract(Integer id);
+
+    /**
+     * 获取合同的签署状态
+     *
+     * @param storeId 店铺ID
+     * @return 签署状态 (0:未签署, 1:已签署, 2:已到期)
+     */
+    R<String> getSigningStatus(Integer storeId);
+}
+

+ 108 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/LifeCouponPlatformServiceImpl.java

@@ -12,11 +12,13 @@ import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.*;
+import shop.alien.entity.store.vo.LifeDiscountCouponVo;
 import shop.alien.entity.storePlatform.vo.LifeCouponPlatformDto;
 import shop.alien.mapper.*;
 import shop.alien.storeplatform.service.LifeCouponPlatformService;
 import shop.alien.util.common.UniqueRandomNumGenerator;
 
+import java.math.BigDecimal;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.time.format.TextStyle;
@@ -270,6 +272,112 @@ public class LifeCouponPlatformServiceImpl extends ServiceImpl<LifeCouponMapper,
         return dto;
     }
 
+    @Override
+    public IPage<LifeDiscountCouponVo> getCouponListAsDiscountVo(int page, int size, String storeId, String status, String name) {
+        LambdaQueryWrapper<LifeCoupon> wrapper = new LambdaQueryWrapper<>();
+        if (status != null && !status.isEmpty() && status.equals("0")) {
+            wrapper.eq(LifeCoupon::getDataType, 1);
+        } else {
+            wrapper.eq(status != null && !status.isEmpty(), LifeCoupon::getStatus, status);
+        }
+        wrapper.eq(storeId != null && !storeId.isEmpty(), LifeCoupon::getStoreId, storeId);
+        wrapper.like(name != null && !name.isEmpty(), LifeCoupon::getName, name);
+        wrapper.eq(LifeCoupon::getType, 1);
+        wrapper.last("ORDER BY CASE " +
+                "WHEN status = 3 THEN 0 " +
+                "WHEN status = 1 THEN 1 " +
+                "WHEN status = 5 THEN 2 " +
+                "WHEN status IN (4, 6, 7) THEN 3 " +
+                "ELSE 4 END ASC, created_time DESC");
+
+        IPage<LifeCoupon> lifeCouponIPage = lifeCouponMapper.selectPage(new Page<>(page, size), wrapper);
+        IPage<LifeDiscountCouponVo> result = new Page<>(page, size);
+        result.setPages(lifeCouponIPage.getPages());
+        result.setCurrent(lifeCouponIPage.getCurrent());
+        result.setSize(lifeCouponIPage.getSize());
+        result.setTotal(lifeCouponIPage.getTotal());
+
+        List<LifeDiscountCouponVo> voList = lifeCouponIPage.getRecords().stream().map(this::mapLifeCouponToDiscountVo).collect(Collectors.toList());
+        result.setRecords(voList);
+        return result;
+    }
+
+    /** 代金券转成与优惠券一致的结构(LifeDiscountCouponVo),type=4 */
+    private LifeDiscountCouponVo mapLifeCouponToDiscountVo(LifeCoupon lc) {
+        LifeDiscountCouponVo vo = new LifeDiscountCouponVo();
+        // 代金券 id 统一从 life_coupon.id 返回:voucherId 必填(字符串),id/couponId 在为数字时一并返回便于前端与优惠券格式一致
+        vo.setVoucherId(lc.getId());
+        if (lc.getId() != null) {
+            try {
+                int numericId = Integer.parseInt(lc.getId());
+                vo.setId(numericId);
+                vo.setCouponId(numericId);
+            } catch (NumberFormatException ignored) { }
+        }
+        vo.setStoreId(lc.getStoreId());
+        vo.setName(lc.getName());
+        vo.setType(lc.getType());
+        vo.setSingleQty(lc.getSingleQty());
+        if (lc.getPrice() != null) {
+            try {
+                vo.setNominalValue(new BigDecimal(lc.getPrice()));
+            } catch (Exception ignored) { }
+        }
+        if (lc.getStartDate() != null) {
+            vo.setStartDate(lc.getStartDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+        }
+        if (lc.getEndDate() != null) {
+            vo.setEndDate(lc.getEndDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+            vo.setValidDate(lc.getEndDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+        }
+        vo.setBeginGetDate(vo.getStartDate());
+        vo.setEndGetDate(vo.getEndDate());
+        vo.setCreatedTime(lc.getCreatedTime());
+        vo.setCouponStatus(lc.getDataType() != null && lc.getDataType() == 0 ? 1 : 0);
+        vo.setQuantityClaimed(0);
+        if (lc.getStatus() != null) {
+            switch (lc.getStatus()) {
+                case 0:
+                    vo.setStatus(5);
+                    vo.setStatusDesc("草稿");
+                    break;
+                case 1:
+                    vo.setStatus(2);
+                    vo.setStatusDesc("待审核");
+                    break;
+                case 2:
+                    vo.setStatus(2);
+                    vo.setStatusDesc("未开始");
+                    break;
+                case 3:
+                    vo.setStatus(3);
+                    vo.setStatusDesc("审核拒绝");
+                    break;
+                case 4:
+                    vo.setStatus(4);
+                    vo.setStatusDesc("已售罄");
+                    break;
+                case 5:
+                    vo.setStatus(0);
+                    vo.setStatusDesc("进行中");
+                    break;
+                case 6:
+                    vo.setStatus(3);
+                    vo.setStatusDesc("已下架");
+                    break;
+                case 7:
+                    vo.setStatus(1);
+                    vo.setStatusDesc("已结束");
+                    break;
+                default:
+                    vo.setStatus(null);
+                    vo.setStatusDesc("未知状态");
+                    break;
+            }
+        }
+        return vo;
+    }
+
     /**
      * 更新优惠券状态
      * 修改优惠券的状态,支持上架、下架等操作

+ 8 - 4
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/LifeDiscountCouponPlatformServiceImpl.java

@@ -491,25 +491,29 @@ public class LifeDiscountCouponPlatformServiceImpl extends ServiceImpl<LifeDisco
                 int startResult = localNow.compareTo(lifeDiscountCoupon.getBeginGetDate());
                 int endResult = localNow.compareTo(lifeDiscountCoupon.getEndGetDate());
                 if (lifeDiscountCoupon.getCouponStatus() == 0) {
-                    //如果为草稿则状态为未开始
                     lifeDiscountCouponVo.setStatus(Integer.parseInt(DiscountCouponEnum.DRAFT.getValue()));
-                } else if (lifeDiscountCoupon.getSingleQty() == null || lifeDiscountCoupon.getSingleQty() == 0) {//无库存则已售罄 //如果当前时间小于开始时间
+                    lifeDiscountCouponVo.setStatusDesc("草稿");
+                } else if (lifeDiscountCoupon.getSingleQty() == null || lifeDiscountCoupon.getSingleQty() == 0) {
                     lifeDiscountCouponVo.setStatus(Integer.parseInt(DiscountCouponEnum.HAVE_SOLD_OUT.getValue()));
+                    lifeDiscountCouponVo.setStatusDesc("已售罄");
                 } else if (startResult < 0) {
                     lifeDiscountCouponVo.setStatus(Integer.parseInt(DiscountCouponEnum.HAVE_NOT_STARTED.getValue()));
+                    lifeDiscountCouponVo.setStatusDesc("未开始");
                 } else if (lifeDiscountCoupon.getGetStatus() == null || lifeDiscountCoupon.getGetStatus().toString().equals(DiscountCouponEnum.NO_GET.getValue())) {
                     lifeDiscountCouponVo.setStatus(Integer.parseInt(DiscountCouponEnum.SUSPEND_GET.getValue()));
+                    lifeDiscountCouponVo.setStatusDesc("已暂停");
                 } else if (endResult > 0) {
                     lifeDiscountCouponVo.setStatus(Integer.parseInt(DiscountCouponEnum.FINISHED.getValue()));
+                    lifeDiscountCouponVo.setStatusDesc("已结束");
                 } else if (startResult >= 0 && endResult <= 0) {
                     lifeDiscountCouponVo.setStatus(Integer.parseInt(DiscountCouponEnum.UNDER_WAY.getValue()));
+                    lifeDiscountCouponVo.setStatusDesc("进行中");
                 }
             } else {
                 if (lifeDiscountCoupon.getCouponStatus() == 0) {
-                    //如果为草稿则状态为未开始
                     lifeDiscountCouponVo.setStatus(Integer.parseInt(DiscountCouponEnum.DRAFT.getValue()));
+                    lifeDiscountCouponVo.setStatusDesc("草稿");
                 } else {
-                    // 开始结束时间为空,数据异常跳过
                     continue;
                 }
             }

+ 136 - 19
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -16,17 +16,27 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
+import com.alibaba.fastjson.JSONObject;
 import shop.alien.entity.store.LifeDiscountCoupon;
+import shop.alien.entity.store.LifeNotice;
 import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.entity.storePlatform.StoreOperationalActivity;
+import shop.alien.entity.storePlatform.StoreOperationalActivitySignup;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityDTO;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
 import shop.alien.mapper.LifeDiscountCouponMapper;
+import shop.alien.mapper.LifeNoticeMapper;
 import shop.alien.mapper.StoreImgMapper;
+import shop.alien.mapper.StoreUserMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivitySignupMapper;
 import shop.alien.storeplatform.feign.AlienAIFeign;
+import shop.alien.storeplatform.feign.AlienStoreFeign;
 import shop.alien.storeplatform.service.OperationalActivityService;
 import shop.alien.storeplatform.util.AiContentModerationUtil;
+import shop.alien.util.common.Constants;
 
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -61,8 +71,16 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
     private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
 
+    private final StoreOperationalActivitySignupMapper signupMapper;
+
+    private final StoreUserMapper storeUserMapper;
+
+    private final LifeNoticeMapper lifeNoticeMapper;
+
     private final AlienAIFeign alienAIFeign;
 
+    private final AlienStoreFeign alienStoreFeign;
+
     private final RedissonClient redissonClient;
 
     private final AiContentModerationUtil aiContentModerationUtil;
@@ -139,11 +157,12 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                     
                     // 审核结束后,设置审核时间
                     Date auditTime = new Date();
+                    activity.setAuditStatus(auditResult.isPassed() ? 1 : 2);
                     activity.setAuditTime(auditTime);
                     
                     // 审核通过,根据活动时间自动设置状态
                     Date currentTime = new Date();
-                    Date startTime = activity.getSignupStartTime() != null ? activity.getSignupStartTime() : activity.getStartTime();
+                    Date startTime = activity.getStartTime();
                     Date endTime = activity.getEndTime();
                     
                     int status;
@@ -169,6 +188,15 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                     }
 
                     result = activityMapper.insert(activity);
+                    
+                    // AI审核后向商户发送通知
+                    if (result > 0) {
+                        try {
+                            sendActivityAuditNotice(activity, auditResult.isPassed(), auditResult.getFailureReason());
+                        } catch (Exception e) {
+                            log.error("发送活动审核通知失败,activityId={}, error={}", activity.getId(), e.getMessage(), e);
+                        }
+                    }
                     // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
                     if (dto.getUploadImgType() == 2) {
                         // 格式化输入AI参数
@@ -191,16 +219,16 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                             // 提取横向图(banner_image)的图片URL
                             if (data.has("banner_image")) {
                                 JsonNode bannerImage = data.get("banner_image");
-                                if (bannerImage.has("image_url") && !bannerImage.get("image_url").isNull()) {
-                                    String bannerImageUrl = bannerImage.get("image_url").asText();
+                                if (bannerImage.has("ali_url") && !bannerImage.get("ali_url").isNull()) {
+                                    String bannerImageUrl = bannerImage.get("ali_url").asText();
                                     dto.getActivityTitleImg().setImgUrl(bannerImageUrl);
                                 }
                             }
                             // 提取竖向图(vertical_image)的图片URL
                             if (data.has("vertical_image")) {
                                 JsonNode verticalImage = data.get("vertical_image");
-                                if (verticalImage.has("image_url") && !verticalImage.get("image_url").isNull()) {
-                                    String verticalImageUrl = verticalImage.get("image_url").asText();
+                                if (verticalImage.has("ali_url") && !verticalImage.get("ali_url").isNull()) {
+                                    String verticalImageUrl = verticalImage.get("ali_url").asText();
                                     dto.getActivityDetailImg().setImgUrl(verticalImageUrl);
                                 }
                             }
@@ -285,7 +313,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                 }
 
                 String auditText = (auditParam != null && auditParam.has("text")) ? auditParam.get("text").asText() : "";
-                JsonNode imagesNode = (auditParam != null) ? auditParam.get("image_urls") : null;
+                JsonNode imagesNode = (auditParam != null) ? auditParam.get("ali_urls") : null;
 
                 List<String> imageUrls = (imagesNode != null && imagesNode.isArray())
                         ? StreamSupport.stream(imagesNode.spliterator(), false)
@@ -298,6 +326,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
                 // 审核结束后,设置审核时间
                 Date auditTime = new Date();
+                activity.setAuditStatus(auditResult.isPassed() ? 1 : 2);
                 activity.setAuditTime(auditTime);
 
                 // 审核通过,根据活动时间自动设置状态
@@ -328,6 +357,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                 }
 
                 result = activityMapper.updateById(activity);
+                sendActivityAuditNotice(activity, auditResult.isPassed(), auditResult.getFailureReason());
                 // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
                 if (dto.getUploadImgType() == 2) {
                     // 格式化输入AI参数
@@ -350,16 +380,16 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                         // 提取横向图(banner_image)的图片URL
                         if (data.has("banner_image")) {
                             JsonNode bannerImage = data.get("banner_image");
-                            if (bannerImage.has("image_url") && !bannerImage.get("image_url").isNull()) {
-                                String bannerImageUrl = bannerImage.get("image_url").asText();
+                            if (bannerImage.has("ali_url") && !bannerImage.get("ali_url").isNull()) {
+                                String bannerImageUrl = bannerImage.get("ali_url").asText();
                                 dto.getActivityTitleImg().setImgUrl(bannerImageUrl);
                             }
                         }
                         // 提取竖向图(vertical_image)的图片URL
                         if (data.has("vertical_image")) {
                             JsonNode verticalImage = data.get("vertical_image");
-                            if (verticalImage.has("image_url") && !verticalImage.get("image_url").isNull()) {
-                                String verticalImageUrl = verticalImage.get("image_url").asText();
+                            if (verticalImage.has("ali_url") && !verticalImage.get("ali_url").isNull()) {
+                                String verticalImageUrl = verticalImage.get("ali_url").asText();
                                 dto.getActivityDetailImg().setImgUrl(verticalImageUrl);
                             }
                         }
@@ -411,7 +441,15 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
             throw new IllegalArgumentException("活动ID不能为空");
         }
 
-        // 逻辑删除
+        // 先删除相关的报名人员(逻辑删除)
+        LambdaUpdateWrapper<StoreOperationalActivitySignup> signupWrapper = new LambdaUpdateWrapper<>();
+        signupWrapper.eq(StoreOperationalActivitySignup::getActivityId, id)
+                .eq(StoreOperationalActivitySignup::getDeleteFlag, 0)
+                .set(StoreOperationalActivitySignup::getDeleteFlag, 1);
+        int signupDeleteCount = signupMapper.update(null, signupWrapper);
+        log.info("删除活动相关报名人员数量: {}", signupDeleteCount);
+
+        // 逻辑删除活动
         return activityMapper.deleteById(id);
     }
 
@@ -448,14 +486,15 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
         } else if (activity.getStatus() == 7) {
             vo.setStatusName("已结束");
         }
-
-        // 设置优惠券名称(判空处理)
-        if (activity.getCouponId() != null) {
-            LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
-            if (coupon != null) {
-                vo.setCouponName(coupon.getName());
-            }
-        }
+//
+//        // 设置优惠券名称与优惠券类型(判空处理)
+//        if (activity.getCouponId() != null) {
+//            LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
+//            if (coupon != null) {
+//                vo.setCouponName(coupon.getName());
+//                vo.setCouponType(coupon.getType());
+//            }
+//        }
 
         StoreImg activityTitleImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
                 .eq(StoreImg::getStoreId, vo.getStoreId())
@@ -600,5 +639,83 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
         return activityMapper.updateById(activity);
     }
+
+    /**
+     * 发送活动审核通知给商户
+     *
+     * @param activity 活动信息
+     * @param approved 是否通过:true-通过,false-拒绝
+     * @param rejectReason 拒绝原因(审核不通过时)
+     */
+    private void sendActivityAuditNotice(StoreOperationalActivity activity, boolean approved, String rejectReason) {
+        log.info("OperationalActivityServiceImpl.sendActivityAuditNotice: activityId={}, approved={}", 
+                activity.getId(), approved);
+
+        if (activity.getStoreId() == null) {
+            log.warn("活动商户ID为空,无法发送通知,activityId={}", activity.getId());
+            return;
+        }
+
+        // 查询商户用户信息(获取手机号)
+        LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
+        userWrapper.eq(StoreUser::getStoreId, activity.getStoreId())
+                .eq(StoreUser::getDeleteFlag, 0)
+                .last("LIMIT 1");
+        StoreUser storeUser = storeUserMapper.selectOne(userWrapper);
+        if (storeUser == null || storeUser.getPhone() == null) {
+            log.warn("商户用户信息不存在或手机号为空,无法发送通知,storeId={}", activity.getStoreId());
+            return;
+        }
+
+        String phone = storeUser.getPhone();
+        String receiverId = "store_" + phone;
+
+        // 构建通知内容
+        String message;
+        if (approved) {
+            // 审核成功
+            message = "您创建的活动已审核通过,快去查看吧";
+        } else {
+            // 审核拒绝
+            message = "您创建的活动经审核未通过,拒绝原因:" + (rejectReason != null ? rejectReason : "") + ",请重新提交审核";
+        }
+
+        // 创建通知对象
+        JSONObject contextJson = new JSONObject();
+        contextJson.put("activityId", activity.getId());
+        contextJson.put("activityName", activity.getActivityName());
+        contextJson.put("status", approved ? "通过" : "拒绝");
+        contextJson.put("message", message);
+
+        LifeNotice lifeNotice = new LifeNotice();
+        lifeNotice.setSenderId("system");
+        lifeNotice.setReceiverId(receiverId);
+        lifeNotice.setBusinessId(activity.getId());
+        lifeNotice.setTitle("活动审核通知");
+        lifeNotice.setContext(contextJson.toJSONString());
+        lifeNotice.setNoticeType(Constants.Notice.SYSTEM_NOTICE); // 系统通知
+        lifeNotice.setIsRead(0);
+
+        // 保存通知到数据库
+        lifeNoticeMapper.insert(lifeNotice);
+
+        // 通过WebSocket发送实时通知
+        try {
+            WebSocketVo webSocketVo = new WebSocketVo();
+            webSocketVo.setSenderId("system");
+            webSocketVo.setReceiverId(receiverId);
+            webSocketVo.setCategory("notice");
+            webSocketVo.setNoticeType("1");
+            webSocketVo.setIsRead(0);
+            webSocketVo.setText(JSONObject.toJSONString(lifeNotice));
+
+            alienStoreFeign.sendMsgToClientByPhoneId(receiverId, JSONObject.toJSONString(webSocketVo));
+            log.info("活动审核通知发送成功,activityId={}, receiverId={}, approved={}", 
+                    activity.getId(), receiverId, approved);
+        } catch (Exception e) {
+            log.error("发送WebSocket通知失败,activityId={}, receiverId={}, error={}", 
+                    activity.getId(), receiverId, e.getMessage(), e);
+        }
+    }
 }
 

+ 9 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivitySignupServiceImpl.java

@@ -93,6 +93,10 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
             StoreOperationalActivitySignupVO vo = new StoreOperationalActivitySignupVO();
             BeanUtils.copyProperties(signup, vo);
 
+            // 设置报名人姓名和手机号(signupname 和 signupphone)
+            vo.setSignupName(signup.getUserName());
+            vo.setSignupPhone(signup.getPhone());
+
             // 查询活动名称和活动类型
             LambdaQueryWrapper<StoreOperationalActivity> activityWrapper = new LambdaQueryWrapper<>();
             activityWrapper.eq(StoreOperationalActivity::getId, signup.getActivityId())
@@ -140,6 +144,10 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
         StoreOperationalActivitySignupVO vo = new StoreOperationalActivitySignupVO();
         BeanUtils.copyProperties(signup, vo);
 
+        // 设置报名人姓名和手机号(signupname 和 signupphone)
+        vo.setSignupName(signup.getUserName());
+        vo.setSignupPhone(signup.getPhone());
+
         // 查询活动名称和活动类型
         LambdaQueryWrapper<StoreOperationalActivity> activityWrapper = new LambdaQueryWrapper<>();
         activityWrapper.eq(StoreOperationalActivity::getId, signup.getActivityId())
@@ -148,6 +156,7 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
         if (activity != null) {
             vo.setActivityName(activity.getActivityName());
             vo.setActivityType(activity.getActivityType());
+            vo.setAuditTime(activity.getAuditTime());
         }
 
         // 查询用户昵称

+ 1488 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/PlatformStoreOperationalStatisticsServiceImpl.java

@@ -0,0 +1,1488 @@
+package shop.alien.storeplatform.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+import shop.alien.entity.store.*;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsVo;
+import shop.alien.mapper.*;
+import shop.alien.storeplatform.service.PlatformStoreOperationalStatisticsService;
+import shop.alien.storeplatform.util.PlatformStatisticsComparisonImageUtil;
+import shop.alien.storeplatform.util.AiAuthTokenUtil;
+import shop.alien.util.ali.AliOSSUtil;
+import shop.alien.util.common.RandomCreateUtil;
+import shop.alien.util.pdf.ImageToPdfUtil;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * 商家经营数据统计服务实现类
+ *
+ * @author system
+ * @version 1.0
+ * @date 2026/01/05
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@RefreshScope
+public class PlatformStoreOperationalStatisticsServiceImpl implements PlatformStoreOperationalStatisticsService {
+
+    private final LifeBrowseRecordMapper lifeBrowseRecordMapper;
+    private final LifeCollectMapper lifeCollectMapper;
+    private final StoreClockInMapper storeClockInMapper;
+    private final LifeUserDynamicsMapper lifeUserDynamicsMapper;
+    private final StoreCommentMapper storeCommentMapper;
+    private final LifeCommentMapper lifeCommentMapper;
+    private final LifeLikeRecordMapper lifeLikeRecordMapper;
+    private final LifeFansMapper lifeFansMapper;
+    private final LifeBlacklistMapper lifeBlacklistMapper;
+    private final LifeDiscountCouponStoreFriendMapper lifeDiscountCouponStoreFriendMapper;
+    private final LifeDiscountCouponUserMapper lifeDiscountCouponUserMapper;
+    private final LifeCouponMapper lifeCouponMapper;
+    private final CommonRatingMapper commonRatingMapper;
+    private final StoreEvaluationMapper storeEvaluationMapper;
+    private final StoreCommentAppealMapper storeCommentAppealMapper;
+    private final StorePriceMapper storePriceMapper;
+    private final StoreCuisineMapper storeCuisineMapper;
+    private final StoreInfoMapper storeInfoMapper;
+    private final StoreUserMapper storeUserMapper;
+    private final StoreOperationalStatisticsHistoryMapper statisticsHistoryMapper;
+    private final StoreTrackStatisticsMapper storeTrackStatisticsMapper;
+    private final AliOSSUtil aliOSSUtil;
+    private final RestTemplate restTemplate;
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+
+
+    @Value("${third-party-ai-store-summary-suggestion.base-url:}")
+    private String aiStatisticsAnalysisUrl;
+
+    private static final String DATE_FORMAT = "yyyy-MM-dd";
+    private static final String STAT_TYPE_DAILY = "DAILY";
+
+
+    @Override
+    public StoreOperationalStatisticsVo getPlatformStatisticsInTrackFormat(Integer storeId, String startTime, String endTime) {
+        log.info("StoreOperationalStatisticsServiceImpl.getStatisticsInTrackFormat - storeId={}, startTime={}, endTime={}", 
+                storeId, startTime, endTime);
+        
+        // 计算统计数据
+        StoreOperationalStatisticsVo statistics = calculateStatistics(storeId, startTime, endTime);
+        
+        // 不再保存统计数据到历史表,只有对比接口才会保存历史数据
+        
+        return statistics;
+    }
+
+    @Override
+    public StoreOperationalStatisticsComparisonVo getPlatformStatisticsComparison(Integer storeId, String currentStartTime, String currentEndTime,
+                                                                          String previousStartTime, String previousEndTime) {
+        log.info("StoreOperationalStatisticsServiceImpl.getStatisticsComparison - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}",
+                storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
+        }
+        if (currentStartTime == null || currentStartTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("当期开始时间不能为空");
+        }
+        if (currentEndTime == null || currentEndTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("当期结束时间不能为空");
+        }
+        if (previousStartTime == null || previousStartTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("上期开始时间不能为空");
+        }
+        if (previousEndTime == null || previousEndTime.trim().isEmpty()) {
+            throw new IllegalArgumentException("上期结束时间不能为空");
+        }
+
+        StoreOperationalStatisticsComparisonVo comparison = new StoreOperationalStatisticsComparisonVo();
+        comparison.setCurrentStartTime(currentStartTime);
+        comparison.setCurrentEndTime(currentEndTime);
+        comparison.setPreviousStartTime(previousStartTime);
+        comparison.setPreviousEndTime(previousEndTime);
+
+        // 获取当期和上期的统计数据
+        StoreOperationalStatisticsVo currentStatistics = calculateStatistics(storeId, currentStartTime, currentEndTime);
+        StoreOperationalStatisticsVo previousStatistics = calculateStatistics(storeId, previousStartTime, previousEndTime);
+
+        // 构建对比数据
+        comparison.setTrafficData(buildTrafficDataComparison(currentStatistics.getTrafficData(), previousStatistics.getTrafficData()));
+        comparison.setInteractionData(buildInteractionDataComparison(currentStatistics.getInteractionData(), previousStatistics.getInteractionData()));
+        comparison.setCouponData(buildCouponDataComparison(currentStatistics.getCouponData(), previousStatistics.getCouponData()));
+        comparison.setVoucherData(buildVoucherDataComparison(currentStatistics.getVoucherData(), previousStatistics.getVoucherData()));
+        comparison.setServiceQualityData(buildServiceQualityDataComparison(currentStatistics.getServiceQualityData(), previousStatistics.getServiceQualityData()));
+        comparison.setPriceListRanking(buildPriceListRankingComparison(currentStatistics.getPriceListRanking(), previousStatistics.getPriceListRanking(), storeId));
+
+        // 保存历史记录(不包含PDF URL,PDF URL只在 generateStatisticsComparisonPdf 接口中保存)
+        Integer historyId = saveStatisticsHistory(storeId, currentStartTime, currentEndTime,
+                previousStartTime, previousEndTime, comparison, null);
+
+        // 将历史记录ID设置到返回对象中
+        comparison.setHistoryId(historyId);
+
+        // 异步调用AI接口进行数据分析
+        if (historyId != null) {
+            try {
+                callAiAnalysisAsync(historyId, storeId, comparison);
+            } catch (Exception e) {
+                log.error("调用AI分析接口失败 - historyId={}, error={}", historyId, e.getMessage(), e);
+                // AI分析失败不影响主流程,只记录日志
+            }
+        }
+
+        return comparison;
+    }
+
+    @Override
+    public StoreOperationalStatisticsHistory getPlatformHistoryById(Integer id) {
+        log.info("StoreOperationalStatisticsServiceImpl.getHistoryById - id={}", id);
+
+        // 参数校验
+        if (id == null || id <= 0) {
+            throw new IllegalArgumentException("历史记录ID不能为空且必须大于0");
+        }
+
+        // 查询历史记录
+        LambdaQueryWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreOperationalStatisticsHistory::getId, id)
+                .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0);
+
+        StoreOperationalStatisticsHistory history = statisticsHistoryMapper.selectOne(wrapper);
+
+        if (history == null) {
+            log.warn("查询历史统计记录详情失败,记录不存在或已删除 - id={}", id);
+            throw new RuntimeException("历史记录不存在或已删除");
+        }
+
+        log.info("查询历史统计记录详情成功 - id={}", id);
+        return history;
+    }
+
+    @Override
+    public IPage<StoreOperationalStatisticsHistory> getPlatformHistoryList(Integer storeId, Integer page, Integer size, String createdTime) {
+        log.info("StoreOperationalStatisticsServiceImpl.getHistoryList - storeId={}, page={}, size={}, createdTime={}", storeId, page, size, createdTime);
+
+        // 参数校验
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
+        }
+        int pageNum = page != null && page > 0 ? page : 1;
+        int pageSize = size != null && size > 0 ? size : 10;
+
+        // 构建分页对象
+        IPage<StoreOperationalStatisticsHistory> pageObj = new Page<>(pageNum, pageSize);
+
+        // 构建查询条件
+        LambdaQueryWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreOperationalStatisticsHistory::getStoreId, storeId)
+                .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0)
+                .orderByDesc(StoreOperationalStatisticsHistory::getQueryTime);
+
+        // created_time 可选:与 query_time 为同一天(query_time 格式 yyyy-MM-dd HH:mm:ss,created_time 格式 yyyy-MM-dd)
+        if (StringUtils.hasText(createdTime)) {
+            wrapper.apply("DATE(query_time) = {0}", createdTime.trim());
+        }
+
+        // 执行分页查询
+        IPage<StoreOperationalStatisticsHistory> result = statisticsHistoryMapper.selectPage(pageObj, wrapper);
+
+        log.info("查询历史统计记录列表成功 - storeId={}, 共{}条记录,当前页{}条", storeId, result.getTotal(), result.getRecords().size());
+        return result;
+    }
+
+    @Override
+    public boolean batchPlatformDeleteHistory(List<Integer> ids) {
+        log.info("StoreOperationalStatisticsServiceImpl.batchDeleteHistory - ids={}", ids);
+
+        if (ids == null || ids.isEmpty()) {
+            log.warn("批量删除历史统计记录失败,ID列表为空");
+            return false;
+        }
+
+        try {
+            // 使用逻辑删除
+            LambdaUpdateWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaUpdateWrapper<>();
+            wrapper.in(StoreOperationalStatisticsHistory::getId, ids)
+                    .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0)
+                    .set(StoreOperationalStatisticsHistory::getDeleteFlag, 1);
+
+            int result = statisticsHistoryMapper.update(null, wrapper);
+            return result > 0;
+        } catch (Exception e) {
+            log.error("批量删除历史统计记录失败 - ids={}, error={}", ids, e.getMessage(), e);
+            return false;
+        }
+    }
+
+    @Override
+    public String generateStatisticsComparisonPdfByHistoryId(Integer storeId, Integer historyId) {
+        log.info("StoreOperationalStatisticsServiceImpl.generateStatisticsComparisonPdfByHistoryId - storeId={}, historyId={}", storeId, historyId);
+
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("店铺ID不能为空且必须大于0");
+        }
+        if (historyId == null || historyId <= 0) {
+            throw new IllegalArgumentException("历史记录ID不能为空且必须大于0");
+        }
+
+        // 1. 根据 historyId 查询历史记录
+        StoreOperationalStatisticsHistory history = getHistoryById(historyId);
+        if (!storeId.equals(history.getStoreId())) {
+            throw new IllegalArgumentException("历史记录与店铺ID不匹配");
+        }
+        String statisticsDataJson = history.getStatisticsData();
+        if (statisticsDataJson == null || statisticsDataJson.trim().isEmpty()) {
+            throw new IllegalArgumentException("该历史记录无统计数据(statistics_data为空)");
+        }
+
+        // 2. 将 statistics_data 的 JSON 转为 StoreOperationalStatisticsComparisonVo
+        StoreOperationalStatisticsComparisonVo comparison = JSON.parseObject(statisticsDataJson.trim(), StoreOperationalStatisticsComparisonVo.class);
+        if (comparison == null) {
+            throw new RuntimeException("解析统计数据对比JSON失败");
+        }
+
+        try {
+            // 3. 生成图片
+            byte[] imageBytes = PlatformStatisticsComparisonImageUtil.generateImage(comparison);
+            java.io.ByteArrayInputStream imageInputStream = new java.io.ByteArrayInputStream(imageBytes);
+            byte[] pdfBytes = ImageToPdfUtil.imageToPdfBytes(imageInputStream);
+            if (pdfBytes == null || pdfBytes.length == 0) {
+                throw new RuntimeException("图片转PDF失败");
+            }
+
+            // 4. 上传PDF到OSS
+            String ossFilePath = "statistics/comparison_" + storeId + "_" + RandomCreateUtil.getRandomNum(8) + ".pdf";
+            java.io.ByteArrayInputStream pdfInputStream = new java.io.ByteArrayInputStream(pdfBytes);
+            String pdfUrl = aliOSSUtil.uploadFile(pdfInputStream, ossFilePath);
+            log.info("根据历史记录生成统计数据对比PDF并上传成功 - storeId={}, historyId={}, pdfUrl={}", storeId, historyId, pdfUrl);
+
+            // 5. 更新该历史记录的 PDF URL
+            updateHistoryPdfUrl(historyId, pdfUrl);
+            return pdfUrl;
+        } catch (Exception e) {
+            log.error("根据历史记录生成统计数据对比PDF失败 - storeId={}, historyId={}, error={}", storeId, historyId, e.getMessage(), e);
+            throw new RuntimeException("生成PDF报告失败: " + e.getMessage(), e);
+        }
+    }
+
+    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;
+        }
+    }
+
+
+    public StoreOperationalStatisticsHistory getHistoryById(Integer id) {
+        log.info("StoreOperationalStatisticsServiceImpl.getHistoryById - id={}", id);
+
+        // 参数校验
+        if (id == null || id <= 0) {
+            throw new IllegalArgumentException("历史记录ID不能为空且必须大于0");
+        }
+
+        // 查询历史记录
+        LambdaQueryWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreOperationalStatisticsHistory::getId, id)
+                .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0);
+
+        StoreOperationalStatisticsHistory history = statisticsHistoryMapper.selectOne(wrapper);
+
+        if (history == null) {
+            log.warn("查询历史统计记录详情失败,记录不存在或已删除 - id={}", id);
+            throw new RuntimeException("历史记录不存在或已删除");
+        }
+
+        log.info("查询历史统计记录详情成功 - id={}", id);
+        return history;
+    }
+
+    /**
+     * 构建流量数据对比
+     */
+    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()));
+        comparison.setNewVisitors(buildComparisonData(current.getNewVisitors(), previous.getNewVisitors()));
+        comparison.setVisitDuration(buildComparisonData(current.getVisitDuration(), previous.getVisitDuration()));
+        comparison.setAvgVisitDuration(buildComparisonData(current.getAvgVisitDuration(), previous.getAvgVisitDuration()));
+        return comparison;
+    }
+
+    /**
+     * 构建互动数据对比
+     */
+    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()));
+        comparison.setConsultMerchantCount(buildComparisonData(current.getConsultMerchantCount(), previous.getConsultMerchantCount()));
+        comparison.setFriendsCount(buildComparisonData(current.getFriendsCount(), previous.getFriendsCount()));
+        comparison.setFollowCount(buildComparisonData(current.getFollowCount(), previous.getFollowCount()));
+        comparison.setFansCount(buildComparisonData(current.getFansCount(), previous.getFansCount()));
+        comparison.setPostsPublishedCount(buildComparisonData(current.getPostsPublishedCount(), previous.getPostsPublishedCount()));
+        comparison.setPostLikesCount(buildComparisonData(current.getPostLikesCount(), previous.getPostLikesCount()));
+        comparison.setPostCommentsCount(buildComparisonData(current.getPostCommentsCount(), previous.getPostCommentsCount()));
+        comparison.setPostSharesCount(buildComparisonData(current.getPostSharesCount(), previous.getPostSharesCount()));
+        comparison.setReportedCount(buildComparisonData(current.getReportedCount(), previous.getReportedCount()));
+        comparison.setBlockedCount(buildComparisonData(current.getBlockedCount(), previous.getBlockedCount()));
+        return comparison;
+    }
+
+    /**
+     * 构建优惠券数据对比
+     */
+    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()));
+        comparison.setGiftToFriendsUsedAmount(buildComparisonData(current.getGiftToFriendsUsedAmount(), previous.getGiftToFriendsUsedAmount()));
+        comparison.setGiftToFriendsUsedAmountRatio(buildComparisonData(current.getGiftToFriendsUsedAmountRatio(), previous.getGiftToFriendsUsedAmountRatio()));
+        comparison.setFriendsGiftCount(buildComparisonData(current.getFriendsGiftCount(), previous.getFriendsGiftCount()));
+        comparison.setFriendsGiftAmount(buildComparisonData(current.getFriendsGiftAmount(), previous.getFriendsGiftAmount()));
+        comparison.setFriendsGiftUsedCount(buildComparisonData(current.getFriendsGiftUsedCount(), previous.getFriendsGiftUsedCount()));
+        comparison.setFriendsGiftUsedAmount(buildComparisonData(current.getFriendsGiftUsedAmount(), previous.getFriendsGiftUsedAmount()));
+        comparison.setFriendsGiftUsedAmountRatio(buildComparisonData(current.getFriendsGiftUsedAmountRatio(), previous.getFriendsGiftUsedAmountRatio()));
+        return comparison;
+    }
+
+    /**
+     * 构建代金券数据对比
+     */
+    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()));
+        comparison.setGiftToFriendsUsedAmount(buildComparisonData(current.getGiftToFriendsUsedAmount(), previous.getGiftToFriendsUsedAmount()));
+        comparison.setGiftToFriendsUsedAmountRatio(buildComparisonData(current.getGiftToFriendsUsedAmountRatio(), previous.getGiftToFriendsUsedAmountRatio()));
+        comparison.setFriendsGiftCount(buildComparisonData(current.getFriendsGiftCount(), previous.getFriendsGiftCount()));
+        comparison.setFriendsGiftAmount(buildComparisonData(current.getFriendsGiftAmount(), previous.getFriendsGiftAmount()));
+        comparison.setFriendsGiftUsedCount(buildComparisonData(current.getFriendsGiftUsedCount(), previous.getFriendsGiftUsedCount()));
+        comparison.setFriendsGiftUsedAmount(buildComparisonData(current.getFriendsGiftUsedAmount(), previous.getFriendsGiftUsedAmount()));
+        comparison.setFriendsGiftUsedAmountRatio(buildComparisonData(current.getFriendsGiftUsedAmountRatio(), previous.getFriendsGiftUsedAmountRatio()));
+        return comparison;
+    }
+
+    /**
+     * 构建服务质量数据对比
+     */
+    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.setScoreOne(buildComparisonData(current.getScoreOne(), previous.getScoreOne()));
+        comparison.setScoreTwo(buildComparisonData(current.getScoreTwo(), previous.getScoreTwo()));
+        comparison.setScoreThree(buildComparisonData(current.getScoreThree(), previous.getScoreThree()));
+        comparison.setTotalReviews(buildComparisonData(current.getTotalReviews(), previous.getTotalReviews()));
+        comparison.setPositiveReviews(buildComparisonData(current.getPositiveReviews(), previous.getPositiveReviews()));
+        comparison.setNeutralReviews(buildComparisonData(current.getNeutralReviews(), previous.getNeutralReviews()));
+        comparison.setNegativeReviews(buildComparisonData(current.getNegativeReviews(), previous.getNegativeReviews()));
+        comparison.setNegativeReviewRatio(buildComparisonData(current.getNegativeReviewRatio(), previous.getNegativeReviewRatio()));
+        comparison.setNegativeReviewAppealsCount(buildComparisonData(current.getNegativeReviewAppealsCount(), previous.getNegativeReviewAppealsCount()));
+        comparison.setNegativeReviewAppealsSuccessCount(buildComparisonData(current.getNegativeReviewAppealsSuccessCount(), previous.getNegativeReviewAppealsSuccessCount()));
+        comparison.setNegativeReviewAppealsSuccessRatio(buildComparisonData(current.getNegativeReviewAppealsSuccessRatio(), previous.getNegativeReviewAppealsSuccessRatio()));
+        return comparison;
+    }
+
+
+    /**
+     * 构建价目表排名数据对比
+     */
+    private List<StoreOperationalStatisticsComparisonVo.PriceListRankingComparison> buildPriceListRankingComparison(
+            List<StoreOperationalStatisticsVo.PriceListRanking> current, List<StoreOperationalStatisticsVo.PriceListRanking> previous, Integer storeId) {
+        List<StoreOperationalStatisticsComparisonVo.PriceListRankingComparison> result = new ArrayList<>();
+
+        // 如果当期数据为空,返回空列表
+        if (current == null || current.isEmpty()) {
+            return result;
+        }
+
+        // 如果上期数据为空,创建空列表
+        if (previous == null) {
+            previous = new ArrayList<>();
+        }
+
+        // 创建上期数据的Map,以priceId为key,方便查找
+        Map<Integer, StoreOperationalStatisticsVo.PriceListRanking> previousMap = new HashMap<>();
+        for (StoreOperationalStatisticsVo.PriceListRanking prev : previous) {
+            if (prev.getPriceId() != null) {
+                previousMap.put(prev.getPriceId(), prev);
+            }
+        }
+
+        // 收集需要查询名称的priceId(如果名称为空)
+        List<Integer> needQueryPriceIds = new ArrayList<>();
+        for (StoreOperationalStatisticsVo.PriceListRanking curr : current) {
+            if (curr.getPriceId() != null &&
+                    (curr.getPriceListItemName() == null || curr.getPriceListItemName().trim().isEmpty())) {
+                needQueryPriceIds.add(curr.getPriceId());
+            }
+        }
+
+        // 批量查询价目表名称(根据business_section判断查询美食价目表还是通用价目表)
+        Map<Integer, String> priceNameMap = new HashMap<>();
+        if (!needQueryPriceIds.isEmpty() && storeId != null) {
+            // 查询店铺信息,获取business_section
+            StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
+            if (storeInfo != null && storeInfo.getBusinessSection() != null && storeInfo.getBusinessSection() == 1) {
+                // business_section = 1 表示美食,查询美食价目表
+                LambdaQueryWrapper<StoreCuisine> cuisineWrapper = new LambdaQueryWrapper<>();
+                cuisineWrapper.in(StoreCuisine::getId, needQueryPriceIds)
+                        .eq(StoreCuisine::getDeleteFlag, 0);
+                List<StoreCuisine> cuisines = storeCuisineMapper.selectList(cuisineWrapper);
+                for (StoreCuisine cuisine : cuisines) {
+                    if (cuisine.getId() != null && cuisine.getName() != null) {
+                        priceNameMap.put(cuisine.getId(), cuisine.getName());
+                    }
+                }
+            } else {
+                // 其他情况查询通用价目表
+                LambdaQueryWrapper<StorePrice> priceWrapper = new LambdaQueryWrapper<>();
+                priceWrapper.in(StorePrice::getId, needQueryPriceIds)
+                        .eq(StorePrice::getDeleteFlag, 0);
+                List<StorePrice> prices = storePriceMapper.selectList(priceWrapper);
+                for (StorePrice price : prices) {
+                    if (price.getId() != null && price.getName() != null) {
+                        priceNameMap.put(price.getId(), price.getName());
+                    }
+                }
+            }
+        }
+
+        // 遍历当期数据,构建对比
+        for (StoreOperationalStatisticsVo.PriceListRanking curr : current) {
+            StoreOperationalStatisticsComparisonVo.PriceListRankingComparison comparison =
+                    new StoreOperationalStatisticsComparisonVo.PriceListRankingComparison();
+
+            comparison.setPriceId(curr.getPriceId());
+
+            // 设置价目表名称,如果为空则从查询结果中获取
+            String priceListItemName = curr.getPriceListItemName();
+            if (priceListItemName == null || priceListItemName.trim().isEmpty()) {
+                priceListItemName = priceNameMap.get(curr.getPriceId());
+            }
+            comparison.setPriceListItemName(priceListItemName);
+
+            // 获取上期对应的价目表数据
+            StoreOperationalStatisticsVo.PriceListRanking prev = previousMap.get(curr.getPriceId());
+
+            // 构建浏览量对比
+            Long currentPageViews = curr.getPageViews() != null ? curr.getPageViews() : 0L;
+            Long previousPageViews = (prev != null && prev.getPageViews() != null) ? prev.getPageViews() : 0L;
+            comparison.setPageViews(buildComparisonData(currentPageViews, previousPageViews));
+
+            // 构建访客数对比
+            Long currentVisitors = curr.getVisitors() != null ? curr.getVisitors() : 0L;
+            Long previousVisitors = (prev != null && prev.getVisitors() != null) ? prev.getVisitors() : 0L;
+            comparison.setVisitors(buildComparisonData(currentVisitors, previousVisitors));
+
+            // 构建分享数对比
+            Long currentShares = curr.getShares() != null ? curr.getShares() : 0L;
+            Long previousShares = (prev != null && prev.getShares() != null) ? prev.getShares() : 0L;
+            comparison.setShares(buildComparisonData(currentShares, previousShares));
+
+            result.add(comparison);
+        }
+
+        return result;
+    }
+
+    /**
+     * 异步调用AI接口进行数据分析
+     *
+     * @param historyId 历史记录ID
+     * @param storeId 店铺ID
+     * @param comparison 统计数据对比
+     */
+    private void callAiAnalysisAsync(Integer historyId, Integer storeId, StoreOperationalStatisticsComparisonVo comparison) {
+        // 如果AI接口URL未配置,跳过调用
+        if (!StringUtils.hasText(aiStatisticsAnalysisUrl)) {
+            log.warn("AI统计分析接口URL未配置,跳过AI分析 - historyId={}", historyId);
+            return;
+        }
+
+        try {
+            // 获取访问令牌
+            String accessToken = aiAuthTokenUtil.getAccessToken();
+            if (!StringUtils.hasText(accessToken)) {
+                log.error("调用AI分析接口失败,获取accessToken失败 - historyId={}", historyId);
+                return;
+            }
+
+            // 构建请求体,只发送id
+            Map<String, Object> requestBody = new HashMap<>();
+            requestBody.put("id", historyId);
+
+            // 构建请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.set("Authorization", "Bearer " + accessToken);
+
+            log.info(requestBody.toString());
+
+            HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
+
+            log.info("开始调用AI统计分析接口 - historyId={}, storeId={}", historyId, storeId);
+            ResponseEntity<String> response = restTemplate.postForEntity(
+                    aiStatisticsAnalysisUrl != null ? aiStatisticsAnalysisUrl : "", request, String.class);
+
+            if (response != null && response.getStatusCode() == HttpStatus.OK) {
+                String responseBody = response.getBody();
+                log.info("AI统计分析接口调用成功 - historyId={}, response={}", historyId, responseBody);
+
+                // 解析AI返回的结果
+                if (StringUtils.hasText(responseBody)) {
+                    try {
+                        @SuppressWarnings("unchecked")
+                        Map<String, Object> responseMap = (Map<String, Object>) JSON.parseObject(responseBody, Map.class);
+                        if (responseMap != null) {
+                            String summary = extractStringValue(responseMap, "summary");
+                            String optimizationSuggestions = extractStringValue(responseMap, "optimizationSuggestions");
+
+                            // 更新历史记录的AI分析结果
+                            if (StringUtils.hasText(summary) || StringUtils.hasText(optimizationSuggestions)) {
+                                updateHistoryAiAnalysis(historyId, 1, summary, optimizationSuggestions);
+                                log.info("更新AI分析结果成功 - historyId={}", historyId);
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.warn("解析AI分析结果失败 - historyId={}, responseBody={}, error={}",
+                                historyId, responseBody, e.getMessage());
+                    }
+                }
+            } else {
+                log.warn("AI统计分析接口调用失败 - historyId={}, statusCode={}",
+                        historyId, response != null ? response.getStatusCode() : null);
+            }
+        } catch (Exception e) {
+            log.error("调用AI统计分析接口异常 - historyId={}, error={}", historyId, e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 构建对比数据
+     */
+    private StoreOperationalStatisticsComparisonVo.BaseComparisonData buildComparisonData(Object current, Object previous) {
+        StoreOperationalStatisticsComparisonVo.BaseComparisonData comparisonData = new StoreOperationalStatisticsComparisonVo.BaseComparisonData();
+        comparisonData.setCurrent(current);
+        comparisonData.setPrevious(previous);
+
+        // 计算变化率
+        BigDecimal changeRate = calculateChangeRate(current, previous);
+        comparisonData.setChangeRate(changeRate);
+
+        return comparisonData;
+    }
+
+    /**
+     * 计算变化率(百分比)
+     */
+    private BigDecimal calculateChangeRate(Object current, Object previous) {
+        BigDecimal currentValue = toBigDecimal(current);
+        BigDecimal previousValue = toBigDecimal(previous);
+
+        if (previousValue == null || previousValue.compareTo(BigDecimal.ZERO) == 0) {
+            if (currentValue != null && currentValue.compareTo(BigDecimal.ZERO) > 0) {
+                return BigDecimal.valueOf(100); // 从0增长,视为100%增长
+            }
+            return BigDecimal.ZERO;
+        }
+
+        if (currentValue == null) {
+            currentValue = BigDecimal.ZERO;
+        }
+
+        // 变化率 = ((当期 - 上期) / 上期) * 100
+        BigDecimal change = currentValue.subtract(previousValue);
+        BigDecimal rate = change.divide(previousValue, 4, RoundingMode.HALF_UP)
+                .multiply(BigDecimal.valueOf(100));
+        return rate.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 将对象转换为BigDecimal
+     */
+    private BigDecimal toBigDecimal(Object value) {
+        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;
+        }
+    }
+
+    public boolean updateHistoryAiAnalysis(Integer historyId, Integer aiAnalysisCompleted, String summary, String optimizationSuggestions) {
+        log.info("StoreOperationalStatisticsServiceImpl.updateHistoryAiAnalysis - historyId={}, aiAnalysisCompleted={}", historyId, aiAnalysisCompleted);
+
+        if (historyId == null || historyId <= 0) {
+            log.warn("更新历史统计记录AI分析结果失败,历史记录ID无效 - 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("更新历史统计记录AI分析结果失败,历史记录不存在或已删除 - historyId={}", historyId);
+                return false;
+            }
+
+            // 更新AI分析相关字段
+            LambdaUpdateWrapper<StoreOperationalStatisticsHistory> wrapper = new LambdaUpdateWrapper<>();
+            wrapper.eq(StoreOperationalStatisticsHistory::getId, historyId)
+                    .eq(StoreOperationalStatisticsHistory::getDeleteFlag, 0);
+
+            if (aiAnalysisCompleted != null) {
+                wrapper.set(StoreOperationalStatisticsHistory::getAiAnalysisCompleted, aiAnalysisCompleted);
+            }
+            if (summary != null) {
+                wrapper.set(StoreOperationalStatisticsHistory::getSummary, summary.trim());
+            }
+            if (optimizationSuggestions != null) {
+                wrapper.set(StoreOperationalStatisticsHistory::getOptimizationSuggestions, optimizationSuggestions.trim());
+            }
+
+            int result = statisticsHistoryMapper.update(null, wrapper);
+            if (result > 0) {
+                log.info("更新历史统计记录AI分析结果成功 - historyId={}, aiAnalysisCompleted={}", historyId, aiAnalysisCompleted);
+                return true;
+            } else {
+                log.warn("更新历史统计记录AI分析结果失败,未更新任何记录 - historyId={}", historyId);
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("更新历史统计记录AI分析结果失败 - historyId={}, error={}", historyId, e.getMessage(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 从Map中提取String值
+     */
+    private String extractStringValue(Map<String, Object> map, String key) {
+        if (map == null) {
+            return null;
+        }
+        Object value = map.get(key);
+        if (value == null) {
+            return null;
+        }
+        return value.toString();
+    }
+
+    /**
+     * 保存对比统计数据到历史表
+     * @param storeId 店铺ID
+     * @param currentStartTime 当期开始时间
+     * @param currentEndTime 当期结束时间
+     * @param previousStartTime 上期开始时间
+     * @param previousEndTime 上期结束时间
+     * @param comparison 对比数据
+     * @param pdfUrl PDF文件URL(可选)
+     * @return 历史记录ID,保存失败返回null
+     */
+    private Integer saveStatisticsHistory(Integer storeId, String currentStartTime, String currentEndTime,
+                                          String previousStartTime, String previousEndTime,
+                                          StoreOperationalStatisticsComparisonVo comparison, String pdfUrl) {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
+            Date startDate = sdf.parse(currentStartTime);
+            Date endDate = sdf.parse(currentEndTime);
+            Date previousStartDate = sdf.parse(previousStartTime);
+            Date previousEndDate = sdf.parse(previousEndTime);
+
+            StoreOperationalStatisticsHistory history = new StoreOperationalStatisticsHistory();
+            history.setStoreId(storeId);
+            history.setStartTime(startDate);
+            history.setEndTime(endDate);
+            history.setPreviousStartTime(previousStartDate);
+            history.setPreviousEndTime(previousEndDate);
+            history.setQueryTime(new Date());
+
+            // 将对比统计数据序列化为JSON字符串(包含当期、上期和变化率等完整对比数据)
+            String statisticsJson = JSON.toJSONString(comparison);
+            history.setStatisticsData(statisticsJson);
+
+            // 如果提供了PDF URL,则保存
+            if (pdfUrl != null && !pdfUrl.trim().isEmpty()) {
+                history.setPdfUrl(pdfUrl);
+            }
+
+            statisticsHistoryMapper.insert(history);
+            log.info("保存对比统计数据到历史表成功 - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}, historyId={}",
+                    storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime, history.getId());
+            return history.getId();
+        } catch (Exception e) {
+            log.error("保存对比统计数据到历史表失败 - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}, error={}",
+                    storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime, e.getMessage(), e);
+            // 保存失败不影响主流程,只记录日志
+            return null;
+        }
+    }
+
+
+    /**
+     * 计算统计数据(从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);
+
+            // 查询指定时间范围内的日统计数据
+            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, endDate, storeId);
+
+        } catch (ParseException 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());
+        }
+    }
+
+    /**
+     * 聚合统计数据(优先取结束日期的数据,只累加流量数据中的新增访客数,其他数据取结束日期的记录)
+     *
+     * @param statisticsList 统计数据列表
+     * @param endDate 结束日期(用于优先选择该日期的数据)
+     * @param storeId 店铺ID
+     */
+    private StoreOperationalStatisticsVo aggregateStatistics(List<StoreTrackStatistics> statisticsList, Date endDate, Integer storeId) {
+        StoreOperationalStatisticsVo result = new StoreOperationalStatisticsVo();
+
+        // 优先查找结束日期的数据(比较日期部分,忽略时间)
+        StoreTrackStatistics endDateStat = null;
+        SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
+        String endDateStr = sdf.format(endDate);
+        for (StoreTrackStatistics stat : statisticsList) {
+            if (stat.getStatDate() != null) {
+                String statDateStr = sdf.format(stat.getStatDate());
+                if (endDateStr.equals(statDateStr)) {
+                    endDateStat = stat;
+                    break;
+                }
+            }
+        }
+
+        // 判断结束日期的数据是否有效(不全为0)
+        boolean endDateStatValid = false;
+        if (endDateStat != null) {
+            endDateStatValid = isStatisticsValid(endDateStat);
+        }
+
+        // 如果没有结束日期的数据,或者结束日期的数据全是0,则按日期排序,取有数据的最新日期
+        StoreTrackStatistics latestStat = null;
+        if (endDateStat != null && endDateStatValid) {
+            latestStat = endDateStat;
+        } else {
+            // 按日期排序,从最新到最旧
+            statisticsList.sort((a, b) -> {
+                if (a.getStatDate() == null && b.getStatDate() == null) return 0;
+                if (a.getStatDate() == null) return 1;
+                if (b.getStatDate() == null) return -1;
+                return b.getStatDate().compareTo(a.getStatDate());
+            });
+
+            // 查找第一个有数据的记录
+            for (StoreTrackStatistics stat : statisticsList) {
+                if (isStatisticsValid(stat)) {
+                    latestStat = stat;
+                    break;
+                }
+            }
+
+            // 如果所有记录都无效,使用最新的记录(即使全是0)
+            if (latestStat == null && !statisticsList.isEmpty()) {
+                latestStat = statisticsList.get(0);
+            }
+
+            if (latestStat != null) {
+                if (endDateStat != null && !endDateStatValid) {
+                    log.info("结束日期{}的数据全是0,使用范围内有数据的最新日期{}的数据",
+                            endDateStr,
+                            latestStat.getStatDate() != null ? sdf.format(latestStat.getStatDate()) : "未知");
+                } else {
+                    log.info("结束日期{}没有统计数据,使用范围内最新日期{}的数据",
+                            endDateStr,
+                            latestStat.getStatDate() != null ? sdf.format(latestStat.getStatDate()) : "未知");
+                }
+            }
+        }
+
+        // 如果找不到有效数据,返回空结果
+        if (latestStat == null) {
+            log.warn("未找到有效的统计数据,返回空结果");
+            return result;
+        }
+
+        // 只累加流量数据中的新增访客数
+        long newVisitorCountSum = 0L;
+        for (StoreTrackStatistics stat : statisticsList) {
+            if (stat.getTrafficData() != null && !stat.getTrafficData().isEmpty()) {
+                try {
+                    Map<String, Object> trafficData = (Map<String, Object>) JSON.parseObject(stat.getTrafficData(), Map.class);
+                    if (trafficData != null && trafficData.containsKey("newVisitorCount")) {
+                        newVisitorCountSum += getLongValue(trafficData, "newVisitorCount");
+                    }
+                } catch (Exception e) {
+                    log.warn("解析流量数据失败: {}", stat.getTrafficData(), e);
+                }
+            }
+        }
+
+        // 使用最新记录的数据,但替换新增访客数为累加值
+        if (latestStat.getTrafficData() != null && !latestStat.getTrafficData().isEmpty()) {
+            try {
+                Map<String, Object> latestTrafficData = (Map<String, Object>) JSON.parseObject(latestStat.getTrafficData(), Map.class);
+                if (latestTrafficData != null) {
+                    latestTrafficData.put("newVisitorCount", newVisitorCountSum);
+                    result.setTrafficData(convertToTrafficDataVoFromMap(latestTrafficData));
+                }
+            } catch (Exception e) {
+                log.warn("解析最新流量数据失败: {}", latestStat.getTrafficData(), e);
+            }
+        }
+
+        // 其他数据直接使用最新记录的值
+        if (latestStat.getInteractionData() != null && !latestStat.getInteractionData().isEmpty()) {
+            result.setInteractionData(convertToInteractionDataVoFromJson(latestStat.getInteractionData()));
+        }
+
+        if (latestStat.getCouponData() != null && !latestStat.getCouponData().isEmpty()) {
+            result.setCouponData(convertToCouponDataVoFromJson(latestStat.getCouponData()));
+        }
+
+        if (latestStat.getVoucherData() != null && !latestStat.getVoucherData().isEmpty()) {
+            result.setVoucherData(convertToVoucherDataVoFromJson(latestStat.getVoucherData()));
+        }
+
+        if (latestStat.getServiceData() != null && !latestStat.getServiceData().isEmpty()) {
+            result.setServiceQualityData(convertToServiceQualityDataVoFromJson(latestStat.getServiceData()));
+        }
+
+        if (latestStat.getPriceRankingData() != null && !latestStat.getPriceRankingData().isEmpty()) {
+            result.setPriceListRanking(convertToPriceListRankingVoFromJson(latestStat.getPriceRankingData(), storeId));
+        }
+
+        return result;
+    }
+
+    // ==================== 工具方法 ====================
+
+    /**
+     * 判断统计数据是否有效(不全为0)
+     * 检查流量数据、互动数据、优惠券数据、代金券数据、服务质量数据、价目表排名数据中是否至少有一个字段不为0
+     */
+    @SuppressWarnings("unchecked")
+    private boolean isStatisticsValid(StoreTrackStatistics stat) {
+        if (stat == null) {
+            return false;
+        }
+
+        try {
+            // 检查流量数据
+            if (stat.getTrafficData() != null && !stat.getTrafficData().isEmpty()) {
+                Map<String, Object> trafficData = (Map<String, Object>) JSON.parseObject(stat.getTrafficData(), Map.class);
+                if (trafficData != null) {
+                    long searchCount = getLongValue(trafficData, "searchCount");
+                    long viewCount = getLongValue(trafficData, "viewCount");
+                    long visitorCount = getLongValue(trafficData, "visitorCount");
+                    long newVisitorCount = getLongValue(trafficData, "newVisitorCount");
+                    long totalDuration = getLongValue(trafficData, "totalDuration");
+                    if (searchCount > 0 || viewCount > 0 || visitorCount > 0 || newVisitorCount > 0 || totalDuration > 0) {
+                        return true;
+                    }
+                }
+            }
+
+            // 检查互动数据
+            if (stat.getInteractionData() != null && !stat.getInteractionData().isEmpty()) {
+                Map<String, Object> interactionData = (Map<String, Object>) JSON.parseObject(stat.getInteractionData(), Map.class);
+                if (interactionData != null) {
+                    long collectCount = getLongValue(interactionData, "collectCount");
+                    long shareCount = getLongValue(interactionData, "shareCount");
+                    long checkinCount = getLongValue(interactionData, "checkinCount");
+                    long consultCount = getLongValue(interactionData, "consultCount");
+                    long friendCount = getLongValue(interactionData, "friendCount");
+                    long followCount = getLongValue(interactionData, "followCount");
+                    long fansCount = getLongValue(interactionData, "fansCount");
+                    long postCount = getLongValue(interactionData, "postCount");
+                    long postLikeCount = getLongValue(interactionData, "postLikeCount");
+                    long postCommentCount = getLongValue(interactionData, "postCommentCount");
+                    long postRepostCount = getLongValue(interactionData, "postRepostCount");
+                    if (collectCount > 0 || shareCount > 0 || checkinCount > 0 || consultCount > 0 ||
+                            friendCount > 0 || followCount > 0 || fansCount > 0 || postCount > 0 ||
+                            postLikeCount > 0 || postCommentCount > 0 || postRepostCount > 0) {
+                        return true;
+                    }
+                }
+            }
+
+            // 检查优惠券数据
+            if (stat.getCouponData() != null && !stat.getCouponData().isEmpty()) {
+                Map<String, Object> couponData = (Map<String, Object>) JSON.parseObject(stat.getCouponData(), Map.class);
+                if (couponData != null) {
+                    long giveToFriendCount = getLongValue(couponData, "giveToFriendCount");
+                    BigDecimal giveToFriendAmount = getBigDecimalValue(couponData, "giveToFriendAmount");
+                    long friendGiveCount = getLongValue(couponData, "friendGiveCount");
+                    BigDecimal friendGiveAmount = getBigDecimalValue(couponData, "friendGiveAmount");
+                    if (giveToFriendCount > 0 || giveToFriendAmount.compareTo(BigDecimal.ZERO) > 0 ||
+                            friendGiveCount > 0 || friendGiveAmount.compareTo(BigDecimal.ZERO) > 0) {
+                        return true;
+                    }
+                }
+            }
+
+            // 检查代金券数据
+            if (stat.getVoucherData() != null && !stat.getVoucherData().isEmpty()) {
+                Map<String, Object> voucherData = (Map<String, Object>) JSON.parseObject(stat.getVoucherData(), Map.class);
+                if (voucherData != null) {
+                    long giveToFriendCount = getLongValue(voucherData, "giveToFriendCount");
+                    BigDecimal giveToFriendAmount = getBigDecimalValue(voucherData, "giveToFriendAmount");
+                    long friendGiveCount = getLongValue(voucherData, "friendGiveCount");
+                    BigDecimal friendGiveAmount = getBigDecimalValue(voucherData, "friendGiveAmount");
+                    if (giveToFriendCount > 0 || giveToFriendAmount.compareTo(BigDecimal.ZERO) > 0 ||
+                            friendGiveCount > 0 || friendGiveAmount.compareTo(BigDecimal.ZERO) > 0) {
+                        return true;
+                    }
+                }
+            }
+
+            // 检查服务质量数据
+            if (stat.getServiceData() != null && !stat.getServiceData().isEmpty()) {
+                Map<String, Object> serviceData = (Map<String, Object>) JSON.parseObject(stat.getServiceData(), Map.class);
+                if (serviceData != null) {
+                    double storeScore = getBigDecimalValue(serviceData, "storeScore").doubleValue();
+                    long ratingCount = getLongValue(serviceData, "ratingCount");
+                    long appealCount = getLongValue(serviceData, "appealCount");
+                    if (storeScore > 0 || ratingCount > 0 || appealCount > 0) {
+                        return true;
+                    }
+                }
+            }
+
+            // 检查价目表排名数据
+            if (stat.getPriceRankingData() != null && !stat.getPriceRankingData().isEmpty()) {
+                List<Map<String, Object>> priceRankingData = (List<Map<String, Object>>) (List<?>) JSON.parseArray(stat.getPriceRankingData());
+                if (priceRankingData != null && !priceRankingData.isEmpty()) {
+                    for (Map<String, Object> item : priceRankingData) {
+                        long viewCount = getLongValue(item, "viewCount");
+                        long visitorCount = getLongValue(item, "visitorCount");
+                        long shareCount = getLongValue(item, "shareCount");
+                        if (viewCount > 0 || visitorCount > 0 || shareCount > 0) {
+                            return true;
+                        }
+                    }
+                }
+            }
+
+            return false;
+        } catch (Exception e) {
+            log.warn("判断统计数据有效性失败: {}", e.getMessage(), e);
+            // 如果解析失败,认为数据无效
+            return false;
+        }
+    }
+
+    /**
+     * 从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;
+        }
+    }
+
+    // ==================== 从JSON/Map直接转换的方法 ====================
+
+    /**
+     * 从Map转换为流量数据VO
+     */
+    private StoreOperationalStatisticsVo.TrafficData convertToTrafficDataVoFromMap(Map<String, Object> trafficDataMap) {
+        StoreOperationalStatisticsVo.TrafficData vo = new StoreOperationalStatisticsVo.TrafficData();
+        vo.setStoreSearchVolume(getLongValue(trafficDataMap, "searchCount"));
+        vo.setPageViews(getLongValue(trafficDataMap, "viewCount"));
+        vo.setVisitors(getLongValue(trafficDataMap, "visitorCount"));
+        vo.setNewVisitors(getLongValue(trafficDataMap, "newVisitorCount"));
+        // 访问时长从毫秒转换为秒
+        Long totalDuration = getLongValue(trafficDataMap, "totalDuration");
+        vo.setVisitDuration(totalDuration / 1000);
+        Long avgDuration = getLongValue(trafficDataMap, "avgDuration");
+        vo.setAvgVisitDuration(avgDuration / 1000);
+        return vo;
+    }
+
+    /**
+     * 从JSON字符串转换为互动数据VO
+     */
+    @SuppressWarnings("unchecked")
+    private StoreOperationalStatisticsVo.InteractionData convertToInteractionDataVoFromJson(String interactionDataJson) {
+        try {
+            Map<String, Object> data = (Map<String, Object>) JSON.parseObject(interactionDataJson, Map.class);
+            if (data == null) return new StoreOperationalStatisticsVo.InteractionData();
+
+            StoreOperationalStatisticsVo.InteractionData vo = new StoreOperationalStatisticsVo.InteractionData();
+            vo.setStoreCollectionCount(getLongValue(data, "collectCount"));
+            vo.setStoreShareCount(getLongValue(data, "shareCount"));
+            vo.setStoreCheckInCount(getLongValue(data, "checkinCount"));
+            vo.setConsultMerchantCount(getLongValue(data, "consultCount"));
+            vo.setFriendsCount(getLongValue(data, "friendCount"));
+            vo.setFollowCount(getLongValue(data, "followCount"));
+            vo.setFansCount(getLongValue(data, "fansCount"));
+            vo.setPostsPublishedCount(getLongValue(data, "postCount"));
+            vo.setPostLikesCount(getLongValue(data, "postLikeCount"));
+            vo.setPostCommentsCount(getLongValue(data, "postCommentCount"));
+            vo.setPostSharesCount(getLongValue(data, "postRepostCount"));
+            vo.setReportedCount(getLongValue(data, "reportCount"));
+            vo.setBlockedCount(getLongValue(data, "blockCount"));
+            return vo;
+        } catch (Exception e) {
+            log.warn("解析互动数据失败: {}", interactionDataJson, e);
+            return new StoreOperationalStatisticsVo.InteractionData();
+        }
+    }
+
+    /**
+     * 从JSON字符串转换为优惠券数据VO
+     */
+    @SuppressWarnings("unchecked")
+    private StoreOperationalStatisticsVo.CouponData convertToCouponDataVoFromJson(String couponDataJson) {
+        try {
+            Map<String, Object> data = (Map<String, Object>) JSON.parseObject(couponDataJson, Map.class);
+            if (data == null) return new StoreOperationalStatisticsVo.CouponData();
+
+            StoreOperationalStatisticsVo.CouponData vo = new StoreOperationalStatisticsVo.CouponData();
+            vo.setGiftToFriendsCount(getLongValue(data, "giveToFriendCount"));
+            vo.setGiftToFriendsAmount(getBigDecimalValue(data, "giveToFriendAmount"));
+            vo.setGiftToFriendsUsedCount(getLongValue(data, "giveToFriendUseCount"));
+            vo.setGiftToFriendsUsedAmount(getBigDecimalValue(data, "giveToFriendUseAmount"));
+
+            // 使用金额占比
+            Object useAmountPercent = data.get("giveToFriendUseAmountPercent");
+            if (useAmountPercent != null) {
+                if (useAmountPercent instanceof Number) {
+                    vo.setGiftToFriendsUsedAmountRatio(BigDecimal.valueOf(((Number) useAmountPercent).doubleValue()));
+                } else {
+                    vo.setGiftToFriendsUsedAmountRatio(new BigDecimal(useAmountPercent.toString()));
+                }
+            } else {
+                vo.setGiftToFriendsUsedAmountRatio(BigDecimal.ZERO);
+            }
+
+            vo.setFriendsGiftCount(getLongValue(data, "friendGiveCount"));
+            vo.setFriendsGiftAmount(getBigDecimalValue(data, "friendGiveAmount"));
+            vo.setFriendsGiftUsedCount(getLongValue(data, "friendGiveUseCount"));
+            vo.setFriendsGiftUsedAmount(getBigDecimalValue(data, "friendGiveUseAmount"));
+
+            // 好友赠送使用金额占比
+            Object friendUseAmountPercent = data.get("friendGiveUseAmountPercent");
+            if (friendUseAmountPercent != null) {
+                if (friendUseAmountPercent instanceof Number) {
+                    vo.setFriendsGiftUsedAmountRatio(BigDecimal.valueOf(((Number) friendUseAmountPercent).doubleValue()));
+                } else {
+                    vo.setFriendsGiftUsedAmountRatio(new BigDecimal(friendUseAmountPercent.toString()));
+                }
+            } else {
+                vo.setFriendsGiftUsedAmountRatio(BigDecimal.ZERO);
+            }
+
+            return vo;
+        } catch (Exception e) {
+            log.warn("解析优惠券数据失败: {}", couponDataJson, e);
+            return new StoreOperationalStatisticsVo.CouponData();
+        }
+    }
+
+    /**
+     * 从JSON字符串转换为代金券数据VO
+     */
+    @SuppressWarnings("unchecked")
+    private StoreOperationalStatisticsVo.VoucherData convertToVoucherDataVoFromJson(String voucherDataJson) {
+        try {
+            Map<String, Object> data = (Map<String, Object>) JSON.parseObject(voucherDataJson, Map.class);
+            if (data == null) return new StoreOperationalStatisticsVo.VoucherData();
+
+            StoreOperationalStatisticsVo.VoucherData vo = new StoreOperationalStatisticsVo.VoucherData();
+            vo.setGiftToFriendsCount(getLongValue(data, "giveToFriendCount"));
+            vo.setGiftToFriendsAmount(getBigDecimalValue(data, "giveToFriendAmount"));
+            vo.setGiftToFriendsUsedCount(getLongValue(data, "giveToFriendUseCount"));
+            vo.setGiftToFriendsUsedAmount(getBigDecimalValue(data, "giveToFriendUseAmount"));
+
+            // 使用金额占比
+            Object useAmountPercent = data.get("giveToFriendUseAmountPercent");
+            if (useAmountPercent != null) {
+                if (useAmountPercent instanceof Number) {
+                    vo.setGiftToFriendsUsedAmountRatio(BigDecimal.valueOf(((Number) useAmountPercent).doubleValue()));
+                } else {
+                    vo.setGiftToFriendsUsedAmountRatio(new BigDecimal(useAmountPercent.toString()));
+                }
+            } else {
+                vo.setGiftToFriendsUsedAmountRatio(BigDecimal.ZERO);
+            }
+
+            vo.setFriendsGiftCount(getLongValue(data, "friendGiveCount"));
+            vo.setFriendsGiftAmount(getBigDecimalValue(data, "friendGiveAmount"));
+            vo.setFriendsGiftUsedCount(getLongValue(data, "friendGiveUseCount"));
+            vo.setFriendsGiftUsedAmount(getBigDecimalValue(data, "friendGiveUseAmount"));
+
+            // 好友赠送使用金额占比
+            Object friendUseAmountPercent = data.get("friendGiveUseAmountPercent");
+            if (friendUseAmountPercent != null) {
+                if (friendUseAmountPercent instanceof Number) {
+                    vo.setFriendsGiftUsedAmountRatio(BigDecimal.valueOf(((Number) friendUseAmountPercent).doubleValue()));
+                } else {
+                    vo.setFriendsGiftUsedAmountRatio(new BigDecimal(friendUseAmountPercent.toString()));
+                }
+            } else {
+                vo.setFriendsGiftUsedAmountRatio(BigDecimal.ZERO);
+            }
+
+            return vo;
+        } catch (Exception e) {
+            log.warn("解析代金券数据失败: {}", voucherDataJson, e);
+            return new StoreOperationalStatisticsVo.VoucherData();
+        }
+    }
+
+    /**
+     * 从JSON字符串转换为服务质量数据VO
+     */
+    @SuppressWarnings("unchecked")
+    private StoreOperationalStatisticsVo.ServiceQualityData convertToServiceQualityDataVoFromJson(String serviceDataJson) {
+        try {
+            Map<String, Object> data = (Map<String, Object>) JSON.parseObject(serviceDataJson, Map.class);
+            if (data == null) return new StoreOperationalStatisticsVo.ServiceQualityData();
+
+            StoreOperationalStatisticsVo.ServiceQualityData vo = new StoreOperationalStatisticsVo.ServiceQualityData();
+
+            // 评分字段
+            Object storeScore = data.get("storeScore");
+            if (storeScore != null) {
+                if (storeScore instanceof Number) {
+                    vo.setStoreRating(BigDecimal.valueOf(((Number) storeScore).doubleValue()).setScale(1, RoundingMode.HALF_UP));
+                } else {
+                    vo.setStoreRating(new BigDecimal(storeScore.toString()).setScale(1, RoundingMode.HALF_UP));
+                }
+            } else {
+                vo.setStoreRating(BigDecimal.ZERO);
+            }
+
+            Object scoreOne = data.get("scoreOne");
+            if (scoreOne != null) {
+                if (scoreOne instanceof Number) {
+                    vo.setScoreOne(BigDecimal.valueOf(((Number) scoreOne).doubleValue()).setScale(1, RoundingMode.HALF_UP));
+                } else {
+                    vo.setScoreOne(new BigDecimal(scoreOne.toString()).setScale(1, RoundingMode.HALF_UP));
+                }
+            } else {
+                vo.setScoreOne(BigDecimal.ZERO);
+            }
+
+            Object scoreTwo = data.get("scoreTwo");
+            if (scoreTwo != null) {
+                if (scoreTwo instanceof Number) {
+                    vo.setScoreTwo(BigDecimal.valueOf(((Number) scoreTwo).doubleValue()).setScale(1, RoundingMode.HALF_UP));
+                } else {
+                    vo.setScoreTwo(new BigDecimal(scoreTwo.toString()).setScale(1, RoundingMode.HALF_UP));
+                }
+            } else {
+                vo.setScoreTwo(BigDecimal.ZERO);
+            }
+
+            Object scoreThree = data.get("scoreThree");
+            if (scoreThree != null) {
+                if (scoreThree instanceof Number) {
+                    vo.setScoreThree(BigDecimal.valueOf(((Number) scoreThree).doubleValue()).setScale(1, RoundingMode.HALF_UP));
+                } else {
+                    vo.setScoreThree(new BigDecimal(scoreThree.toString()).setScale(1, RoundingMode.HALF_UP));
+                }
+            } else {
+                vo.setScoreThree(BigDecimal.ZERO);
+            }
+
+            vo.setTotalReviews(getLongValue(data, "ratingCount"));
+            vo.setPositiveReviews(getLongValue(data, "goodRatingCount"));
+            vo.setNeutralReviews(getLongValue(data, "midRatingCount"));
+            vo.setNegativeReviews(getLongValue(data, "badRatingCount"));
+
+            // 差评占比
+            Object badRatingPercent = data.get("badRatingPercent");
+            if (badRatingPercent != null) {
+                if (badRatingPercent instanceof Number) {
+                    vo.setNegativeReviewRatio(BigDecimal.valueOf(((Number) badRatingPercent).doubleValue()).setScale(2, RoundingMode.HALF_UP));
+                } else {
+                    vo.setNegativeReviewRatio(new BigDecimal(badRatingPercent.toString()).setScale(2, RoundingMode.HALF_UP));
+                }
+            } else {
+                vo.setNegativeReviewRatio(BigDecimal.ZERO);
+            }
+
+            vo.setNegativeReviewAppealsCount(getLongValue(data, "appealCount"));
+            vo.setNegativeReviewAppealsSuccessCount(getLongValue(data, "appealSuccessCount"));
+
+            // 申诉成功占比
+            Object appealSuccessPercent = data.get("appealSuccessPercent");
+            if (appealSuccessPercent != null) {
+                if (appealSuccessPercent instanceof Number) {
+                    vo.setNegativeReviewAppealsSuccessRatio(BigDecimal.valueOf(((Number) appealSuccessPercent).doubleValue()).setScale(2, RoundingMode.HALF_UP));
+                } else {
+                    vo.setNegativeReviewAppealsSuccessRatio(new BigDecimal(appealSuccessPercent.toString()).setScale(2, RoundingMode.HALF_UP));
+                }
+            } else {
+                vo.setNegativeReviewAppealsSuccessRatio(BigDecimal.ZERO);
+            }
+
+            return vo;
+        } catch (Exception e) {
+            log.warn("解析服务质量数据失败: {}", serviceDataJson, e);
+            return new StoreOperationalStatisticsVo.ServiceQualityData();
+        }
+    }
+
+    /**
+     * 从JSON字符串转换为价目表排名数据VO
+     */
+    @SuppressWarnings("unchecked")
+    private List<StoreOperationalStatisticsVo.PriceListRanking> convertToPriceListRankingVoFromJson(String priceRankingDataJson, Integer storeId) {
+        List<StoreOperationalStatisticsVo.PriceListRanking> result = new ArrayList<>();
+
+        try {
+            List<Map<String, Object>> dataList = (List<Map<String, Object>>) (List<?>) JSON.parseArray(priceRankingDataJson);
+            if (dataList == null || dataList.isEmpty()) {
+                return result;
+            }
+
+            // 批量查询价目表名称
+            List<Integer> priceIds = new ArrayList<>();
+            for (Map<String, Object> item : dataList) {
+                Integer priceId = getIntegerValue(item, "priceId");
+                if (priceId != null) {
+                    priceIds.add(priceId);
+                }
+            }
+
+            Map<Integer, String> priceNameMap = new HashMap<>();
+            if (!priceIds.isEmpty() && storeId != null) {
+                // 查询店铺信息,获取business_section
+                StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
+                if (storeInfo != null && storeInfo.getBusinessSection() != null && storeInfo.getBusinessSection() == 1) {
+                    // business_section = 1 表示美食,查询美食价目表
+                    LambdaQueryWrapper<StoreCuisine> cuisineWrapper = new LambdaQueryWrapper<>();
+                    cuisineWrapper.in(StoreCuisine::getId, priceIds)
+                            .eq(StoreCuisine::getDeleteFlag, 0);
+                    List<StoreCuisine> cuisines = storeCuisineMapper.selectList(cuisineWrapper);
+                    for (StoreCuisine cuisine : cuisines) {
+                        if (cuisine.getId() != null && cuisine.getName() != null) {
+                            priceNameMap.put(cuisine.getId(), cuisine.getName());
+                        }
+                    }
+                } else {
+                    // 其他情况查询通用价目表
+                    LambdaQueryWrapper<StorePrice> priceWrapper = new LambdaQueryWrapper<>();
+                    priceWrapper.in(StorePrice::getId, priceIds)
+                            .eq(StorePrice::getDeleteFlag, 0);
+                    List<StorePrice> prices = storePriceMapper.selectList(priceWrapper);
+                    for (StorePrice price : prices) {
+                        if (price.getId() != null && price.getName() != null) {
+                            priceNameMap.put(price.getId(), price.getName());
+                        }
+                    }
+                }
+            }
+
+            // 转换为VO列表
+            for (Map<String, Object> item : dataList) {
+                StoreOperationalStatisticsVo.PriceListRanking ranking = new StoreOperationalStatisticsVo.PriceListRanking();
+                Integer priceId = getIntegerValue(item, "priceId");
+                ranking.setPriceId(priceId);
+                ranking.setPageViews(getLongValue(item, "viewCount"));
+                ranking.setVisitors(getLongValue(item, "visitorCount"));
+                ranking.setShares(getLongValue(item, "shareCount"));
+
+                if (priceId != null) {
+                    ranking.setPriceListItemName(priceNameMap.get(priceId));
+                }
+
+                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);
+            }
+
+        } catch (Exception e) {
+            log.warn("解析价目表排名数据失败: {}", priceRankingDataJson, e);
+        }
+
+        return result;
+    }
+
+    /**
+     * 从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;
+        }
+    }
+
+    /**
+     * 从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;
+        }
+    }
+}

+ 243 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreContractServiceImpl.java

@@ -0,0 +1,243 @@
+package shop.alien.storeplatform.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreContract;
+import shop.alien.entity.store.dto.StoreContractQueryDto;
+import shop.alien.entity.store.vo.StoreContractVo;
+import shop.alien.mapper.StoreContractMapper;
+import shop.alien.storeplatform.service.StoreContractService;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 合同服务实现类
+ *
+ * @author alien
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class StoreContractServiceImpl implements StoreContractService {
+
+    private final StoreContractMapper storeContractMapper;
+
+    @Override
+    public R<IPage<StoreContractVo>> getContractList(StoreContractQueryDto queryDto) {
+        try {
+            log.info("StoreContractServiceImpl.getContractList?queryDto={}", queryDto);
+
+            // 构建分页对象
+            Page<StoreContractVo> page = new Page<>(queryDto.getPageNum(), queryDto.getPageSize());
+
+            // 执行分页查询
+            IPage<StoreContractVo> result = storeContractMapper.selectContractPage(
+                    page,
+                    queryDto.getContractName(),
+                    queryDto.getStatus(),
+                    queryDto.getStoreId()
+            );
+
+            // 设置状态名称
+            for (StoreContractVo vo : result.getRecords()) {
+                if (vo.getStatus() != null) {
+                    vo.setStatusName(vo.getStatus());
+                }
+            }
+
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("StoreContractServiceImpl.getContractList ERROR: {}", e.getMessage(), e);
+            return R.fail("查询合同列表失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<StoreContractVo> getContractDetail(Integer id) {
+        try {
+            log.info("StoreContractServiceImpl.getContractDetail?id={}", id);
+
+            StoreContract contract = storeContractMapper.selectById(id);
+            if (contract == null) {
+                return R.fail("合同不存在");
+            }
+
+            StoreContractVo vo = new StoreContractVo();
+            BeanUtils.copyProperties(contract, vo);
+
+            // 设置状态名称
+            if (vo.getSigningStatus() != null) {
+                vo.setStatusName(vo.getSigningStatus());
+            }
+
+            return R.data(vo);
+        } catch (Exception e) {
+            log.error("StoreContractServiceImpl.getContractDetail ERROR: {}", e.getMessage(), e);
+            return R.fail("查询合同详情失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<StoreContract> addContract(StoreContract contract) {
+        try {
+            log.info("StoreContractServiceImpl.addContract?contract={}", contract);
+
+            // 设置默认值
+            if (contract.getSigningStatus() == null) {
+                contract.setSigningStatus("未签署"); // 默认未签署
+            }
+            if (contract.getDeleteFlag() == null) {
+                contract.setDeleteFlag(0);
+            }
+            contract.setCreatedTime(new Date());
+
+            int result = storeContractMapper.insert(contract);
+            if (result > 0) {
+                return R.data(contract);
+            }
+            return R.fail("新增合同失败");
+        } catch (Exception e) {
+            log.error("StoreContractServiceImpl.addContract ERROR: {}", e.getMessage(), e);
+            return R.fail("新增合同失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<StoreContract> updateContract(StoreContract contract) {
+        try {
+            log.info("StoreContractServiceImpl.updateContract?contract={}", contract);
+
+            if (contract.getId() == null) {
+                return R.fail("合同ID不能为空");
+            }
+
+            contract.setUpdatedTime(new Date());
+            int result = storeContractMapper.updateById(contract);
+            if (result > 0) {
+                StoreContract updated = storeContractMapper.selectById(contract.getId());
+                return R.data(updated);
+            }
+            return R.fail("更新合同失败");
+        } catch (Exception e) {
+            log.error("StoreContractServiceImpl.updateContract ERROR: {}", e.getMessage(), e);
+            return R.fail("更新合同失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<Boolean> signContract(Long id) {
+        try {
+            log.info("StoreContractServiceImpl.signContract?id={}", id);
+
+            StoreContract contract = storeContractMapper.selectById(id);
+            if (contract == null) {
+                return R.fail("合同不存在");
+            }
+
+            if ("已签署".equals(contract.getSigningStatus())) {
+                return R.fail("合同已签署,无需重复签署");
+            }
+
+            // 更新合同状态为已签署
+            StoreContract updateContract = new StoreContract();
+            updateContract.setId(id);
+            updateContract.setSigningStatus("已签署");
+            updateContract.setSigningTime(new Date());
+            updateContract.setUpdatedTime(new Date());
+
+            int result = storeContractMapper.updateById(updateContract);
+            if (result > 0) {
+                return R.success("合同签署成功");
+            }
+            return R.fail("合同签署失败");
+        } catch (Exception e) {
+            log.error("StoreContractServiceImpl.signContract ERROR: {}", e.getMessage(), e);
+            return R.fail("合同签署失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<Boolean> deleteContract(Integer id) {
+        try {
+            log.info("StoreContractServiceImpl.deleteContract?id={}", id);
+
+            StoreContract contract = storeContractMapper.selectById(id);
+            if (contract == null) {
+                return R.fail("合同不存在");
+            }
+
+            // 逻辑删除
+            StoreContract updateContract = new StoreContract();
+            updateContract.setDeleteFlag(1);
+            updateContract.setUpdatedTime(new Date());
+
+            int result = storeContractMapper.updateById(updateContract);
+            if (result > 0) {
+                return R.success("删除合同成功");
+            }
+            return R.fail("删除合同失败");
+        } catch (Exception e) {
+            log.error("StoreContractServiceImpl.deleteContract ERROR: {}", e.getMessage(), e);
+            return R.fail("删除合同失败: " + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<String> getSigningStatus(Integer storeId) {
+        try {
+            log.info("StoreContractServiceImpl.getSigningStatus?storeId={}", storeId);
+
+            if (storeId == null) {
+                return R.fail("店铺ID不能为空");
+            }
+
+            // 根据店铺ID查询最新的合同(按创建时间倒序)
+            List<StoreContract> contracts = storeContractMapper.selectList(
+                    new LambdaQueryWrapper<StoreContract>()
+                            .eq(StoreContract::getStoreId, storeId.longValue())
+                            .eq(StoreContract::getDeleteFlag, 0)
+                            .orderByDesc(StoreContract::getCreatedTime)
+                            .last("LIMIT 1")
+            );
+
+            if (contracts == null || contracts.isEmpty()) {
+                return R.fail("该店铺暂无合同");
+            }
+
+            StoreContract contract = contracts.get(0);
+            String signingStatus = contract.getSigningStatus();
+            // 将字符串状态转换为数字:已签署=1, 未签署=0, 已到期=2
+            Integer statusValue = null;
+            if (signingStatus != null) {
+                switch (signingStatus) {
+                    case "已签署":
+                        statusValue = 1;
+                        break;
+                    case "未签署":
+                        statusValue = 0;
+                        break;
+                    case "已到期":
+                        statusValue = 2;
+                        break;
+                    default:
+                        statusValue = 0;
+                }
+            }
+            return R.data(signingStatus);
+        } catch (Exception e) {
+            log.error("StoreContractServiceImpl.getSigningStatus ERROR: {}", e.getMessage(), e);
+            return R.fail("获取签署状态失败: " + e.getMessage());
+        }
+    }
+}
+

+ 80 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/util/AiAuthTokenUtil.java

@@ -0,0 +1,80 @@
+package shop.alien.storeplatform.util;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * AI 服务鉴权配置
+ */
+@Slf4j
+@Component
+@RefreshScope
+@RequiredArgsConstructor
+public class AiAuthTokenUtil {
+
+    private final RestTemplate restTemplate;
+
+    @Value("${third-party-login.base-url}")
+    private String loginUrl;
+
+    @Value("${third-party-user-name.base-url}")
+    private String userName;
+
+    @Value("${third-party-pass-word.base-url}")
+    private String passWord;
+
+    /**
+     * 登录 AI 服务,获取 token
+     *
+     * @return accessToken
+     */
+    public String getAccessToken() {
+        log.info("登录Ai服务获取token...{}", loginUrl);
+        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("username", userName);
+        formData.add("password", passWord);
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
+
+        ResponseEntity<String> response;
+        try {
+            log.info("请求Ai服务登录接口===================>");
+            response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
+        } catch (Exception e) {
+            log.error("请求AI服务登录接口失败", e);
+            return null;
+        }
+
+        if (response != null && response.getStatusCode() == HttpStatus.OK) {
+            String body = response.getBody();
+            log.info("请求Ai服务登录成功 postForEntity.getBody()\t{}", body);
+            if (StringUtils.hasText(body)) {
+                JSONObject jsonObject = JSONObject.parseObject(body);
+                if (jsonObject != null) {
+                    JSONObject dataJson = jsonObject.getJSONObject("data");
+                    if (dataJson != null) {
+                        return dataJson.getString("access_token");
+                    }
+                }
+            }
+            log.warn("AI服务登录响应解析失败 body: {}", body);
+            return null;
+        }
+
+        log.error("请求AI服务 登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+        return null;
+    }
+}
+
+

+ 831 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/util/PlatformStatisticsComparisonImageUtil.java

@@ -0,0 +1,831 @@
+package shop.alien.storeplatform.util;
+
+import lombok.extern.slf4j.Slf4j;
+import shop.alien.entity.store.vo.StoreOperationalStatisticsComparisonVo;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.nio.file.Files;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 经营统计数据对比图片生成工具类
+ * 将 StoreOperationalStatisticsComparisonVo 数据转换为图片
+ *
+ * @author system
+ * @since 2026-01-05
+ */
+@Slf4j
+public class PlatformStatisticsComparisonImageUtil {
+
+    // 颜色定义
+    private static final Color BACKGROUND_COLOR = Color.WHITE;
+    private static final Color TEXT_COLOR = new Color(51, 51, 51); // #333333
+    private static final Color HEADER_BG_COLOR = new Color(245, 245, 245); // #F5F5F5
+    private static final Color POSITIVE_COLOR = new Color(76, 175, 80); // 绿色 #4CAF50
+    private static final Color NEGATIVE_COLOR = new Color(244, 67, 54); // 红色 #F44336
+    private static final Color SECTION_TITLE_COLOR = new Color(33, 33, 33); // #212121
+    private static final Color BORDER_COLOR = new Color(224, 224, 224); // #E0E0E0
+
+    // 字体:优先 classpath 内 fonts/ 下的字体文件(与 resources/fonts/ 对应),支持 .ttf / .otf
+    private static final String[] FONT_RESOURCE_PATHS = {
+            "fonts/NotoSansSC-Regular.ttf",
+            "fonts/NotoSansCJKsc-Regular.otf",
+            "/fonts/NotoSansSC-Regular.ttf",
+            "/fonts/NotoSansCJKsc-Regular.otf"
+    };
+    private static final String[] FONT_FALLBACK_NAMES = {
+            "Microsoft YaHei",
+            "PingFang SC",
+            "WenQuanYi Zen Hei",
+            "WenQuanYi Micro Hei",
+            "Noto Sans CJK SC",
+            "Noto Sans SC",
+            "SimSun",
+            "STSong",
+            "Source Han Sans SC",
+            "DengXian"
+    };
+    private static volatile Font baseChineseFont;   // 从 TTF 加载的基准字体
+    private static volatile String chineseFontName; // 系统字体名(当未加载 TTF 时)
+
+    private static final int TITLE_FONT_SIZE = 24;
+    private static final int SECTION_TITLE_FONT_SIZE = 18;
+    private static final int DATA_FONT_SIZE = 14;
+    private static final int LABEL_FONT_SIZE = 12;
+
+    // 尺寸定义
+    private static final int IMAGE_WIDTH = 800;
+    private static final int PADDING = 20;
+    private static final int SECTION_SPACING = 30;
+    private static final int ROW_HEIGHT = 35;
+    private static final int HEADER_HEIGHT = 50;
+
+    private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,##0.00");
+    private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("#,##0.00%");
+
+    /** 从 classpath 读取字体资源为字节数组(JAR 内更可靠) */
+    private static byte[] readFontBytes(String path) {
+        ClassLoader cl = PlatformStatisticsComparisonImageUtil.class.getClassLoader();
+        for (String p : new String[]{path, path.startsWith("/") ? path.substring(1) : "/" + path}) {
+            InputStream is = cl.getResourceAsStream(p);
+            if (is == null && Thread.currentThread().getContextClassLoader() != null) {
+                is = Thread.currentThread().getContextClassLoader().getResourceAsStream(p);
+            }
+            if (is == null) continue;
+            try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+                byte[] buf = new byte[8192];
+                int n;
+                while ((n = is.read(buf)) != -1) out.write(buf, 0, n);
+                return out.toByteArray();
+            } catch (Exception ignored) {
+            } finally {
+                try { is.close(); } catch (Exception ignored) {}
+            }
+        }
+        return null;
+    }
+
+    private static Font getChineseFont(int style, int size) {
+        if (baseChineseFont != null) {
+            return baseChineseFont.deriveFont(style, size);
+        }
+        if (chineseFontName != null) {
+            return new Font(chineseFontName, style, size);
+        }
+        synchronized (PlatformStatisticsComparisonImageUtil.class) {
+            if (baseChineseFont != null) return baseChineseFont.deriveFont(style, size);
+            if (chineseFontName != null) return new Font(chineseFontName, style, size);
+            for (String path : FONT_RESOURCE_PATHS) {
+                byte[] bytes = readFontBytes(path);
+                if (bytes == null || bytes.length == 0) continue;
+                try {
+                    baseChineseFont = Font.createFont(Font.TRUETYPE_FONT, new ByteArrayInputStream(bytes)).deriveFont(Font.PLAIN, 14f);
+                    return baseChineseFont.deriveFont(style, size);
+                } catch (Exception e1) {
+                    try {
+                        File tmp = File.createTempFile("noto_", path.endsWith(".otf") ? ".otf" : ".ttf");
+                        Files.write(tmp.toPath(), bytes);
+                        baseChineseFont = Font.createFont(Font.TRUETYPE_FONT, tmp).deriveFont(Font.PLAIN, 14f);
+                        tmp.delete();
+                        return baseChineseFont.deriveFont(style, size);
+                    } catch (Exception e2) {
+                        // skip
+                    }
+                }
+            }
+            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+            String[] names = ge.getAvailableFontFamilyNames();
+            if (names != null) {
+                for (String candidate : FONT_FALLBACK_NAMES) {
+                    for (String name : names) {
+                        if (!candidate.equals(name)) continue;
+                        Font f = new Font(name, Font.PLAIN, 14);
+                        if (f.canDisplayUpTo("经") >= 0) continue;
+                        chineseFontName = name;
+                        return new Font(chineseFontName, style, size);
+                    }
+                }
+                for (String name : names) {
+                    Font f = new Font(name, Font.PLAIN, 14);
+                    if (f.canDisplayUpTo("经") >= 0) continue;
+                    chineseFontName = name;
+                    return new Font(chineseFontName, style, size);
+                }
+            }
+            chineseFontName = Font.SANS_SERIF;
+            return new Font(chineseFontName, style, size);
+        }
+    }
+
+    /**
+     * 将统计数据对比转换为图片字节数组
+     *
+     * @param comparison 统计数据对比对象
+     * @return 图片字节数组
+     */
+    public static byte[] generateImage(StoreOperationalStatisticsComparisonVo comparison) {
+        if (comparison == null) {
+            throw new IllegalArgumentException("统计数据对比对象不能为空");
+        }
+
+        try {
+            // 计算图片高度
+            int totalHeight = calculateImageHeight(comparison);
+            
+            // 创建图片
+            BufferedImage image = new BufferedImage(IMAGE_WIDTH, totalHeight, BufferedImage.TYPE_INT_RGB);
+            Graphics2D g2d = image.createGraphics();
+            
+            // 设置抗锯齿
+            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+            
+            // 填充背景
+            g2d.setColor(BACKGROUND_COLOR);
+            g2d.fillRect(0, 0, IMAGE_WIDTH, totalHeight);
+            
+            int currentY = PADDING;
+            
+            // 绘制标题
+            currentY = drawTitle(g2d, currentY, comparison);
+            
+            // 绘制日期范围
+            currentY = drawDateRange(g2d, currentY, comparison);
+            
+            currentY += SECTION_SPACING;
+            
+            // 绘制流量数据
+            if (comparison.getTrafficData() != null) {
+                currentY = drawSection(g2d, currentY, "流量数据", 
+                    buildTrafficDataRows(comparison.getTrafficData()));
+            }
+            
+            // 绘制互动数据
+            if (comparison.getInteractionData() != null) {
+                currentY = drawSection(g2d, currentY, "互动数据", 
+                    buildInteractionDataRows(comparison.getInteractionData()));
+            }
+            
+            // 绘制优惠券数据
+            if (comparison.getCouponData() != null) {
+                currentY = drawSection(g2d, currentY, "优惠券", 
+                    buildCouponDataRows(comparison.getCouponData()));
+            }
+            
+            // 绘制代金券数据
+            if (comparison.getVoucherData() != null) {
+                currentY = drawSection(g2d, currentY, "代金券", 
+                    buildVoucherDataRows(comparison.getVoucherData()));
+            }
+            
+            // 绘制服务质量数据
+            if (comparison.getServiceQualityData() != null) {
+                currentY = drawSection(g2d, currentY, "服务质量", 
+                    buildServiceQualityDataRows(comparison.getServiceQualityData()));
+            }
+            
+            // 绘制价目表排名数据
+            if (comparison.getPriceListRanking() != null && !comparison.getPriceListRanking().isEmpty()) {
+                currentY = drawPriceListRankingSection(g2d, currentY, "价目表排名", 
+                    comparison.getPriceListRanking());
+            }
+            
+            g2d.dispose();
+            
+            // 转换为字节数组
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ImageIO.write(image, "PNG", baos);
+            return baos.toByteArray();
+            
+        } catch (Exception e) {
+            log.error("生成统计数据对比图片失败", e);
+            throw new RuntimeException("生成图片失败: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 计算图片高度
+     */
+    private static int calculateImageHeight(StoreOperationalStatisticsComparisonVo comparison) {
+        int height = PADDING * 2;
+        height += HEADER_HEIGHT; // 标题
+        height += 40; // 日期范围
+        height += SECTION_SPACING;
+        
+        int rowCount = 0;
+        if (comparison.getTrafficData() != null) {
+            rowCount += 6; // 流量数据行数(6个字段)
+        }
+        if (comparison.getInteractionData() != null) {
+            rowCount += 13; // 互动数据行数(13个字段)
+        }
+        if (comparison.getCouponData() != null) {
+            rowCount += 10; // 优惠券数据行数(10个字段)
+        }
+        if (comparison.getVoucherData() != null) {
+            rowCount += 10; // 代金券数据行数(10个字段)
+        }
+        if (comparison.getServiceQualityData() != null) {
+            rowCount += 12; // 服务质量数据行数(12个字段)
+        }
+        if (comparison.getPriceListRanking() != null && !comparison.getPriceListRanking().isEmpty()) {
+            // 价目表数据:每个价目表3个指标(浏览量、访客数、分享数)
+            rowCount += comparison.getPriceListRanking().size() * 3;
+        }
+        
+        height += rowCount * ROW_HEIGHT;
+        height += (rowCount / 6 + 4) * SECTION_SPACING; // 区块间距
+        
+        return height;
+    }
+
+    /**
+     * 绘制标题
+     */
+    private static int drawTitle(Graphics2D g2d, int y, StoreOperationalStatisticsComparisonVo comparison) {
+        Font titleFont = getChineseFont(Font.BOLD, TITLE_FONT_SIZE);
+        g2d.setFont(titleFont);
+        g2d.setColor(SECTION_TITLE_COLOR);
+        
+        String title = "经营数据";
+        FontMetrics fm = g2d.getFontMetrics();
+        int titleWidth = fm.stringWidth(title);
+        int titleX = (IMAGE_WIDTH - titleWidth) / 2;
+        
+        g2d.drawString(title, titleX, y + TITLE_FONT_SIZE);
+        return y + HEADER_HEIGHT;
+    }
+
+    /**
+     * 绘制日期范围
+     */
+    private static int drawDateRange(Graphics2D g2d, int y, StoreOperationalStatisticsComparisonVo comparison) {
+        Font dateFont = getChineseFont(Font.PLAIN, DATA_FONT_SIZE);
+        g2d.setFont(dateFont);
+        g2d.setColor(TEXT_COLOR);
+        
+        String currentDate = formatDate(comparison.getCurrentStartTime()) + "-" + formatDate(comparison.getCurrentEndTime());
+        String previousDate = formatDate(comparison.getPreviousStartTime()) + "-" + formatDate(comparison.getPreviousEndTime());
+        
+        int dateX = PADDING;
+        g2d.drawString(currentDate, dateX, y);
+        g2d.drawString(previousDate, dateX, y + 20);
+        
+        return y + 40;
+    }
+
+    /**
+     * 格式化日期
+     */
+    private static String formatDate(String date) {
+        if (date == null || date.length() < 10) {
+            return date;
+        }
+        // 将 yyyy-MM-dd 转换为 yyyy/MM/dd
+        return date.replace("-", "/");
+    }
+
+    /**
+     * 绘制数据区块
+     */
+    private static int drawSection(Graphics2D g2d, int y, String sectionTitle, List<DataRow> rows) {
+        if (rows == null || rows.isEmpty()) {
+            return y;
+        }
+        
+        // 绘制区块标题
+        Font sectionFont = getChineseFont(Font.BOLD, SECTION_TITLE_FONT_SIZE);
+        g2d.setFont(sectionFont);
+        g2d.setColor(SECTION_TITLE_COLOR);
+        g2d.drawString(sectionTitle, PADDING, y);
+        y += 30;
+
+        // 绘制表头
+        y = drawTableHeader(g2d, y);
+
+        // 绘制数据行
+        Font dataFont = getChineseFont(Font.PLAIN, DATA_FONT_SIZE);
+        Font labelFont = getChineseFont(Font.PLAIN, LABEL_FONT_SIZE);
+        
+        for (DataRow row : rows) {
+            y = drawDataRow(g2d, y, row, dataFont, labelFont);
+        }
+        
+        return y + SECTION_SPACING;
+    }
+
+    /**
+     * 绘制表头
+     */
+    private static int drawTableHeader(Graphics2D g2d, int y) {
+        // 绘制表头背景
+        g2d.setColor(HEADER_BG_COLOR);
+        g2d.fillRect(PADDING, y, IMAGE_WIDTH - PADDING * 2, ROW_HEIGHT);
+        
+        // 绘制表头文字
+        Font headerFont = getChineseFont(Font.BOLD, LABEL_FONT_SIZE);
+        g2d.setFont(headerFont);
+        g2d.setColor(TEXT_COLOR);
+        
+        int col1X = PADDING + 10;
+        int col2X = IMAGE_WIDTH / 2 - 80;
+        int col3X = IMAGE_WIDTH / 2 + 20;
+        int col4X = IMAGE_WIDTH - PADDING - 100;
+        
+        g2d.drawString("指标", col1X, y + 20);
+        g2d.drawString("当期", col2X, y + 20);
+        g2d.drawString("上期", col3X, y + 20);
+        g2d.drawString("变化率", col4X, y + 20);
+        
+        // 绘制边框
+        g2d.setColor(BORDER_COLOR);
+        g2d.drawRect(PADDING, y, IMAGE_WIDTH - PADDING * 2, ROW_HEIGHT);
+        
+        return y + ROW_HEIGHT;
+    }
+
+    /**
+     * 绘制数据行
+     */
+    private static int drawDataRow(Graphics2D g2d, int y, DataRow row, Font dataFont, Font labelFont) {
+        // 绘制行背景(交替颜色)
+        if ((y / ROW_HEIGHT) % 2 == 0) {
+            g2d.setColor(new Color(250, 250, 250));
+            g2d.fillRect(PADDING, y, IMAGE_WIDTH - PADDING * 2, ROW_HEIGHT);
+        }
+        
+        // 绘制指标名称
+        g2d.setFont(labelFont);
+        g2d.setColor(TEXT_COLOR);
+        g2d.drawString(row.getLabel(), PADDING + 10, y + 22);
+        
+        // 绘制当期值
+        g2d.setFont(dataFont);
+        String currentValue = formatValue(row.getCurrent());
+        g2d.drawString(currentValue, IMAGE_WIDTH / 2 - 80, y + 22);
+        
+        // 绘制上期值
+        String previousValue = formatValue(row.getPrevious());
+        g2d.drawString(previousValue, IMAGE_WIDTH / 2 + 20, y + 22);
+        
+        // 绘制变化率(带颜色)
+        String changeRate = formatChangeRate(row.getChangeRate());
+        Color changeColor = row.getChangeRate() != null && row.getChangeRate().compareTo(BigDecimal.ZERO) >= 0 
+            ? POSITIVE_COLOR : NEGATIVE_COLOR;
+        g2d.setColor(changeColor);
+        g2d.drawString(changeRate, IMAGE_WIDTH - PADDING - 100, y + 22);
+        
+        // 绘制边框
+        g2d.setColor(BORDER_COLOR);
+        g2d.drawLine(PADDING, y + ROW_HEIGHT, IMAGE_WIDTH - PADDING, y + ROW_HEIGHT);
+        
+        return y + ROW_HEIGHT;
+    }
+
+    /**
+     * 格式化数值
+     */
+    private static String formatValue(Object value) {
+        if (value == null) {
+            return "0";
+        }
+        if (value instanceof BigDecimal) {
+            BigDecimal bd = (BigDecimal) value;
+            if (bd.scale() > 0) {
+                return DECIMAL_FORMAT.format(bd);
+            } else {
+                return String.valueOf(bd.longValue());
+            }
+        }
+        if (value instanceof Number) {
+            return DECIMAL_FORMAT.format(((Number) value).doubleValue());
+        }
+        return String.valueOf(value);
+    }
+
+    /**
+     * 格式化变化率
+     */
+    private static String formatChangeRate(BigDecimal changeRate) {
+        if (changeRate == null) {
+            return "0.00%";
+        }
+        String sign = changeRate.compareTo(BigDecimal.ZERO) >= 0 ? "+" : "";
+        return sign + PERCENT_FORMAT.format(changeRate.divide(new BigDecimal(100), 4, BigDecimal.ROUND_HALF_UP));
+    }
+
+    /**
+     * 构建流量数据行(按照实体类字段顺序)
+     */
+    private static List<DataRow> buildTrafficDataRows(StoreOperationalStatisticsComparisonVo.TrafficDataComparison data) {
+        List<DataRow> rows = new ArrayList<>();
+        // 1. 店铺搜索量对比
+        if (data.getStoreSearchVolume() != null) {
+            rows.add(new DataRow("店铺搜索量对比", data.getStoreSearchVolume().getCurrent(),
+                data.getStoreSearchVolume().getPrevious(), data.getStoreSearchVolume().getChangeRate()));
+        }
+        // 2. 浏览量对比
+        if (data.getPageViews() != null) {
+            rows.add(new DataRow("浏览量对比", data.getPageViews().getCurrent(),
+                data.getPageViews().getPrevious(), data.getPageViews().getChangeRate()));
+        }
+        // 3. 访客数对比
+        if (data.getVisitors() != null) {
+            rows.add(new DataRow("访客数对比", data.getVisitors().getCurrent(),
+                data.getVisitors().getPrevious(), data.getVisitors().getChangeRate()));
+        }
+        // 4. 新增访客数对比
+        if (data.getNewVisitors() != null) {
+            rows.add(new DataRow("新增访客数对比", data.getNewVisitors().getCurrent(),
+                data.getNewVisitors().getPrevious(), data.getNewVisitors().getChangeRate()));
+        }
+        // 5. 访问时长对比
+        if (data.getVisitDuration() != null) {
+            rows.add(new DataRow("访问时长对比", data.getVisitDuration().getCurrent(),
+                data.getVisitDuration().getPrevious(), data.getVisitDuration().getChangeRate()));
+        }
+        // 6. 平均访问时长对比
+        if (data.getAvgVisitDuration() != null) {
+            rows.add(new DataRow("平均访问时长对比", data.getAvgVisitDuration().getCurrent(),
+                data.getAvgVisitDuration().getPrevious(), data.getAvgVisitDuration().getChangeRate()));
+        }
+        return rows;
+    }
+
+    /**
+     * 构建互动数据行(按照实体类字段顺序)
+     */
+    private static List<DataRow> buildInteractionDataRows(StoreOperationalStatisticsComparisonVo.InteractionDataComparison data) {
+        List<DataRow> rows = new ArrayList<>();
+        // 1. 店铺收藏次数对比
+        if (data.getStoreCollectionCount() != null) {
+            rows.add(new DataRow("店铺收藏次数对比", data.getStoreCollectionCount().getCurrent(),
+                data.getStoreCollectionCount().getPrevious(), data.getStoreCollectionCount().getChangeRate()));
+        }
+        // 2. 店铺分享次数对比
+        if (data.getStoreShareCount() != null) {
+            rows.add(new DataRow("店铺分享次数对比", data.getStoreShareCount().getCurrent(),
+                data.getStoreShareCount().getPrevious(), data.getStoreShareCount().getChangeRate()));
+        }
+        // 3. 店铺打卡次数对比
+        if (data.getStoreCheckInCount() != null) {
+            rows.add(new DataRow("店铺打卡次数对比", data.getStoreCheckInCount().getCurrent(),
+                data.getStoreCheckInCount().getPrevious(), data.getStoreCheckInCount().getChangeRate()));
+        }
+        // 4. 咨询商家次数对比
+        if (data.getConsultMerchantCount() != null) {
+            rows.add(new DataRow("咨询商家次数对比", data.getConsultMerchantCount().getCurrent(),
+                data.getConsultMerchantCount().getPrevious(), data.getConsultMerchantCount().getChangeRate()));
+        }
+        // 5. 好友数量对比
+        if (data.getFriendsCount() != null) {
+            rows.add(new DataRow("好友数量对比", data.getFriendsCount().getCurrent(),
+                data.getFriendsCount().getPrevious(), data.getFriendsCount().getChangeRate()));
+        }
+        // 6. 关注数量对比
+        if (data.getFollowCount() != null) {
+            rows.add(new DataRow("关注数量对比", data.getFollowCount().getCurrent(),
+                data.getFollowCount().getPrevious(), data.getFollowCount().getChangeRate()));
+        }
+        // 7. 粉丝数量对比
+        if (data.getFansCount() != null) {
+            rows.add(new DataRow("粉丝数量对比", data.getFansCount().getCurrent(),
+                data.getFansCount().getPrevious(), data.getFansCount().getChangeRate()));
+        }
+        // 8. 发布动态数量对比
+        if (data.getPostsPublishedCount() != null) {
+            rows.add(new DataRow("发布动态数量对比", data.getPostsPublishedCount().getCurrent(),
+                data.getPostsPublishedCount().getPrevious(), data.getPostsPublishedCount().getChangeRate()));
+        }
+        // 9. 动态点赞数量对比
+        if (data.getPostLikesCount() != null) {
+            rows.add(new DataRow("动态点赞数量对比", data.getPostLikesCount().getCurrent(),
+                data.getPostLikesCount().getPrevious(), data.getPostLikesCount().getChangeRate()));
+        }
+        // 10. 动态评论数量对比
+        if (data.getPostCommentsCount() != null) {
+            rows.add(new DataRow("动态评论数量对比", data.getPostCommentsCount().getCurrent(),
+                data.getPostCommentsCount().getPrevious(), data.getPostCommentsCount().getChangeRate()));
+        }
+        // 11. 动态转发数量对比
+        if (data.getPostSharesCount() != null) {
+            rows.add(new DataRow("动态转发数量对比", data.getPostSharesCount().getCurrent(),
+                data.getPostSharesCount().getPrevious(), data.getPostSharesCount().getChangeRate()));
+        }
+        // 12. 被举报次数对比
+        if (data.getReportedCount() != null) {
+            rows.add(new DataRow("被举报次数对比", data.getReportedCount().getCurrent(),
+                data.getReportedCount().getPrevious(), data.getReportedCount().getChangeRate()));
+        }
+        // 13. 被拉黑次数对比
+        if (data.getBlockedCount() != null) {
+            rows.add(new DataRow("被拉黑次数对比", data.getBlockedCount().getCurrent(),
+                data.getBlockedCount().getPrevious(), data.getBlockedCount().getChangeRate()));
+        }
+        return rows;
+    }
+
+    /**
+     * 构建优惠券数据行(按照实体类字段顺序)
+     */
+    private static List<DataRow> buildCouponDataRows(StoreOperationalStatisticsComparisonVo.CouponDataComparison data) {
+        List<DataRow> rows = new ArrayList<>();
+        // 1. 赠送好友数量对比
+        if (data.getGiftToFriendsCount() != null) {
+            rows.add(new DataRow("赠送好友数量对比", data.getGiftToFriendsCount().getCurrent(), 
+                data.getGiftToFriendsCount().getPrevious(), data.getGiftToFriendsCount().getChangeRate()));
+        }
+        // 2. 赠送好友金额合计对比
+        if (data.getGiftToFriendsAmount() != null) {
+            rows.add(new DataRow("赠送好友金额合计对比", data.getGiftToFriendsAmount().getCurrent(), 
+                data.getGiftToFriendsAmount().getPrevious(), data.getGiftToFriendsAmount().getChangeRate()));
+        }
+        // 3. 赠送好友使用数量对比
+        if (data.getGiftToFriendsUsedCount() != null) {
+            rows.add(new DataRow("赠送好友使用数量对比", data.getGiftToFriendsUsedCount().getCurrent(), 
+                data.getGiftToFriendsUsedCount().getPrevious(), data.getGiftToFriendsUsedCount().getChangeRate()));
+        }
+        // 4. 赠送好友使用金额合计对比
+        if (data.getGiftToFriendsUsedAmount() != null) {
+            rows.add(new DataRow("赠送好友使用金额合计对比", data.getGiftToFriendsUsedAmount().getCurrent(), 
+                data.getGiftToFriendsUsedAmount().getPrevious(), data.getGiftToFriendsUsedAmount().getChangeRate()));
+        }
+        // 5. 赠送好友使用金额占比对比
+        if (data.getGiftToFriendsUsedAmountRatio() != null) {
+            rows.add(new DataRow("赠送好友使用金额占比对比", data.getGiftToFriendsUsedAmountRatio().getCurrent(), 
+                data.getGiftToFriendsUsedAmountRatio().getPrevious(), data.getGiftToFriendsUsedAmountRatio().getChangeRate()));
+        }
+        // 6. 好友赠送数量对比
+        if (data.getFriendsGiftCount() != null) {
+            rows.add(new DataRow("好友赠送数量对比", data.getFriendsGiftCount().getCurrent(), 
+                data.getFriendsGiftCount().getPrevious(), data.getFriendsGiftCount().getChangeRate()));
+        }
+        // 7. 好友赠送金额合计对比
+        if (data.getFriendsGiftAmount() != null) {
+            rows.add(new DataRow("好友赠送金额合计对比", data.getFriendsGiftAmount().getCurrent(), 
+                data.getFriendsGiftAmount().getPrevious(), data.getFriendsGiftAmount().getChangeRate()));
+        }
+        // 8. 好友赠送使用数量对比
+        if (data.getFriendsGiftUsedCount() != null) {
+            rows.add(new DataRow("好友赠送使用数量对比", data.getFriendsGiftUsedCount().getCurrent(), 
+                data.getFriendsGiftUsedCount().getPrevious(), data.getFriendsGiftUsedCount().getChangeRate()));
+        }
+        // 9. 好友赠送使用金额合计对比
+        if (data.getFriendsGiftUsedAmount() != null) {
+            rows.add(new DataRow("好友赠送使用金额合计对比", data.getFriendsGiftUsedAmount().getCurrent(), 
+                data.getFriendsGiftUsedAmount().getPrevious(), data.getFriendsGiftUsedAmount().getChangeRate()));
+        }
+        // 10. 好友赠送使用金额占比对比
+        if (data.getFriendsGiftUsedAmountRatio() != null) {
+            rows.add(new DataRow("好友赠送使用金额占比对比", data.getFriendsGiftUsedAmountRatio().getCurrent(), 
+                data.getFriendsGiftUsedAmountRatio().getPrevious(), data.getFriendsGiftUsedAmountRatio().getChangeRate()));
+        }
+        return rows;
+    }
+
+    /**
+     * 构建代金券数据行(按照实体类字段顺序)
+     */
+    private static List<DataRow> buildVoucherDataRows(StoreOperationalStatisticsComparisonVo.VoucherDataComparison data) {
+        List<DataRow> rows = new ArrayList<>();
+        // 1. 赠送好友数量对比
+        if (data.getGiftToFriendsCount() != null) {
+            rows.add(new DataRow("赠送好友数量对比", data.getGiftToFriendsCount().getCurrent(), 
+                data.getGiftToFriendsCount().getPrevious(), data.getGiftToFriendsCount().getChangeRate()));
+        }
+        // 2. 赠送好友金额合计对比
+        if (data.getGiftToFriendsAmount() != null) {
+            rows.add(new DataRow("赠送好友金额合计对比", data.getGiftToFriendsAmount().getCurrent(), 
+                data.getGiftToFriendsAmount().getPrevious(), data.getGiftToFriendsAmount().getChangeRate()));
+        }
+        // 3. 赠送好友使用数量对比
+        if (data.getGiftToFriendsUsedCount() != null) {
+            rows.add(new DataRow("赠送好友使用数量对比", data.getGiftToFriendsUsedCount().getCurrent(), 
+                data.getGiftToFriendsUsedCount().getPrevious(), data.getGiftToFriendsUsedCount().getChangeRate()));
+        }
+        // 4. 赠送好友使用金额合计对比
+        if (data.getGiftToFriendsUsedAmount() != null) {
+            rows.add(new DataRow("赠送好友使用金额合计对比", data.getGiftToFriendsUsedAmount().getCurrent(), 
+                data.getGiftToFriendsUsedAmount().getPrevious(), data.getGiftToFriendsUsedAmount().getChangeRate()));
+        }
+        // 5. 赠送好友使用金额占比对比
+        if (data.getGiftToFriendsUsedAmountRatio() != null) {
+            rows.add(new DataRow("赠送好友使用金额占比对比", data.getGiftToFriendsUsedAmountRatio().getCurrent(), 
+                data.getGiftToFriendsUsedAmountRatio().getPrevious(), data.getGiftToFriendsUsedAmountRatio().getChangeRate()));
+        }
+        // 6. 好友赠送数量对比
+        if (data.getFriendsGiftCount() != null) {
+            rows.add(new DataRow("好友赠送数量对比", data.getFriendsGiftCount().getCurrent(), 
+                data.getFriendsGiftCount().getPrevious(), data.getFriendsGiftCount().getChangeRate()));
+        }
+        // 7. 好友赠送金额合计对比
+        if (data.getFriendsGiftAmount() != null) {
+            rows.add(new DataRow("好友赠送金额合计对比", data.getFriendsGiftAmount().getCurrent(), 
+                data.getFriendsGiftAmount().getPrevious(), data.getFriendsGiftAmount().getChangeRate()));
+        }
+        // 8. 好友赠送使用数量对比
+        if (data.getFriendsGiftUsedCount() != null) {
+            rows.add(new DataRow("好友赠送使用数量对比", data.getFriendsGiftUsedCount().getCurrent(), 
+                data.getFriendsGiftUsedCount().getPrevious(), data.getFriendsGiftUsedCount().getChangeRate()));
+        }
+        // 9. 好友赠送使用金额合计对比
+        if (data.getFriendsGiftUsedAmount() != null) {
+            rows.add(new DataRow("好友赠送使用金额合计对比", data.getFriendsGiftUsedAmount().getCurrent(), 
+                data.getFriendsGiftUsedAmount().getPrevious(), data.getFriendsGiftUsedAmount().getChangeRate()));
+        }
+        // 10. 好友赠送使用金额占比对比
+        if (data.getFriendsGiftUsedAmountRatio() != null) {
+            rows.add(new DataRow("好友赠送使用金额占比对比", data.getFriendsGiftUsedAmountRatio().getCurrent(), 
+                data.getFriendsGiftUsedAmountRatio().getPrevious(), data.getFriendsGiftUsedAmountRatio().getChangeRate()));
+        }
+        return rows;
+    }
+
+    /**
+     * 构建服务质量数据行(按照实体类字段顺序)
+     */
+    private static List<DataRow> buildServiceQualityDataRows(StoreOperationalStatisticsComparisonVo.ServiceQualityDataComparison data) {
+        List<DataRow> rows = new ArrayList<>();
+        // 1. 店铺评分对比
+        if (data.getStoreRating() != null) {
+            rows.add(new DataRow("店铺评分对比", data.getStoreRating().getCurrent(), 
+                data.getStoreRating().getPrevious(), data.getStoreRating().getChangeRate()));
+        }
+        // 2. 评分1对比
+        if (data.getScoreOne() != null) {
+            rows.add(new DataRow("评分1对比", data.getScoreOne().getCurrent(), 
+                data.getScoreOne().getPrevious(), data.getScoreOne().getChangeRate()));
+        }
+        // 3. 评分2对比
+        if (data.getScoreTwo() != null) {
+            rows.add(new DataRow("评分2对比", data.getScoreTwo().getCurrent(), 
+                data.getScoreTwo().getPrevious(), data.getScoreTwo().getChangeRate()));
+        }
+        // 4. 评分3对比
+        if (data.getScoreThree() != null) {
+            rows.add(new DataRow("评分3对比", data.getScoreThree().getCurrent(), 
+                data.getScoreThree().getPrevious(), data.getScoreThree().getChangeRate()));
+        }
+        // 5. 评价数量对比
+        if (data.getTotalReviews() != null) {
+            rows.add(new DataRow("评价数量对比", data.getTotalReviews().getCurrent(), 
+                data.getTotalReviews().getPrevious(), data.getTotalReviews().getChangeRate()));
+        }
+        // 6. 好评数量对比
+        if (data.getPositiveReviews() != null) {
+            rows.add(new DataRow("好评数量对比", data.getPositiveReviews().getCurrent(), 
+                data.getPositiveReviews().getPrevious(), data.getPositiveReviews().getChangeRate()));
+        }
+        // 7. 中评数量对比
+        if (data.getNeutralReviews() != null) {
+            rows.add(new DataRow("中评数量对比", data.getNeutralReviews().getCurrent(), 
+                data.getNeutralReviews().getPrevious(), data.getNeutralReviews().getChangeRate()));
+        }
+        // 8. 差评数量对比
+        if (data.getNegativeReviews() != null) {
+            rows.add(new DataRow("差评数量对比", data.getNegativeReviews().getCurrent(), 
+                data.getNegativeReviews().getPrevious(), data.getNegativeReviews().getChangeRate()));
+        }
+        // 9. 差评占比对比
+        if (data.getNegativeReviewRatio() != null) {
+            rows.add(new DataRow("差评占比对比", data.getNegativeReviewRatio().getCurrent(), 
+                data.getNegativeReviewRatio().getPrevious(), data.getNegativeReviewRatio().getChangeRate()));
+        }
+        // 10. 差评申诉次数对比
+        if (data.getNegativeReviewAppealsCount() != null) {
+            rows.add(new DataRow("差评申诉次数对比", data.getNegativeReviewAppealsCount().getCurrent(), 
+                data.getNegativeReviewAppealsCount().getPrevious(), data.getNegativeReviewAppealsCount().getChangeRate()));
+        }
+        // 11. 差评申诉成功次数对比
+        if (data.getNegativeReviewAppealsSuccessCount() != null) {
+            rows.add(new DataRow("差评申诉成功次数对比", data.getNegativeReviewAppealsSuccessCount().getCurrent(), 
+                data.getNegativeReviewAppealsSuccessCount().getPrevious(), data.getNegativeReviewAppealsSuccessCount().getChangeRate()));
+        }
+        // 12. 差评申诉成功占比对比
+        if (data.getNegativeReviewAppealsSuccessRatio() != null) {
+            rows.add(new DataRow("差评申诉成功占比对比", data.getNegativeReviewAppealsSuccessRatio().getCurrent(), 
+                data.getNegativeReviewAppealsSuccessRatio().getPrevious(), data.getNegativeReviewAppealsSuccessRatio().getChangeRate()));
+        }
+        return rows;
+    }
+
+    /**
+     * 绘制价目表排名数据区块(特殊格式,每个价目表显示3个指标)
+     */
+    private static int drawPriceListRankingSection(Graphics2D g2d, int y, String sectionTitle, 
+                                                   List<StoreOperationalStatisticsComparisonVo.PriceListRankingComparison> rankings) {
+        if (rankings == null || rankings.isEmpty()) {
+            return y;
+        }
+        
+        // 绘制区块标题
+        Font sectionFont = getChineseFont(Font.BOLD, SECTION_TITLE_FONT_SIZE);
+        g2d.setFont(sectionFont);
+        g2d.setColor(SECTION_TITLE_COLOR);
+        g2d.drawString(sectionTitle, PADDING, y);
+        y += 30;
+
+        Font dataFont = getChineseFont(Font.PLAIN, DATA_FONT_SIZE);
+        Font labelFont = getChineseFont(Font.PLAIN, LABEL_FONT_SIZE);
+
+        // 遍历每个价目表
+        for (StoreOperationalStatisticsComparisonVo.PriceListRankingComparison ranking : rankings) {
+            // 绘制价目表名称(作为子标题)
+            g2d.setFont(getChineseFont(Font.BOLD, LABEL_FONT_SIZE));
+            g2d.setColor(new Color(66, 66, 66));
+            String priceListName = ranking.getPriceListItemName() != null ? ranking.getPriceListItemName() : 
+                ("价目表ID: " + ranking.getPriceId());
+            g2d.drawString(priceListName, PADDING + 20, y);
+            y += 25;
+            
+            // 绘制表头
+            y = drawTableHeader(g2d, y);
+            
+            // 绘制该价目表的3个指标
+            List<DataRow> rows = new ArrayList<>();
+            if (ranking.getPageViews() != null) {
+                rows.add(new DataRow("浏览量", ranking.getPageViews().getCurrent(), 
+                    ranking.getPageViews().getPrevious(), ranking.getPageViews().getChangeRate()));
+            }
+            if (ranking.getVisitors() != null) {
+                rows.add(new DataRow("访客", ranking.getVisitors().getCurrent(), 
+                    ranking.getVisitors().getPrevious(), ranking.getVisitors().getChangeRate()));
+            }
+            if (ranking.getShares() != null) {
+                rows.add(new DataRow("分享数", ranking.getShares().getCurrent(), 
+                    ranking.getShares().getPrevious(), ranking.getShares().getChangeRate()));
+            }
+            
+            // 绘制数据行
+            for (DataRow row : rows) {
+                y = drawDataRow(g2d, y, row, dataFont, labelFont);
+            }
+            
+            y += SECTION_SPACING / 2; // 价目表之间的间距
+        }
+        
+        return y + SECTION_SPACING;
+    }
+
+    /**
+     * 数据行内部类
+     */
+    private static class DataRow {
+        private String label;
+        private Object current;
+        private Object previous;
+        private BigDecimal changeRate;
+
+        public DataRow(String label, Object current, Object previous, BigDecimal changeRate) {
+            this.label = label;
+            this.current = current;
+            this.previous = previous;
+            this.changeRate = changeRate;
+        }
+
+        public String getLabel() {
+            return label;
+        }
+
+        public Object getCurrent() {
+            return current;
+        }
+
+        public Object getPrevious() {
+            return previous;
+        }
+
+        public BigDecimal getChangeRate() {
+            return changeRate;
+        }
+    }
+}

BIN
alien-store-platform/src/main/resources/fonts/NotoSansCJKsc-Regular.otf


+ 78 - 0
alien-store/src/main/java/shop/alien/store/controller/AiUserSessionController.java

@@ -0,0 +1,78 @@
+package shop.alien.store.controller;
+
+import com.alibaba.fastjson2.JSONObject;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.store.util.ai.AiUserSessionUtil;
+
+import java.util.Map;
+
+/**
+ * AI用户会话管理控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"AI用户会话管理"})
+@CrossOrigin
+@RestController
+@RequestMapping("/aiUserSession")
+@RequiredArgsConstructor
+public class AiUserSessionController {
+
+    private final AiUserSessionUtil aiUserSessionUtil;
+
+    @ApiOperation("获取用户会话列表")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/getUserSessions")
+    public R<JSONObject> getUserSessions(@RequestBody Map<String, Object> request) {
+        log.info("AiUserSessionController.getUserSessions: request={}", request);
+        try {
+            if (request == null || !request.containsKey("user_id")) {
+                return R.fail("用户ID不能为空");
+            }
+
+            Object userIdObj = request.get("user_id");
+            if (userIdObj == null) {
+                return R.fail("用户ID不能为空");
+            }
+
+            Integer userId;
+            if (userIdObj instanceof Integer) {
+                userId = (Integer) userIdObj;
+            } else if (userIdObj instanceof Number) {
+                userId = ((Number) userIdObj).intValue();
+            } else {
+                try {
+                    userId = Integer.parseInt(userIdObj.toString());
+                } catch (NumberFormatException e) {
+                    return R.fail("用户ID格式错误");
+                }
+            }
+
+            if (userId <= 0) {
+                return R.fail("用户ID必须大于0");
+            }
+
+            JSONObject result = aiUserSessionUtil.getUserSessions(userId);
+            if (result != null) {
+                Boolean success = result.getBoolean("success");
+                if (Boolean.TRUE.equals(success)) {
+                    return R.data(result);
+                } else {
+                    String message = result.getString("message");
+                    return R.fail(message != null ? message : "获取用户会话列表失败");
+                }
+            }
+            return R.fail("获取用户会话列表失败");
+        } catch (Exception e) {
+            log.error("AiUserSessionController.getUserSessions ERROR: {}", e.getMessage(), e);
+            return R.fail("获取用户会话列表异常:" + e.getMessage());
+        }
+    }
+}
+

+ 13 - 15
alien-store/src/main/java/shop/alien/store/controller/AliController.java

@@ -80,22 +80,20 @@ public class AliController {
     })
     @GetMapping("/getIdInfo")
     public R getIdInfo(@RequestParam("name") String name, @RequestParam("idCard") String idCard, @RequestParam("appType") Integer appType) {
-        log.info("AliController.getIdInfo?name={}&idCard={}", name, idCard);
+        String normalizedName = name == null ? null : name.trim();
+        String normalizedIdCard = idCard == null ? null : idCard.trim().toUpperCase();
+        log.info("AliController.getIdInfo?name={}&idCard={}", normalizedName, normalizedIdCard);
         int size = 0;
         if (appType == 0) {
-            //根据身份查询未注销的用户
-            size = lifeUserService
-                    .list(new LambdaQueryWrapper<LifeUser>()
-                            .eq(LifeUser::getIdCard, idCard)
-                            .eq(LifeUser::getRealName, name)
-                            .eq(LifeUser::getLogoutFlag, 0))
-                    .size();
+            // 根据身份证查询未注销用户:同一身份证只能实名一个账号
+            size = lifeUserService.count(new LambdaQueryWrapper<LifeUser>()
+                    .eq(LifeUser::getIdCard, normalizedIdCard)
+                    .eq(LifeUser::getLogoutFlag, 0));
         } else {
-            //根据身份查询已入驻或审核中的商家
+            // 根据身份证查询已入驻或审核中的商家:同一身份证只能实名一个账号
             List<StoreUser> storeUserList = storeUserService
                     .list(new LambdaQueryWrapper<StoreUser>()
-                            .eq(StoreUser::getIdCard, idCard)
-                            .eq(StoreUser::getName, name));
+                            .eq(StoreUser::getIdCard, normalizedIdCard));
             List<Integer> storeIds = storeUserList.stream()
                     .map(StoreUser::getStoreId)
                     .filter(Objects::nonNull)
@@ -108,10 +106,10 @@ public class AliController {
                         .size();
             }
         }
-//        if (size > 0) {
-//            return R.fail("该身份证已实名认证过");
-//        }
-        if (aliPayConfig.getIdInfo(name, idCard)) {
+        if (size > 0) {
+            return R.fail("该身份证已实名认证过");
+        }
+        if (aliPayConfig.getIdInfo(normalizedName, normalizedIdCard)) {
             return R.success("身份验证成功");
         }
 //        Map map = new HashMap();

+ 48 - 7
alien-store/src/main/java/shop/alien/store/controller/BarPerformanceController.java

@@ -12,6 +12,9 @@ import shop.alien.entity.store.dto.BarPerformanceOnlineStatusDto;
 import shop.alien.entity.store.vo.BarPerformanceDetailVo;
 import shop.alien.store.service.BarPerformanceService;
 
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
 /**
  * 酒吧演出Controller
  * 提供酒吧演出的CRUD、审核、上下线等RESTful API接口
@@ -174,9 +177,14 @@ public class BarPerformanceController {
      * @param page    分页页数
      * @param size    分页条数
      * @param storeId 门店ID
-     * @param statusReview 审核状态(0-待审核 1-审核通过 2-审核拒绝)
+     * @param statusReview 审核状态(0-待审核 1-审核通过 2-审核拒绝)(已废弃,请使用reviewStatus)
      * @param category 演出分类(all-全部, not_started-未开始, in_progress-进行中, ended-已结束)
      * @param performanceName 演出名称(可选,支持模糊搜索)
+     * @param performanceType 演出类型(0-特邀演出,1-常规演出)
+     * @param onlineStatus 上线状态(0-下线,1-上线)
+     * @param reviewStatus 审核状态(0-待审核 1-审核通过 2-审核拒绝)
+     * @param startCreatedTime 创建时间开始(yyyy-MM-dd HH:mm:ss)
+     * @param endCreatedTime 创建时间结束(yyyy-MM-dd HH:mm:ss)
      * @return 演出列表
      */
     @ApiOperation("根据门店ID查询演出列表(支持按审核状态、分类和名称搜索)")
@@ -184,9 +192,14 @@ public class BarPerformanceController {
             @ApiImplicitParam(name = "page", value = "分页页数", dataType = "Integer", paramType = "query", required = true),
             @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", paramType = "query", required = true),
             @ApiImplicitParam(name = "storeId", value = "门店ID", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "statusReview", value = "审核状态(0-待审核 1-审核通过 2-审核拒绝)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "statusReview", value = "审核状态(0-待审核 1-审核通过 2-审核拒绝)(已废弃,请使用reviewStatus)", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "category", value = "演出分类(all-全部, not_started-未开始, in_progress-进行中, ended-已结束)", dataType = "String", paramType = "query", required = false),
-            @ApiImplicitParam(name = "performanceName", value = "演出名称(支持模糊搜索)", dataType = "String", paramType = "query", required = false)
+            @ApiImplicitParam(name = "performanceName", value = "演出名称(支持模糊搜索)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "performanceType", value = "演出类型(0-特邀演出,1-常规演出)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "onlineStatus", value = "上线状态(0-下线,1-上线)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "reviewStatus", value = "审核状态(0-待审核 1-审核通过 2-审核拒绝)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "startCreatedTime", value = "创建时间开始(yyyy-MM-dd HH:mm:ss)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "endCreatedTime", value = "创建时间结束(yyyy-MM-dd HH:mm:ss)", dataType = "String", paramType = "query", required = false)
     })
     @GetMapping("/listByStoreId")
     public R<IPage<BarPerformance>> queryPerformanceListByStoreId(
@@ -195,12 +208,40 @@ public class BarPerformanceController {
             @RequestParam Integer storeId,
             @RequestParam(required = false) Integer statusReview,
             @RequestParam(required = false) String category,
-            @RequestParam(required = false) String performanceName) {
-        log.info("BarPerformanceController.queryPerformanceListByStoreId?page={}, size={}, storeId={}, statusReview={}, category={}, performanceName={}",
-                page, size, storeId, statusReview, category, performanceName);
+            @RequestParam(required = false) String performanceName,
+            @RequestParam(required = false) Integer performanceType,
+            @RequestParam(required = false) Integer onlineStatus,
+            @RequestParam(required = false) Integer reviewStatus,
+            @RequestParam(required = false) String startCreatedTime,
+            @RequestParam(required = false) String endCreatedTime) {
+        // 兼容旧参数:如果reviewStatus为空但statusReview不为空,使用statusReview
+        Integer finalReviewStatus = reviewStatus != null ? reviewStatus : statusReview;
+        
+        // 解析时间参数
+        Date startTime = null;
+        Date endTime = null;
+        if (startCreatedTime != null && !startCreatedTime.isEmpty()) {
+            try {
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                startTime = sdf.parse(startCreatedTime);
+            } catch (Exception e) {
+                log.warn("解析开始时间失败: {}", startCreatedTime, e);
+            }
+        }
+        if (endCreatedTime != null && !endCreatedTime.isEmpty()) {
+            try {
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                endTime = sdf.parse(endCreatedTime);
+            } catch (Exception e) {
+                log.warn("解析结束时间失败: {}", endCreatedTime, e);
+            }
+        }
+        
+        log.info("BarPerformanceController.queryPerformanceListByStoreId?page={}, size={}, storeId={}, statusReview={}, category={}, performanceName={}, performanceType={}, onlineStatus={}, reviewStatus={}, startCreatedTime={}, endCreatedTime={}",
+                page, size, storeId, statusReview, category, performanceName, performanceType, onlineStatus, reviewStatus, startCreatedTime, endCreatedTime);
         try {
             IPage<BarPerformance> performanceList = barPerformanceService.queryPerformanceListByStoreIdAndCategory(
-                    page, size, storeId, statusReview, category != null ? category : "all", performanceName);
+                    page, size, storeId, finalReviewStatus, category != null ? category : "all", performanceName, performanceType, onlineStatus, startTime, endTime);
             return R.data(performanceList);
         } catch (Exception e) {
             log.error("根据门店ID查询演出列表失败", e);

+ 68 - 17
alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponController.java

@@ -183,15 +183,17 @@ public class LifeDiscountCouponController {
     @GetMapping("/getUserCouponList")
     @ApiImplicitParams({@ApiImplicitParam(name = "page", value = "分页页数", dataType = "String", paramType = "query", required = true),
             @ApiImplicitParam(name = "size", value = "分页条数", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "tabType", value = "分页类型(0:全部(未使用),1:即将过期,2:已使用,3:已过期)", dataType = "String", paramType = "query", required = true)
+            @ApiImplicitParam(name = "tabType", value = "分页类型(0:全部(未使用),1:即将过期,2:已使用,3:已过期)", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "type", value = "券类型(不传:优惠券+代金券都返回,1:仅优惠券查 life_discount_coupon,4:仅代金券查 life_coupon)", dataType = "Integer", paramType = "query", required = false)
     })
     public R<List<LifeDiscountCouponVo>> getUserCouponList(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
                                                            @RequestParam(value = "tabType") String tabType,
                                                            @RequestParam(defaultValue = "1") int page,
-                                                           @RequestParam(defaultValue = "10") int size) {
+                                                           @RequestParam(defaultValue = "10") int size,
+                                                           @RequestParam(required = false) Integer type) {
         log.info("LifeDiscountCouponController.getUserCouponList");
         try {
-            List<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getUserCouponList(userLoginInfo, page, size, tabType);
+            List<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getUserCouponList(userLoginInfo, page, size, tabType, type);
             return R.data(storeCouponList);
         } catch (Exception e) {
             log.error("LifeDiscountCouponController.getUserCouponList ERROR Msg={}", e.getMessage());
@@ -229,20 +231,20 @@ public class LifeDiscountCouponController {
         }
     }
 
-    @ApiOperation("获取该店铺所有优惠券(不分页)")
+    @ApiOperation("获取该店铺所有优惠券 代金券(不分页)")
     @ApiOperationSupport(order = 11)
     @GetMapping("/getStoreAllCouponListPaginateNot")
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "storeId", value = "商户id", dataType = "String", paramType = "query", required = true)
+            @ApiImplicitParam(name = "storeId", value = "商户id", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "type", value = "类型:1-优惠券(默认),4-代金券", dataType = "Integer", paramType = "query")
     })
     public R<List<LifeDiscountCouponVo>> getStoreAllCouponListPaginateNot(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
-                                                                          @RequestParam(value = "storeId") String storeId,
-                                                                          @RequestParam(value = "status", required = false) String status,
-                                                                          @RequestParam(value = "couponStatus", defaultValue = "1", required = false) int couponStatus
-    ) {
-        log.info("LifeDiscountCouponController.getStoreAllCouponListPaginateNot?storeId={},status={}, couponStatus={}", storeId, status, couponStatus);
+                                                                          @RequestParam(value = "storeId") String storeId,@RequestParam(value = "status", required = false) String status,
+                                                                          @RequestParam(value = "couponStatus", defaultValue = "1", required = false) int couponStatus,
+                                                                          @RequestParam(value = "type", defaultValue = "1", required = false) Integer type) {
+        log.info("LifeDiscountCouponController.getStoreAllCouponListPaginateNot?storeId={}, status={}, couponStatus={}, type={}", storeId, status, couponStatus, type);
         try {
-            List<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getStoreAllCouponListPaginateNot(status, storeId, userLoginInfo, couponStatus);
+            List<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getStoreAllCouponListPaginateNot(status, storeId, userLoginInfo, couponStatus, type);
             return R.data(storeCouponList);
         } catch (Exception e) {
             log.error("LifeDiscountCouponController.getStoreAllCouponListPaginateNot ERROR Msg={}", e.getMessage());
@@ -250,17 +252,22 @@ public class LifeDiscountCouponController {
         }
     }
 
-    @ApiOperation("获取该优惠券使用规则")
+    @ApiOperation("获取该优惠券/代金券使用规则")
     @ApiOperationSupport(order = 12)
     @GetMapping("/getCouponRule")
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "couponId", value = "优惠券id", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "couponId", value = "优惠券id(type=1 必传)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "type", value = "券类型(不传:优惠券与代金券规则都返回,1:优惠券,4:代金券)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "voucherId", value = "代金券id(type=4 时传此参数或 id)", dataType = "String", paramType = "query", required = false),
     })
-    public R<List<LifeDiscountCouponVo>> getCouponRule(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
-                                                       @RequestParam(value = "couponId") String couponId) {
-        log.info("LifeDiscountCouponController.getCouponRule?couponId={}", couponId);
+    public R<String> getCouponRule(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
+                                   @RequestParam(value = "couponId", required = false) String couponId,
+                                   @RequestParam(required = false) Integer type,
+                                   @RequestParam(value = "voucherId", required = false) String voucherId
+                                   ) {
+        log.info("LifeDiscountCouponController.getCouponRule?couponId={}, type={}, voucherId={}", couponId, type, voucherId);
         try {
-            String couponRule = lifeDiscountCouponService.getCouponRule(couponId, userLoginInfo);
+            String couponRule = lifeDiscountCouponService.getCouponRule(couponId, userLoginInfo, type, voucherId);
             return R.success(couponRule);
         } catch (Exception e) {
             log.error("LifeDiscountCouponController.getCouponRule ERROR Msg={}", e.getMessage());
@@ -268,6 +275,50 @@ public class LifeDiscountCouponController {
         }
     }
 
+    @ApiOperation("我创建的优惠券列表(当前店铺创建的 life_discount_coupon)")
+    @ApiOperationSupport(order = 12)
+    @GetMapping("/getMyCreatedDiscountCouponList")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "商户id", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "status", value = "状态筛选", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "couponStatus", value = "优惠券状态:0草稿,1正式", dataType = "int", paramType = "query", required = false),
+    })
+    public R<List<LifeDiscountCouponVo>> getMyCreatedDiscountCouponList(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
+                                                                         @RequestParam(value = "storeId") String storeId,
+                                                                         @RequestParam(value = "status", required = false) String status,
+                                                                         @RequestParam(value = "couponStatus", defaultValue = "1", required = false) int couponStatus) {
+        log.info("LifeDiscountCouponController.getMyCreatedDiscountCouponList?storeId={}", storeId);
+        try {
+            List<LifeDiscountCouponVo> list = lifeDiscountCouponService.getStoreAllCouponListPaginateNot(status, storeId, userLoginInfo, couponStatus, 1);
+            return R.data(list);
+        } catch (Exception e) {
+            log.error("LifeDiscountCouponController.getMyCreatedDiscountCouponList ERROR Msg={}", e.getMessage());
+            return R.fail("查询失败");
+        }
+    }
+
+    @ApiOperation("我创建的代金券列表(当前店铺创建的 life_coupon,type=1 代金券)")
+    @ApiOperationSupport(order = 12)
+    @GetMapping("/getMyCreatedVoucherList")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "商户id", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "status", value = "状态筛选", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "couponStatus", value = "代金券状态:0草稿,1正式", dataType = "int", paramType = "query", required = false),
+    })
+    public R<List<LifeDiscountCouponVo>> getMyCreatedVoucherList(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
+                                                                 @RequestParam(value = "storeId") String storeId,
+                                                                 @RequestParam(value = "status", required = false) String status,
+                                                                 @RequestParam(value = "couponStatus", defaultValue = "1", required = false) int couponStatus) {
+        log.info("LifeDiscountCouponController.getMyCreatedVoucherList?storeId={}", storeId);
+        try {
+            List<LifeDiscountCouponVo> list = lifeDiscountCouponService.getStoreAllCouponListPaginateNot(status, storeId, userLoginInfo, couponStatus, 4);
+            return R.data(list);
+        } catch (Exception e) {
+            log.error("LifeDiscountCouponController.getMyCreatedVoucherList ERROR Msg={}", e.getMessage());
+            return R.fail("查询失败");
+        }
+    }
+
     @ApiOperation("获取优惠券状态")
     @ApiOperationSupport(order = 13)
     @GetMapping("/getCouponStatus")

+ 39 - 24
alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponStoreFriendController.java

@@ -54,12 +54,12 @@ public class LifeDiscountCouponStoreFriendController {
         }
     }
 
-//    @TrackEvent(
-//            eventType = "COUPON_GIVE",
-//            eventCategory = "COUPON",
-//            storeId = "#{#lifeDiscountCouponStoreFriendDto.storeId}",
-//            targetType = "COUPON"
-//    )
+@TrackEvent(
+          eventType = "COUPON_GIVE",
+           eventCategory = "COUPON",
+           storeId = "#{#lifeDiscountCouponStoreFriendDto.storeId}",
+           targetType = "COUPON"
+   )
     @ApiOperation("给好友发放优惠券")
     @ApiOperationSupport(order = 2)
     @PostMapping("/setFriendCoupon")
@@ -133,17 +133,22 @@ public class LifeDiscountCouponStoreFriendController {
 
     @ApiOperation("保存好友赠券规则")
     @PostMapping("/saveFriendCouponRule")
-    public R saveFriendCouponRule(@RequestBody LifeDiscountCouponFriendRule lifeDiscountCouponFriendRule) {
+    public R<LifeDiscountCouponFriendRuleVo> saveFriendCouponRule(@RequestBody LifeDiscountCouponFriendRule lifeDiscountCouponFriendRule) {
         log.info("LifeDiscountCouponStoreFriendController.saveFriendCouponRule?lifeDiscountCouponFriendRule={}", lifeDiscountCouponFriendRule.toString());
-        LifeDiscountCouponFriendRuleVo lifeDiscountCouponFriendRuleVo = lifeDiscountCouponStoreFriendService.saveFriendCouponRule(lifeDiscountCouponFriendRule);
-        return R.data(lifeDiscountCouponFriendRuleVo);
+        try {
+            LifeDiscountCouponFriendRuleVo lifeDiscountCouponFriendRuleVo = lifeDiscountCouponStoreFriendService.saveFriendCouponRule(lifeDiscountCouponFriendRule);
+            return R.data(lifeDiscountCouponFriendRuleVo, "保存成功");
+        } catch (Exception e) {
+            log.error("LifeDiscountCouponStoreFriendController.saveFriendCouponRule ERROR Msg={}", e.getMessage());
+            return R.fail("保存失败");
+        }
     }
 
     @ApiOperation("删除赠券规则")
     @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "id", dataType = "String", paramType = "query", required = true)
     })
     @GetMapping("/delFriendCouponRule")
-    private R delFriendCouponRule(@RequestParam(value = "id") String id) {
+    public R delFriendCouponRule(@RequestParam(value = "id") String id) {
         log.info("LifeDiscountCouponStoreFriendController.delFriendCouponRule?id={}", id);
         lifeDiscountCouponStoreFriendService.delFriendCouponRule(id);
         return R.success("删除成功");
@@ -153,38 +158,48 @@ public class LifeDiscountCouponStoreFriendController {
     @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "id", dataType = "String", paramType = "query", required = true)
     })
     @GetMapping("/getRuleById")
-    private R<LifeDiscountCouponFriendRuleVo> getRuleById(@RequestParam(value = "id") String id) {
+    public R<LifeDiscountCouponFriendRuleVo> getRuleById(@RequestParam(value = "id") String id) {
         log.info("LifeDiscountCouponStoreFriendController.getRuleById?id={}", id);
         return R.data(lifeDiscountCouponStoreFriendService.getRuleById(id));
     }
 
-    @ApiOperation("查询好友赠券")
-    @ApiImplicitParams({@ApiImplicitParam(name = "storeId", value = "当前登录店铺id", dataType = "String", paramType = "query", required = true)
-    ,@ApiImplicitParam(name = "friendStoreUserId", value = "选中好友店铺用户id", dataType = "String", paramType = "query", required = false)
+    @ApiOperation("查询好友赠券。type=4 返回代金券,否则返回优惠券")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "当前登录店铺id", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "friendStoreUserId", value = "选中好友店铺用户id", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "type", value = "4=代金券,其他=优惠券", dataType = "Integer", paramType = "query", required = false)
     })
     @GetMapping("/getReceivedFriendCouponList")
-    private R<List<LifeDiscountCouponFriendRuleDetailVo>> getReceivedFriendCouponList(@RequestParam(value = "storeId") String storeId, @RequestParam(value = "friendStoreUserId",required = false)String friendStoreUserId) {
-        log.info("LifeDiscountCouponStoreFriendController.getReceivedFriendCouponList?storeId={},friendStoreUserId={}", storeId,friendStoreUserId);
-        return R.data(lifeDiscountCouponStoreFriendService.getReceivedFriendCouponList(storeId,friendStoreUserId));
+    public R<List<LifeDiscountCouponFriendRuleDetailVo>> getReceivedFriendCouponList(@RequestParam(value = "storeId") String storeId,
+                                                                                     @RequestParam(value = "friendStoreUserId", required = false) String friendStoreUserId,
+                                                                                     @RequestParam(value = "type", required = false) Integer type) {
+        log.info("LifeDiscountCouponStoreFriendController.getReceivedFriendCouponList?storeId={},friendStoreUserId={},type={}", storeId, friendStoreUserId, type);
+        return R.data(lifeDiscountCouponStoreFriendService.getReceivedFriendCouponList(storeId, friendStoreUserId, type));
     }
 
     @ApiOperation("查询赠券规则")
     @ApiImplicitParams({@ApiImplicitParam(name = "storeId", value = "当前登录店铺id", dataType = "String", paramType = "query", required = true)
     })
     @GetMapping("/getRuleList")
-    private R<List<LifeDiscountCouponFriendRuleVo>> getRuleList(@RequestParam(value = "storeId") String storeId, String acName, String status) {
+    public R<List<LifeDiscountCouponFriendRuleVo>> getRuleList(@RequestParam(value = "storeId") String storeId, String acName, String status) {
         log.info("LifeDiscountCouponStoreFriendController.getRuleList?storeId={},name={},status={}", storeId, acName, status);
         return R.data(lifeDiscountCouponStoreFriendService.getRuleList(storeId, acName, status));
     }
 
-    @ApiOperation("查询赠券记录")
-    @ApiImplicitParams({@ApiImplicitParam(name = "storeUserId", value = "好友赠我-当前登录店铺id", dataType = "String", paramType = "query", required = false)
-            ,@ApiImplicitParam(name = "friendStoreUserId", value = "我赠好友-选中好友店铺用户id", dataType = "String", paramType = "query", required = false)
+    @ApiOperation("查询赠券记录。type=4 仅代金券,不传返回全部(优惠券+代金券)")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeUserId", value = "好友赠我-当前登录店铺id", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "friendStoreUserId", value = "我赠好友-选中好友店铺用户id", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "storeName", value = "店铺名称模糊", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "type", value = "4=仅代金券,不传=全部", dataType = "Integer", paramType = "query", required = false)
     })
     @GetMapping("/getReceivedSendFriendCouponList")
-    private R<List<LifeDiscountCouponFriendRuleVo>> getReceivedSendFriendCouponList(@RequestParam(value = "storeUserId",required = false) String storeUserId, @RequestParam(value = "friendStoreUserId",required = false)String friendStoreUserId, @RequestParam(value = "storeName",required = false)String storeName) {
-        log.info("LifeDiscountCouponStoreFriendController.getReceivedSendFriendCouponList?storeId={},friendStoreUserId={},storeName={}", storeUserId,friendStoreUserId,storeName);
-        return R.data(lifeDiscountCouponStoreFriendService.getReceivedSendFriendCouponList(storeUserId,friendStoreUserId,storeName));
+    public R<List<LifeDiscountCouponFriendRuleVo>> getReceivedSendFriendCouponList(@RequestParam(value = "storeUserId", required = false) String storeUserId,
+                                                                                   @RequestParam(value = "friendStoreUserId", required = false) String friendStoreUserId,
+                                                                                   @RequestParam(value = "storeName", required = false) String storeName,
+                                                                                   @RequestParam(value = "type", required = false) Integer type) {
+        log.info("LifeDiscountCouponStoreFriendController.getReceivedSendFriendCouponList?storeUserId={},friendStoreUserId={},storeName={},type={}", storeUserId, friendStoreUserId, storeName, type);
+        return R.data(lifeDiscountCouponStoreFriendService.getReceivedSendFriendCouponList(storeUserId, friendStoreUserId, storeName, type));
 
 
 

+ 149 - 5
alien-store/src/main/java/shop/alien/store/controller/StoreCuisineController.java

@@ -2,7 +2,9 @@ package shop.alien.store.controller;
 
 import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
@@ -17,10 +19,12 @@ import shop.alien.entity.store.dto.CuisineComboDto;
 import shop.alien.entity.store.dto.CuisineTypeResponseDto;
 import shop.alien.entity.store.dto.TablewareFeeDto;
 import shop.alien.entity.store.vo.PriceListVo;
+import shop.alien.mapper.StoreCuisineMapper;
 import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.StoreCuisineService;
 import shop.alien.store.service.StoreInfoService;
 import shop.alien.store.service.StorePriceService;
+import shop.alien.store.util.ai.AiContentModerationUtil;
 import shop.alien.store.util.ai.AiGetPriceUtil;
 
 import java.text.SimpleDateFormat;
@@ -51,15 +55,142 @@ public class StoreCuisineController {
 
     private final StoreInfoService storeInfoService;
 
+    private final AiContentModerationUtil aiContentModerationUtil;
+
+    private final StoreCuisineMapper storeCuisineMapper;
+
     @ApiOperation("新增美食套餐或单品")
     @ApiOperationSupport(order = 1)
     @PostMapping("/addCuisineCombo")
     public R<StoreCuisine> addCuisineCombo(@RequestBody CuisineComboDto cuisineComboDto) {
-        log.info("StoreCuisineController.save?storeCuisine={}", cuisineComboDto);
-        if (storeCuisineService.addCuisineCombo(cuisineComboDto)) {
-            return R.success("新增成功");
+        log.info("StoreCuisineController.addCuisineCombo?cuisineComboDto={}", cuisineComboDto);
+        
+        // 参数验证
+        if (cuisineComboDto == null) {
+            log.error("新增美食套餐或单品失败:参数不能为空");
+            return R.fail("参数不能为空");
+        }
+        
+        try {
+            // 保存数据
+            boolean saveResult = storeCuisineService.addCuisineCombo(cuisineComboDto);
+            if (!saveResult) {
+                log.error("新增美食套餐或单品失败:保存操作返回false");
+                return R.fail("新增失败");
+            }
+            
+            // 由于 addCuisineCombo 方法返回的是 boolean,我们需要通过查询获取保存后的记录
+            // 通过 storeId + name + cuisineType + 最新的创建时间 来查询,确保获取到刚保存的记录
+            StoreCuisine savedCuisine = null;
+            if (cuisineComboDto.getStoreId() != null && StringUtils.isNotEmpty(cuisineComboDto.getName()) 
+                    && cuisineComboDto.getCuisineType() != null) {
+                LambdaQueryWrapper<StoreCuisine> queryWrapper = new LambdaQueryWrapper<>();
+                queryWrapper.eq(StoreCuisine::getStoreId, cuisineComboDto.getStoreId())
+                        .eq(StoreCuisine::getName, cuisineComboDto.getName())
+                        .eq(StoreCuisine::getCuisineType, cuisineComboDto.getCuisineType())
+                        .orderByDesc(StoreCuisine::getCreatedTime)
+                        .last("LIMIT 1");
+                savedCuisine = storeCuisineService.getOne(queryWrapper);
+            }
+            
+            if (savedCuisine == null) {
+                log.error("新增美食套餐或单品失败:保存后查询不到数据");
+                return R.fail("新增失败:数据保存异常");
+            }
+            
+            // 将状态置为"审核中"(0)
+            LambdaUpdateWrapper<StoreCuisine> auditingWrapper = new LambdaUpdateWrapper<>();
+            auditingWrapper.eq(StoreCuisine::getId, savedCuisine.getId());
+            auditingWrapper.set(StoreCuisine::getStatus, 0);
+            auditingWrapper.set(StoreCuisine::getRejectionReason, null);
+            auditingWrapper.set(StoreCuisine::getAuditTime, new Date());
+            storeCuisineMapper.update(null, auditingWrapper);
+            
+            // 组装 AI 审核文本和图片
+            StringBuilder textContent = new StringBuilder();
+            if (StringUtils.isNotEmpty(cuisineComboDto.getName())) {
+                textContent.append(cuisineComboDto.getName()).append(" ");
+            }
+            if (StringUtils.isNotEmpty(cuisineComboDto.getDetailContent())) {
+                textContent.append(cuisineComboDto.getDetailContent()).append(" ");
+            }
+            if (StringUtils.isNotEmpty(cuisineComboDto.getDescription())) {
+                textContent.append(cuisineComboDto.getDescription()).append(" ");
+            }
+            if (StringUtils.isNotEmpty(cuisineComboDto.getDishReview())) {
+                textContent.append(cuisineComboDto.getDishReview()).append(" ");
+            }
+            if (StringUtils.isNotEmpty(cuisineComboDto.getExtraNote())) {
+                textContent.append(cuisineComboDto.getExtraNote()).append(" ");
+            }
+            if (StringUtils.isNotEmpty(cuisineComboDto.getReserveRule())) {
+                textContent.append(cuisineComboDto.getReserveRule()).append(" ");
+            }
+            if (StringUtils.isNotEmpty(cuisineComboDto.getUsageRule())) {
+                textContent.append(cuisineComboDto.getUsageRule()).append(" ");
+            }
+
+            List<String> imageUrls = new ArrayList<>();
+
+            if (StringUtils.isNotEmpty(cuisineComboDto.getImages())) {
+                String[] urls = cuisineComboDto.getImages().split(",");
+                for (String url : urls) {
+                    if (StringUtils.isNotEmpty(url.trim())) {
+                        String trimmedUrl = url.trim();
+                        imageUrls.add(trimmedUrl);
+                    }
+                }
+            }
+
+            if (StringUtils.isNotEmpty(cuisineComboDto.getImageContent())) {
+                String[] urls = cuisineComboDto.getImageContent().split(",");
+                for (String url : urls) {
+                    if (StringUtils.isNotEmpty(url.trim())) {
+                        String trimmedUrl = url.trim();
+                        imageUrls.add(trimmedUrl);
+                    }
+                }
+            }
+
+            // 执行AI审核
+            if (StringUtils.isNotEmpty(textContent.toString()) || imageUrls.size() > 0) {
+                AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(textContent.toString(), imageUrls);
+                boolean allPassed = (auditResult != null);
+                
+                LambdaUpdateWrapper<StoreCuisine> auditUpdateWrapper = new LambdaUpdateWrapper<>();
+                auditUpdateWrapper.eq(StoreCuisine::getId, savedCuisine.getId());
+                auditUpdateWrapper.set(StoreCuisine::getRejectionReason, null);
+                auditUpdateWrapper.set(StoreCuisine::getAuditTime, new Date());
+                
+                if (allPassed) {
+                    // 审核通过 审核状态为1 上架状态为1 已上架
+                    auditUpdateWrapper.set(StoreCuisine::getStatus, 1);
+                    auditUpdateWrapper.set(StoreCuisine::getShelfStatus, 1);
+                    log.info("AI审核通过,ID: {}", savedCuisine.getId());
+                } else {
+                    // 审核拒绝 审核状态为2 上架状态为0 没有上下架状态
+                    auditUpdateWrapper.set(StoreCuisine::getStatus, 2);
+                    auditUpdateWrapper.set(StoreCuisine::getShelfStatus, 0);
+                    log.info("AI审核拒绝,ID: {}", savedCuisine.getId());
+                }
+                storeCuisineMapper.update(null, auditUpdateWrapper);
+            }
+            
+            // 重新查询一次,返回最新的数据
+            StoreCuisine finalCuisine = storeCuisineService.getById(savedCuisine.getId());
+            if (finalCuisine != null) {
+                log.info("新增成功,返回ID: {}", finalCuisine.getId());
+                return R.data(finalCuisine, "新增成功");
+            }
+            
+            // 如果查询失败,返回保存后的对象(至少包含ID)
+            log.warn("新增成功但最终查询失败,返回保存后的对象,ID: {}", savedCuisine.getId());
+            return R.data(savedCuisine, "新增成功");
+            
+        } catch (Exception e) {
+            log.error("新增美食套餐或单品异常", e);
+            return R.fail("新增失败:" + e.getMessage());
         }
-        return R.fail("新增失败");
     }
 
     @ApiOperation("修改美食套餐或单品")
@@ -193,6 +324,11 @@ public class StoreCuisineController {
             // 默认只查询已上架的菜品/套餐(shelf_status = 1)
             if(null==origin){
                 queryWrapper.eq(StoreCuisine::getShelfStatus, 1);
+            }else if(origin == 0){
+                queryWrapper.in(StoreCuisine::getShelfStatus, 1,2);
+            }
+            else{
+                queryWrapper.eq(StoreCuisine::getShelfStatus, origin);
             }
             if (storeId != null) {
                 queryWrapper.eq(StoreCuisine::getStoreId, storeId);
@@ -238,7 +374,15 @@ public class StoreCuisineController {
                 queryWrapper.eq(StorePrice::getStatus, status);
             }
             // 默认只查询已上架的菜品/套餐(shelf_status = 1)
-            queryWrapper.eq(StorePrice::getShelfStatus, 1);
+            if(null==origin){
+                queryWrapper.eq(StorePrice::getShelfStatus, 1);
+            }else if(origin == 0){
+                queryWrapper.in(StorePrice::getShelfStatus, 1,2);
+            }
+            else{
+                queryWrapper.eq(StorePrice::getShelfStatus, origin);
+            }
+//            queryWrapper.eq(StorePrice::getShelfStatus, 1);
             // 创建时间范围查询
             if (startTime != null) {
                 queryWrapper.ge(StorePrice::getCreatedTime, startTime);

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

@@ -236,6 +236,28 @@ public class StoreInfoController {
             
             // 如果 store_id 不为空,则使用该 store_id 查询门店详细信息
             if (storeUser.getStoreId() != null && storeUser.getStoreId() > 0) {
+                // 先查询门店信息,检查审核状态和删除标记
+                StoreInfo storeInfo = storeInfoMapper.selectById(storeUser.getStoreId());
+                
+                if (storeInfo == null) {
+                    log.warn("门店不存在: storeId={}, phone={}", storeUser.getStoreId(), phone);
+                    return R.fail("门店不存在");
+                }
+                
+                // 排除被删除的门店
+                if (storeInfo.getDeleteFlag() != null && storeInfo.getDeleteFlag() != 0) {
+                    log.warn("门店已被删除: storeId={}, phone={}, deleteFlag={}", 
+                            storeUser.getStoreId(), phone, storeInfo.getDeleteFlag());
+                    return R.fail("门店已被删除");
+                }
+                
+                // 排除审核状态为2(审核失败)的门店
+                if (storeInfo.getStoreApplicationStatus() != null && storeInfo.getStoreApplicationStatus() == 2) {
+                    log.warn("门店审核失败: storeId={}, phone={}, storeApplicationStatus={}", 
+                            storeUser.getStoreId(), phone, storeInfo.getStoreApplicationStatus());
+                    return R.fail("门店审核失败");
+                }
+                
                 return R.data(storeInfoService.getDetail(storeUser.getStoreId()));
             } else {
                 log.warn("商家用户的 store_id 为空: phone={}, userId={}", phone, storeUser.getId());

+ 30 - 9
alien-store/src/main/java/shop/alien/store/controller/StoreOperationalActivityController.java

@@ -114,7 +114,7 @@ public class StoreOperationalActivityController {
         log.info("StoreOperationalActivityController.signup dto={}", dto);
         try {
             boolean result = operationalActivityService.signup(dto);
-            return result ? R.success("报名成功") : R.fail("报名失败");
+            return result ? R.success("你已成功参与,请耐心等待报名结果") : R.fail("报名失败");
         } catch (IllegalArgumentException e) {
             return R.fail(e.getMessage());
         } catch (Exception e) {
@@ -187,29 +187,50 @@ public class StoreOperationalActivityController {
     @ApiOperation("案例列表")
     @ApiOperationSupport(order = 7)
     @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataTypeClass = Integer.class, paramType = "query", required = true),
             @ApiImplicitParam(name = "activityStatus", value = "活动状态", dataTypeClass = Integer.class, paramType = "query"),
             @ApiImplicitParam(name = "pageNum", value = "当前页", dataTypeClass = Integer.class, paramType = "query"),
             @ApiImplicitParam(name = "pageSize", value = "每页条数", dataTypeClass = Integer.class, paramType = "query")
     })
     @GetMapping("/achievement/case/list")
     public R<IPage<StoreOperationalActivityAchievementCaseVo>> listCasePage(
+            @RequestParam("storeId") Integer storeId,
             @RequestParam(value = "activityStatus", required = false) Integer activityStatus,
             @RequestParam(value = "pageNum", required = false) Integer pageNum,
             @RequestParam(value = "pageSize", required = false) Integer pageSize) {
-        log.info("StoreOperationalActivityController.listCasePage activityStatus={}, pageNum={}, pageSize={}",
-                activityStatus, pageNum, pageSize);
+        log.info("StoreOperationalActivityController.listCasePage storeId={}, activityStatus={}, pageNum={}, pageSize={}",
+                storeId, activityStatus, pageNum, pageSize);
         try {
             IPage<StoreOperationalActivityAchievementCaseVo> result =
-                    achievementService.listCasePage(activityStatus, pageNum, pageSize);
+                    achievementService.listCasePage(storeId, activityStatus, pageNum, pageSize);
             return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
         } catch (Exception e) {
             log.error("StoreOperationalActivityController.listCasePage ERROR: {}", e.getMessage(), e);
             return R.fail(e.getMessage());
         }
     }
 
-    @ApiOperation("案例详情")
+    @ApiOperation("案例卡片概览(列表默认一条,含总数)")
     @ApiOperationSupport(order = 8)
+    @ApiImplicitParam(name = "storeId", value = "店铺ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    @GetMapping("/achievement/case/preview")
+    public R<StoreOperationalActivityCasePreviewVo> listCasePreview(@RequestParam("storeId") Integer storeId) {
+        log.info("StoreOperationalActivityController.listCasePreview storeId={}", storeId);
+        try {
+            StoreOperationalActivityCasePreviewVo result = achievementService.listCasePreview(storeId);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.listCasePreview ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("案例详情")
+    @ApiOperationSupport(order = 9)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true),
             @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
@@ -231,7 +252,7 @@ public class StoreOperationalActivityController {
     }
 
     @ApiOperation("我的报名列表")
-    @ApiOperationSupport(order = 9)
+    @ApiOperationSupport(order = 10)
     @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
     @GetMapping("/signup/my")
     public R<List<StoreOperationalActivityMySignupVo>> listMySignups(@RequestParam("userId") Integer userId) {
@@ -253,7 +274,7 @@ public class StoreOperationalActivityController {
     }
 
     @ApiOperation("删除报名及成果")
-    @ApiOperationSupport(order = 10)
+    @ApiOperationSupport(order = 11)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true),
             @ApiImplicitParam(name = "signupId", value = "报名ID", dataTypeClass = Integer.class, paramType = "query", required = true)
@@ -279,7 +300,7 @@ public class StoreOperationalActivityController {
     }
 
     @ApiOperation("商家端案例列表")
-    @ApiOperationSupport(order = 10)
+    @ApiOperationSupport(order = 12)
     @PostMapping("/achievement/case/store/list")
     public R<IPage<StoreOperationalActivityAchievementCaseVo>> listStoreCasePage(
             @RequestBody StoreOperationalActivityCaseQueryDto dto) {
@@ -289,7 +310,7 @@ public class StoreOperationalActivityController {
                 return R.fail("商户ID不能为空");
             }
             IPage<StoreOperationalActivityAchievementCaseVo> result = achievementService.listStoreCasePage(
-                    dto.getStoreId(), dto.getHasResult(), dto.getActivityName(), dto.getPageNum(), dto.getPageSize());
+                    dto.getStoreId(), dto.getActivityStatus(), dto.getActivityName(), dto.getPageNum(), dto.getPageSize());
             return R.data(result);
         } catch (IllegalArgumentException e) {
             return R.fail(e.getMessage());

+ 124 - 7
alien-store/src/main/java/shop/alien/store/controller/StorePriceController.java

@@ -1,7 +1,9 @@
 package shop.alien.store.controller;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
@@ -12,8 +14,10 @@ import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreInfo;
 import shop.alien.entity.store.StorePrice;
 import shop.alien.mapper.StoreInfoMapper;
+import shop.alien.mapper.StorePriceMapper;
 import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.StorePriceService;
+import shop.alien.store.util.ai.AiContentModerationUtil;
 import shop.alien.util.encryption.Decrypt;
 import shop.alien.util.encryption.Encrypt;
 import shop.alien.util.encryption.JasyptEncryptorUtil;
@@ -21,10 +25,7 @@ import shop.alien.util.encryption.StandardAesUtil;
 import shop.alien.util.encryption.properties.EncryptProperties;
 
 import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**
  * 通用价目表
@@ -47,6 +48,10 @@ public class StorePriceController {
 
     private final EncryptProperties encryptProperties;
 
+    private final AiContentModerationUtil aiContentModerationUtil;
+
+    private final StorePriceMapper storePriceMapper;
+
     @Value("${test1}")
     private String dbPassword;
 
@@ -103,10 +108,122 @@ public class StorePriceController {
     @PostMapping("/save")
     public R<StorePrice> save(@RequestBody StorePrice storePrice) {
         log.info("StorePriceController.save?storePrice={}", storePrice);
-        if (storePriceService.save(storePrice)) {
-            return R.data(storePrice, "新增成功");
+        
+        // 参数验证
+        if (storePrice == null) {
+            log.error("新增通用价目失败:参数不能为空");
+            return R.fail("参数不能为空");
+        }
+        
+        try {
+            // 保存数据
+            boolean saveResult = storePriceService.save(storePrice);
+            if (!saveResult) {
+                log.error("新增通用价目失败:保存操作返回false");
+                return R.fail("新增失败");
+            }
+            
+            // MyBatis-Plus 的 save 方法会自动将生成的主键ID填充到对象中
+            Integer savedId = storePrice.getId();
+            if (savedId == null) {
+                log.error("新增通用价目失败:保存后ID为空");
+                return R.fail("新增失败:ID生成失败");
+            }
+            
+            // 重新查询一次,确保返回包含完整数据(包括ID和所有数据库字段)的对象
+            StorePrice savedPrice = storePriceService.getById(savedId);
+            if (savedPrice == null) {
+                log.error("新增通用价目失败:保存后查询不到数据,ID={}", savedId);
+                return R.fail("新增失败:数据保存异常");
+            }
+            
+            // 将状态置为"审核中"(0)
+            LambdaUpdateWrapper<StorePrice> auditingWrapper = new LambdaUpdateWrapper<>();
+            auditingWrapper.eq(StorePrice::getId, savedPrice.getId());
+            auditingWrapper.set(StorePrice::getStatus, 0);
+            auditingWrapper.set(StorePrice::getRejectionReason, null);
+            auditingWrapper.set(StorePrice::getAuditTime, new Date());
+            storePriceMapper.update(null, auditingWrapper);
+            
+            // 组装 AI 审核文本和图片
+            StringBuilder textContent = new StringBuilder();
+            if (StringUtils.isNotEmpty(storePrice.getName())) {
+                textContent.append(storePrice.getName()).append(" ");
+            }
+            if (StringUtils.isNotEmpty(storePrice.getDetailContent())) {
+                textContent.append(storePrice.getDetailContent()).append(" ");
+            }
+            if (StringUtils.isNotEmpty(storePrice.getExtraNote())) {
+                textContent.append(storePrice.getExtraNote()).append(" ");
+            }
+            if (StringUtils.isNotEmpty(storePrice.getReserveRule())) {
+                textContent.append(storePrice.getReserveRule()).append(" ");
+            }
+            if (StringUtils.isNotEmpty(storePrice.getUsageRule())) {
+                textContent.append(storePrice.getUsageRule()).append(" ");
+            }
+
+            List<String> imageUrls = new ArrayList<>();
+
+            if (StringUtils.isNotEmpty(storePrice.getImages())) {
+                String[] urls = storePrice.getImages().split(",");
+                for (String url : urls) {
+                    if (StringUtils.isNotEmpty(url.trim())) {
+                        String trimmedUrl = url.trim();
+                        imageUrls.add(trimmedUrl);
+                    }
+                }
+            }
+
+            if (StringUtils.isNotEmpty(storePrice.getImageContent())) {
+                String[] urls = storePrice.getImageContent().split(",");
+                for (String url : urls) {
+                    if (StringUtils.isNotEmpty(url.trim())) {
+                        String trimmedUrl = url.trim();
+                        imageUrls.add(trimmedUrl);
+                    }
+                }
+            }
+
+            // 执行AI审核
+            if (StringUtils.isNotEmpty(textContent.toString()) || imageUrls.size() > 0) {
+                AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(textContent.toString(), imageUrls);
+                boolean allPassed = (auditResult != null);
+                
+                LambdaUpdateWrapper<StorePrice> auditUpdateWrapper = new LambdaUpdateWrapper<>();
+                auditUpdateWrapper.eq(StorePrice::getId, savedPrice.getId());
+                auditUpdateWrapper.set(StorePrice::getRejectionReason, null);
+                auditUpdateWrapper.set(StorePrice::getAuditTime, new Date());
+                
+                if (allPassed) {
+                    // 审核通过 审核状态为1 上架状态为1 已上架
+                    auditUpdateWrapper.set(StorePrice::getStatus, 1);
+                    auditUpdateWrapper.set(StorePrice::getShelfStatus, 1);
+                    log.info("AI审核通过,ID: {}", savedPrice.getId());
+                } else {
+                    // 审核拒绝 审核状态为2 上架状态为0 没有上下架状态
+                    auditUpdateWrapper.set(StorePrice::getStatus, 2);
+                    auditUpdateWrapper.set(StorePrice::getShelfStatus, 0);
+                    log.info("AI审核拒绝,ID: {}", savedPrice.getId());
+                }
+                storePriceMapper.update(null, auditUpdateWrapper);
+            }
+            
+            // 重新查询一次,返回最新的数据
+            StorePrice finalPrice = storePriceService.getById(savedPrice.getId());
+            if (finalPrice != null) {
+                log.info("新增成功,返回ID: {}", finalPrice.getId());
+                return R.data(finalPrice, "新增成功");
+            }
+            
+            // 如果查询失败,返回保存后的对象(至少包含ID)
+            log.warn("新增成功但最终查询失败,返回保存后的对象,ID: {}", savedId);
+            return R.data(savedPrice, "新增成功");
+            
+        } catch (Exception e) {
+            log.error("新增通用价目异常", e);
+            return R.fail("新增失败:" + e.getMessage());
         }
-        return R.fail("新增失败");
     }
 
     @ApiOperation("修改通用价目")

+ 2 - 2
alien-store/src/main/java/shop/alien/store/dto/StoreOperationalActivityCaseQueryDto.java

@@ -17,8 +17,8 @@ public class StoreOperationalActivityCaseQueryDto {
     @ApiModelProperty(value = "商户ID", required = true)
     private Integer storeId;
 
-    @ApiModelProperty(value = "上传情况:0-未上传, 1-已上传")
-    private Integer hasResult;
+    @ApiModelProperty(value = "活动状态")
+    private Integer activityStatus;
 
     @ApiModelProperty(value = "所属活动(活动名称,支持模糊查询)")
     private String activityName;

+ 8 - 2
alien-store/src/main/java/shop/alien/store/service/BarPerformanceService.java

@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import shop.alien.entity.store.BarPerformance;
 import shop.alien.entity.store.vo.BarPerformanceDetailVo;
 
+import java.util.Date;
+
 /**
  * 酒吧演出服务接口
  * 提供酒吧演出的CRUD、审核、上下线等核心业务功能
@@ -74,10 +76,14 @@ public interface BarPerformanceService {
      * @param page    分页页数
      * @param size    分页条数
      * @param storeId 门店ID
-     * @param statusReview 审核状态(0-待审核 1-审核通过 2-审核拒绝)
+     * @param reviewStatus 审核状态(0-待审核 1-审核通过 2-审核拒绝)
      * @param category 演出分类(all-全部, not_started-未开始, in_progress-进行中, ended-已结束)
      * @param performanceName 演出名称(可选,支持模糊搜索)
+     * @param performanceType 演出类型(0-特邀演出,1-常规演出)
+     * @param onlineStatus 上线状态(0-下线,1-上线)
+     * @param startCreatedTime 创建时间开始
+     * @param endCreatedTime 创建时间结束
      * @return 演出列表
      */
-    IPage<BarPerformance> queryPerformanceListByStoreIdAndCategory(Integer page, Integer size, Integer storeId, Integer statusReview, String category, String performanceName);
+    IPage<BarPerformance> queryPerformanceListByStoreIdAndCategory(Integer page, Integer size, Integer storeId, Integer reviewStatus, String category, String performanceName, Integer performanceType, Integer onlineStatus, Date startCreatedTime, Date endCreatedTime);
 }

+ 9 - 3
alien-store/src/main/java/shop/alien/store/service/LifeDiscountCouponService.java

@@ -63,8 +63,9 @@ public interface LifeDiscountCouponService extends IService<LifeDiscountCoupon>
 
     /**
      * 获取该用户优惠券列表
+     * @param type 不传:优惠券+代金券都返回;1:仅优惠券(查 life_discount_coupon);4:仅代金券(查 life_coupon)
      */
-    List<LifeDiscountCouponVo> getUserCouponList(UserLoginInfo userLoginInfo, int page, int size, String tabType);
+    List<LifeDiscountCouponVo> getUserCouponList(UserLoginInfo userLoginInfo, int page, int size, String tabType, Integer type);
 
     /**
      * 获取所有优惠券列表(分页)
@@ -73,13 +74,18 @@ public interface LifeDiscountCouponService extends IService<LifeDiscountCoupon>
 
     /**
      * 获取所有优惠券列表(不分页)
+     * @param type 类型:1-优惠券(默认),4-代金券
      */
-    List<LifeDiscountCouponVo> getStoreAllCouponListPaginateNot(String status, String storeId, UserLoginInfo userLoginInfo, int couponStatus);
+    List<LifeDiscountCouponVo> getStoreAllCouponListPaginateNot(String status, String storeId, UserLoginInfo userLoginInfo, int couponStatus, Integer type);
 
     /**
      * 获取优惠券规则
      */
-    String getCouponRule(String couponId, UserLoginInfo userLoginInfo);
+    /**
+     * 获取优惠券/代金券使用规则
+     * @param type 不传:优惠券与代金券规则都返回;1:仅优惠券;4:仅代金券
+     */
+    String getCouponRule(String couponId, UserLoginInfo userLoginInfo, Integer type,String voucherId);
 
     /**
      * 获取优惠券状态

+ 13 - 7
alien-store/src/main/java/shop/alien/store/service/LifeDiscountCouponStoreFriendService.java

@@ -53,20 +53,26 @@ public interface LifeDiscountCouponStoreFriendService extends IService<LifeDisco
 
     void delFriendCouponRule(String id);
 
-    List<LifeDiscountCouponFriendRuleDetailVo> getReceivedFriendCouponList(String storeId,String friendStoreUserId);
+    /**
+     * 查询收到的赠券列表。type=4 返回代金券(life_coupon),否则返回优惠券(life_discount_coupon)
+     */
+    List<LifeDiscountCouponFriendRuleDetailVo> getReceivedFriendCouponList(String storeId, String friendStoreUserId, Integer type);
 
     List<LifeDiscountCouponFriendRuleVo> getRuleList(String storeId, String acName, String status);
 
     LifeDiscountCouponFriendRuleVo getRuleById(String id);
 
-    List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(String storeUserId, String friendStoreUserId,String storeName);
+    /** type=4 仅代金券,不传则返回全部(优惠券+代金券) */
+    List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(String storeUserId, String friendStoreUserId, String storeName, Integer type);
 
     /**
-     * 好评送券:用户对店铺好评且通过AI审核通过后,将店铺可用券发放到用户卡包
+     * 好评送券:用户好评且AI审核通过后,按运营活动配置的优惠券/代金券ID发放到用户券包,发放成功后扣减库存;按券类型发送对应通知。
      *
-     * @param userId  评价用户ID(life用户)
-     * @param storeId 店铺ID(businessId)
-     * @return 发放的优惠券数量,0表示未发放
+     * @param userId   评价用户ID(life用户)
+     * @param storeId  店铺ID(businessId)
+     * @param couponId 优惠券ID(life_discount_coupon),为null则不发优惠券
+     * @param voucherId 代金券ID(life_coupon),为null则不发代金券
+     * @return 发放数量(优惠券+代金券),0表示未发放
      */
-    int issueCouponForGoodRating(Integer userId, Integer storeId);
+//    int issueCouponForGoodRating(Integer userId, Integer storeId);
 }

+ 105 - 18
alien-store/src/main/java/shop/alien/store/service/LifeStoreService.java

@@ -203,35 +203,80 @@ public class LifeStoreService {
         return mutualAttention;
     }
 
+    /**
+     * 获取首页信息(优化版)
+     * 
+     * @param phoneId 用户标识(格式:user_手机号 或 store_手机号)
+     * @return 首页信息
+     */
     public LifeFansVo getHomePageInfo(String phoneId) {
+        // 参数校验
+        if (StringUtils.isEmpty(phoneId)) {
+            log.warn("getHomePageInfo: phoneId为空");
+            return new LifeFansVo();
+        }
+
+        // 验证 phoneId 格式
+        String[] phoneIdParts = phoneId.split("_");
+        if (phoneIdParts.length < 2) {
+            log.warn("getHomePageInfo: phoneId格式错误,phoneId={}", phoneId);
+            return new LifeFansVo();
+        }
+
         QueryWrapper<LifeFansVo> wrapper = new QueryWrapper<>();
         wrapper.groupBy("foll.phoneId");
 
         // 判断自己的拉黑type
-        String blockerType = "";
-        String blockerId = "";
-        if ("user".equals(phoneId.split("_")[0])) {
-            String myselfUserPhone = phoneId.split("_")[1];
-            blockerType = "2";
-            LifeUser myLifeUser = lifeUserService.getUserByPhone(myselfUserPhone);
-            blockerId = String.valueOf(myLifeUser.getId());
-        } else {
-            String myselfStorePhone = phoneId.split("_")[1];
-            blockerType = "1";
-            StoreUser myStoreUser = storeUserService.getUserByPhone(myselfStorePhone);
-            blockerId = String.valueOf(myStoreUser.getId());
+        final String blockerType;
+        final String blockerId;
+        try {
+            if ("user".equals(phoneIdParts[0])) {
+                String myselfUserPhone = phoneIdParts[1];
+                blockerType = "2";
+                LifeUser myLifeUser = lifeUserService.getUserByPhone(myselfUserPhone);
+                if (myLifeUser == null || myLifeUser.getId() == null) {
+                    log.warn("getHomePageInfo: 用户不存在,phoneId={}", phoneId);
+                    return new LifeFansVo();
+                }
+                blockerId = String.valueOf(myLifeUser.getId());
+            } else {
+                String myselfStorePhone = phoneIdParts[1];
+                blockerType = "1";
+                StoreUser myStoreUser = storeUserService.getUserByPhone(myselfStorePhone);
+                if (myStoreUser == null || myStoreUser.getId() == null) {
+                    log.warn("getHomePageInfo: 商户不存在,phoneId={}", phoneId);
+                    return new LifeFansVo();
+                }
+                blockerId = String.valueOf(myStoreUser.getId());
+            }
+        } catch (Exception e) {
+            log.error("getHomePageInfo: 获取用户信息异常,phoneId={}", phoneId, e);
+            return new LifeFansVo();
         }
 
-        IPage<LifeFansVo> myFollowed = lifeFansMapper.getMyFollowed(new Page<>(1, Integer.MAX_VALUE), phoneId, blockerType, blockerId, wrapper);
-        int followedNum = myFollowed.getRecords().stream().filter(x -> null == x.getBlackListid()).collect(Collectors.toList()).size();
+        // 优化:使用分页查询统计数量,避免使用 Integer.MAX_VALUE 导致内存溢出
+        // 使用合理的分页大小,如果数据量很大则循环分页统计
+        int pageSize = 1000; // 设置合理的分页大小
+        
+        // 统计关注数量(过滤被拉黑的)
+        int followedNum = countFilteredRecords(phoneId, blockerType, blockerId, wrapper, pageSize, 
+                (page, w) -> lifeFansMapper.getMyFollowed(page, phoneId, blockerType, blockerId, w));
 
-        IPage<LifeFansVo> myFans = lifeFansMapper.getMyFans(new Page<>(1, Integer.MAX_VALUE), phoneId, blockerType, blockerId, wrapper);
-        int fansNum = myFans.getRecords().stream().filter(x -> null == x.getBlackListid()).collect(Collectors.toList()).size();
+        // 统计粉丝数量(过滤被拉黑的)
+        int fansNum = countFilteredRecords(phoneId, blockerType, blockerId, wrapper, pageSize,
+                (page, w) -> lifeFansMapper.getMyFans(page, phoneId, blockerType, blockerId, w));
 
-        IPage<LifeFansVo> mutualAttention = lifeFansMapper.getMutualAttention(new Page<>(1, Integer.MAX_VALUE), phoneId, blockerType, blockerId, wrapper);
-        int friendNum = mutualAttention.getRecords().stream().filter(x -> null == x.getBlackListid()).collect(Collectors.toList()).size();
+        // 统计好友数量(过滤被拉黑的)
+        int friendNum = countFilteredRecords(phoneId, blockerType, blockerId, wrapper, pageSize,
+                (page, w) -> lifeFansMapper.getMutualAttention(page, phoneId, blockerType, blockerId, w));
 
+        // 获取基础信息(包含dynamicsNum)
         LifeFansVo lifeFansVo = lifeFansMapper.getHomePageInfo(phoneId);
+        if (lifeFansVo == null) {
+            lifeFansVo = new LifeFansVo();
+        }
+        
+        // 设置统计数量
         lifeFansVo.setFollowNum(String.valueOf(followedNum));
         lifeFansVo.setFansNum(String.valueOf(fansNum));
         lifeFansVo.setFriendNum(String.valueOf(friendNum));
@@ -398,6 +443,48 @@ public class LifeStoreService {
     }
 
 
+    /**
+     * 统计过滤后的记录数量(分页查询,避免内存溢出)
+     * 
+     * @param phoneId 用户标识
+     * @param blockerType 拉黑类型
+     * @param blockerId 拉黑者ID
+     * @param wrapper 查询条件
+     * @param pageSize 分页大小
+     * @param queryFunction 查询函数
+     * @return 过滤后的记录数量
+     */
+    private int countFilteredRecords(String phoneId, String blockerType, String blockerId, 
+                                     QueryWrapper<LifeFansVo> wrapper, int pageSize,
+                                     java.util.function.BiFunction<Page<LifeFansVo>, QueryWrapper<LifeFansVo>, IPage<LifeFansVo>> queryFunction) {
+        int totalCount = 0;
+        int currentPage = 1;
+        int maxPages = 100; // 限制最大页数,防止无限循环
+        
+        while (currentPage <= maxPages) {
+            IPage<LifeFansVo> pageResult = queryFunction.apply(new Page<>(currentPage, pageSize), wrapper);
+            
+            if (pageResult == null || CollectionUtils.isEmpty(pageResult.getRecords())) {
+                break;
+            }
+            
+            // 统计当前页过滤后的数量
+            long currentPageCount = pageResult.getRecords().stream()
+                    .filter(x -> null == x.getBlackListid())
+                    .count();
+            totalCount += (int) currentPageCount;
+            
+            // 如果当前页数据少于分页大小,说明已经是最后一页
+            if (pageResult.getRecords().size() < pageSize) {
+                break;
+            }
+            
+            currentPage++;
+        }
+        
+        return totalCount;
+    }
+
     private void filterBlocked(String fansId, IPage<LifeFansVo> myFollowed) {
 
         // 判断自己的拉黑type

+ 16 - 4
alien-store/src/main/java/shop/alien/store/service/StoreOperationalActivityAchievementService.java

@@ -4,8 +4,11 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseDetailVo;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseVo;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityCasePreviewVo;
 import shop.alien.store.dto.StoreOperationalActivityAchievementDto;
 
+import java.util.List;
+
 /**
  * 运营活动成果服务
  *
@@ -51,7 +54,16 @@ public interface StoreOperationalActivityAchievementService {
      * @param pageSize 每页条数
      * @return 分页列表
      */
-    IPage<StoreOperationalActivityAchievementCaseVo> listCasePage(Integer activityStatus, Integer pageNum, Integer pageSize);
+    IPage<StoreOperationalActivityAchievementCaseVo> listCasePage(Integer storeId, Integer activityStatus, Integer pageNum, Integer pageSize);
+
+    /**
+     * 案例卡片概览:列表默认返回一条,外层带案例总数。
+     * 每条为某活动下某用户的最新更新成果,包含首图/视频、昵称、更新时间。
+     *
+     * @param storeId 店铺ID
+     * @return 含 list(默认1条)与 total(总数)
+     */
+    StoreOperationalActivityCasePreviewVo listCasePreview(Integer storeId);
 
     /**
      * 案例详情
@@ -63,15 +75,15 @@ public interface StoreOperationalActivityAchievementService {
     StoreOperationalActivityAchievementCaseDetailVo getCaseDetail(Integer activityId, Integer userId);
 
     /**
-     * 商家端案例列表(按商户ID、上传情况、活动名称筛选)
+     * 商家端案例列表(按商户ID、活动状态、活动名称筛选)
      *
      * @param storeId 商户ID
-     * @param hasResult 上传情况:0-未上传, 1-已上传
+     * @param activityStatus 活动状态
      * @param activityName 活动名称(模糊查询)
      * @param pageNum 当前页
      * @param pageSize 每页条数
      * @return 分页列表
      */
-    IPage<StoreOperationalActivityAchievementCaseVo> listStoreCasePage(Integer storeId, Integer hasResult,
+    IPage<StoreOperationalActivityAchievementCaseVo> listStoreCasePage(Integer storeId, Integer activityStatus,
                                                                         String activityName, Integer pageNum, Integer pageSize);
 }

+ 20 - 2
alien-store/src/main/java/shop/alien/store/service/impl/BarPerformanceServiceImpl.java

@@ -572,11 +572,11 @@ public class BarPerformanceServiceImpl implements BarPerformanceService {
                 // 忽略无效的状态值
             }
         }
-        return queryPerformanceListByStoreIdAndCategory(page, size, storeId, reviewStatus, "all", null);
+        return queryPerformanceListByStoreIdAndCategory(page, size, storeId, reviewStatus, "all", null, null, null, null, null);
     }
 
     @Override
-    public IPage<BarPerformance> queryPerformanceListByStoreIdAndCategory(Integer page, Integer size, Integer storeId, Integer reviewStatus, String category, String performanceName) {
+    public IPage<BarPerformance> queryPerformanceListByStoreIdAndCategory(Integer page, Integer size, Integer storeId, Integer reviewStatus, String category, String performanceName, Integer performanceType, Integer onlineStatus, Date startCreatedTime, Date endCreatedTime) {
         if (page == null || size == null || storeId == null || storeId <= 0) {
             return new Page<>();
         }
@@ -590,6 +590,16 @@ public class BarPerformanceServiceImpl implements BarPerformanceService {
             queryWrapper.like("performance_name", performanceName);
         }
 
+        // 按演出类型筛选
+        if (performanceType != null) {
+            queryWrapper.eq("performance_type", performanceType);
+        }
+
+        // 按上线状态筛选
+        if (onlineStatus != null) {
+            queryWrapper.eq("online_status", onlineStatus);
+        }
+
         // 按审核状态筛选(数据库字段 review_status)
         if (reviewStatus != null) {
             queryWrapper.eq("review_status", reviewStatus);
@@ -649,6 +659,14 @@ public class BarPerformanceServiceImpl implements BarPerformanceService {
             }
         }
 
+        // 按创建时间范围筛选
+        if (startCreatedTime != null) {
+            queryWrapper.ge("created_time", startCreatedTime);
+        }
+        if (endCreatedTime != null) {
+            queryWrapper.le("created_time", endCreatedTime);
+        }
+
         queryWrapper.eq("delete_flag", 0);
         // 按提交时间(创建时间)倒序排序
         queryWrapper.orderByDesc("created_time");

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

@@ -24,7 +24,9 @@ import shop.alien.entity.store.vo.CommonCommentVo;
 import shop.alien.entity.store.vo.CommonRatingVo;
 import shop.alien.entity.store.vo.StoreInfoScoreVo;
 import shop.alien.entity.store.vo.WebSocketVo;
+import shop.alien.entity.storePlatform.StoreOperationalActivity;
 import shop.alien.mapper.*;
+import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
 import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.service.CommonCommentService;
 import shop.alien.store.service.CommonRatingService;
@@ -81,6 +83,7 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
     private final ExecutorService commonVideoTaskExecutor;
     private final AiVideoModerationUtil aiVideoModerationUtil;
     private final LifeDiscountCouponStoreFriendService lifeDiscountCouponStoreFriendService;
+    private final StoreOperationalActivityMapper storeOperationalActivityMapper;
 
     public static final List<String> SERVICES_LIST = ImmutableList.of(
             TextReviewServiceEnum.COMMENT_DETECTION_PRO.getService(),
@@ -105,17 +108,7 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                 commonRating.setScoreThree(parse.getDouble("scoreThree"));
             }
             int i = this.save(commonRating) ? 0 : 1;
-            if (i == 0 && commonRating.getBusinessType() != null && commonRating.getBusinessType() == RatingBusinessTypeEnum.STORE_RATING.getBusinessType()) {
-                Double score = commonRating.getScore();
-                if (score != null && score >= 4.5 && commonRating.getUserId() != null && commonRating.getBusinessId() != null) {
-                    try {
-
-                        lifeDiscountCouponStoreFriendService.issueCouponForGoodRating(Math.toIntExact(commonRating.getUserId()), commonRating.getBusinessId());
-                    } catch (Exception ex) {
-                        log.warn("CommonRatingService.saveCommonRating 好评送券异常 userId={}, storeId={}, msg={}", commonRating.getUserId(), commonRating.getBusinessId(), ex.getMessage());
-                    }
-                }
-            }
+            // 好评发券改为仅在 AI 审核通过后执行,见 doBusinessWithType 中逻辑
             // 一次遍历完成分类,避免多次流式处理
             Map<String, List<String>> urlCategoryMap = StoreRenovationRequirementServiceImpl.classifyUrls(Arrays.asList(commonRating.getImageUrls().split(",")));
             AiContentModerationUtil.AuditResult auditResult = new AiContentModerationUtil.AuditResult(true, "");
@@ -231,6 +224,38 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                 websocketVo.setText(JSONObject.from(lifeMessage).toJSONString());
                 webSocketProcess.sendMessage("store_" + storeUser.getPhone(), JSONObject.from(websocketVo).toJSONString());
             }
+            // 用户好评且 AI 审核通过后:按运营活动表(评论有礼)配置的 coupon_id/voucher_id 发放,用 participation_limit 限制参与次数,发放成功后扣减库存,按优惠券/代金券发对应通知
+            if (score != null && score >= 4.5 && commonRating.getUserId() != null && commonRating.getBusinessId() != null) {
+                try {
+                    Date now = new Date();
+                    LambdaQueryWrapper<StoreOperationalActivity> activityWrapper = new LambdaQueryWrapper<>();
+                    activityWrapper.eq(StoreOperationalActivity::getStoreId, businessId)
+                            .eq(StoreOperationalActivity::getActivityType, "COMMENT")
+                            .in(StoreOperationalActivity::getStatus, 5, 8)
+                            .le(StoreOperationalActivity::getStartTime, now)
+                            .ge(StoreOperationalActivity::getEndTime, now)
+                            .orderByDesc(StoreOperationalActivity::getId)
+                            .last("LIMIT 1");
+                    StoreOperationalActivity activity = storeOperationalActivityMapper.selectOne(activityWrapper);
+//                    if (activity == null || (activity.getCouponId() == null && activity.getVoucherId() == null)) {
+//                        return;
+//                    }
+                    Integer limit = activity.getParticipationLimit();
+                    if (limit != null && limit > 0) {
+                        int passedCount = commonRatingMapper.countPassedGoodRatingsByUserAndStore(commonRating.getUserId(), businessId);
+                        if (passedCount > limit) {
+                            log.info("CommonRatingService 好评送券跳过:超过运营活动参与次数 participation_limit={}, count={}, userId={}, storeId={}",
+                                    limit, passedCount, commonRating.getUserId(), businessId);
+                            return;
+                        }
+                    }
+//                    lifeDiscountCouponStoreFriendService.issueCouponForGoodRating(
+//                            Math.toIntExact(commonRating.getUserId()), businessId,
+//                            activity.getCouponId(), activity.getVoucherId());
+                } catch (Exception ex) {
+                    log.warn("CommonRatingService 好评送券异常 userId={}, storeId={}, msg={}", commonRating.getUserId(), businessId, ex.getMessage());
+                }
+            }
         }
 
     }
@@ -363,6 +388,12 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
         wrapper.eq(CommonRating::getIsShow, 1);
         List<CommonRating> commonRatings = commonRatingMapper.selectList(wrapper);
         List<Long> collect = commonRatings.stream().map(x -> x.getId()).collect(Collectors.toList());
+        // 查询没有回复的评价数量
+        wrapper.eq(CommonRating::getDeleteFlag,0);
+        wrapper.ge(CommonRating::getScore,0.5);
+        wrapper.le(CommonRating::getScore,2.5);
+        Integer noReplyCount = commonRatingMapper.getRatingWithNoReply(wrapper);
+
         // 如果为空直接返回
         Map<String, Object> ratingCount = new HashMap<>();
         if(commonRatings.isEmpty()){
@@ -370,7 +401,7 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
         }
         // 获取评价统计信息(总评论数、有图评论数、好评数、中评数、差评数)
         ratingCount = commonRatingMapper.getRatingCount(new QueryWrapper<CommonRating>().in("id", collect));
-
+        ratingCount.put("noReplyCount", noReplyCount);
         // 计算好评、中评、差评占比
         // 注意:数据库返回的 count 可能是 BigDecimal、Long 或 Integer 类型,需要安全转换
         int goodCount = getIntValue(ratingCount.get("goodCount"));

+ 62 - 3
alien-store/src/main/java/shop/alien/store/service/impl/LicenseAuditAsyncService.java

@@ -135,8 +135,33 @@ public class LicenseAuditAsyncService {
                     StoreInfo updateStoreInfo = new StoreInfo();
                     updateStoreInfo.setId(storeId);
                     updateStoreInfo.setBusinessLicenseExpirationTime(expiryDate);
+
+                    // 同步更新门店到期时间(expirationTime)
+                    // 门店到期时间 = min(合同到期时间, 营业执照到期时间)
+                    // 由于合同到期时间未单独存储,需要根据旧值推断
+                    StoreInfo currentStore = storeInfoMapper.selectById(storeId);
+                    if (currentStore != null) {
+                        Date currentExpiration = currentStore.getExpirationTime();
+                        Date oldBizExpiration = currentStore.getBusinessLicenseExpirationTime();
+                        if (currentExpiration != null && oldBizExpiration != null
+                                && currentExpiration.before(oldBizExpiration)) {
+                            // 合同到期时间 < 旧营业执照到期时间,说明合同是瓶颈
+                            // 新的门店到期时间 = min(合同到期时间, 新营业执照到期时间)
+                            updateStoreInfo.setExpirationTime(
+                                    expiryDate.before(currentExpiration) ? expiryDate : currentExpiration
+                            );
+                        } else {
+                            // 营业执照是瓶颈(或两者相等、或旧值为空)
+                            // 直接用新的营业执照到期时间更新
+                            updateStoreInfo.setExpirationTime(expiryDate);
+                        }
+                    } else {
+                        updateStoreInfo.setExpirationTime(expiryDate);
+                    }
+
                     storeInfoMapper.updateById(updateStoreInfo);
-                    log.info("营业执照到期时间已更新,门店ID:{},到期时间:{}", storeId, expiryDateStr);
+                    log.info("营业执照到期时间已更新,门店ID:{},营业执照到期:{},门店到期:{}",
+                            storeId, expiryDateStr, updateStoreInfo.getExpirationTime());
                 } catch (Exception e) {
                     log.error("解析营业执照到期时间失败,门店ID:{},expiryDate:{}", storeId, expiryDateStr, e);
                 }
@@ -222,6 +247,17 @@ public class LicenseAuditAsyncService {
                         }
                         log.info("{}AI审核通过,门店ID:{},图片URL:{}", licenseTypeName, storeId, imageUrl);
                         
+                        // 审核通过后,先清除旧的其他资质证明图片(不在当前批次中的)
+                        String[] currentBatchUrls = currentHistory.getImgUrl().split(",");
+                        LambdaUpdateWrapper<StoreImg> cleanOldWrapper = new LambdaUpdateWrapper<>();
+                        cleanOldWrapper.eq(StoreImg::getStoreId, storeId)
+                                .eq(StoreImg::getImgType, 35)
+                                .eq(StoreImg::getDeleteFlag, 0)
+                                .notIn(StoreImg::getImgUrl, Arrays.asList(currentBatchUrls))
+                                .set(StoreImg::getDeleteFlag, 1);
+                        storeImgMapper.update(null, cleanOldWrapper);
+                        log.info("其他资质证明审核通过,已清除旧图片,门店ID:{}", storeId);
+                        
                         // 审核通过后,插入store_img表(检查是否已存在,避免重复插入)
                         StoreImg existingImg = storeImgMapper.selectOne(
                                 new LambdaQueryWrapper<StoreImg>()
@@ -272,9 +308,10 @@ public class LicenseAuditAsyncService {
                     log.info("{}AI审核拒绝,门店ID:{},图片URL:{},拒绝原因:{}", licenseTypeName, storeId, imageUrl, rejectReason);
                     storeInfoMapper.update(null, new LambdaUpdateWrapper<StoreInfo>()
                             .eq(StoreInfo::getId, storeId)
-                            .set(StoreInfo::getBusinessLicenseStatus, 2)
+                            .set(StoreInfo::getBusinessLicenseStatus, 3)
+                            .set(StoreInfo::getBusinessLicenseReason, rejectReason)
                     );
-                    // 审核拒绝时,删除store_img表中的记录(逻辑删除),避免前端展示审核拒绝的图片
+                    // 审核拒绝时,删除store_img表中的记录(逻辑删除),避免前端展示审核拒绝的图片 ->之前都没插入。。。应该没用这段
                     LambdaUpdateWrapper<StoreImg> deleteImgWrapper = new LambdaUpdateWrapper<>();
                     deleteImgWrapper.eq(StoreImg::getStoreId, storeId)
                             .eq(StoreImg::getImgType, 14) // 营业执照对应14
@@ -282,6 +319,19 @@ public class LicenseAuditAsyncService {
                             .eq(StoreImg::getDeleteFlag, 0)
                             .set(StoreImg::getDeleteFlag, 1);
                     storeImgMapper.update(null, deleteImgWrapper);
+                    // 查询最新的type=14的记录,更新imgSort
+                    StoreImg latestImg = storeImgMapper.selectDeleImg(
+                            new LambdaQueryWrapper<StoreImg>()
+                                    .eq(StoreImg::getStoreId, storeId)
+                                    .eq(StoreImg::getImgType, 14)
+                                    .eq(StoreImg::getDeleteFlag, 1)
+                                    .last("limit 1")
+                                    .orderByDesc(StoreImg::getId)
+                    );
+                    if (latestImg != null) {
+                        int update = storeImgMapper.updateDelete(String.valueOf(latestImg.getId()));
+                        log.info("{}AI审核拒绝,已更新store_img记录,门店ID:{},图片URL:{},更新记录数:{}", licenseTypeName, storeId, imageUrl, update);
+                    }
                     log.info("{}AI审核拒绝,已删除store_img记录,门店ID:{},图片URL:{}", licenseTypeName, storeId, imageUrl);
                 } else if (needApprove) {
                     // 审核通过
@@ -292,6 +342,15 @@ public class LicenseAuditAsyncService {
                             .eq(StoreInfo::getId, storeId)
                             .set(StoreInfo::getBusinessLicenseStatus, 1)
                     );
+                    
+                    // 审核通过后,先逻辑删除旧的营业执照记录
+                    LambdaUpdateWrapper<StoreImg> deleteOldImgWrapper = new LambdaUpdateWrapper<>();
+                    deleteOldImgWrapper.eq(StoreImg::getStoreId, storeId)
+                            .eq(StoreImg::getImgType, 14)
+                            .eq(StoreImg::getDeleteFlag, 0)
+                            .set(StoreImg::getDeleteFlag, 1);
+                    storeImgMapper.update(null, deleteOldImgWrapper);
+
                     // 审核通过后,插入store_img表(检查是否已存在,避免重复插入)
                     StoreImg existingImg = storeImgMapper.selectOne(
                             new LambdaQueryWrapper<StoreImg>()

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

@@ -105,10 +105,10 @@ 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) {
+                    if(storeUser != null) {
+                        StoreInfo storeInfo = storeInfoMapper.selectById(storeUser.getStoreId());
+                        lifeBlacklist.setStoreInfo(storeInfo);
+                        StoreImg storeImg = null;
                         lifeBlacklist.setName(storeUser.getName());
                         lifeBlacklist.setPhoneId(storeUser.getPhone());
                         lifeBlacklist.setUserImage(storeUser.getHeadImg());

+ 321 - 45
alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponServiceImpl.java

@@ -68,6 +68,8 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
 
     private final LifeDiscountCouponUserService lifeDiscountCouponUserService;
 
+    private final LifeCouponMapper lifeCouponMapper;
+
     @Override
     public boolean addDiscountCoupon(LifeDiscountCouponDto lifeDiscountCouponDto) {
         try {
@@ -586,12 +588,11 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
     }
 
     @Override
-    public List<LifeDiscountCouponVo> getUserCouponList(UserLoginInfo userLoginInfo, int page, int size, String tabType) {
+    public List<LifeDiscountCouponVo> getUserCouponList(UserLoginInfo userLoginInfo, int page, int size, String tabType, Integer type) {
         List<LifeDiscountCouponVo> lifeDiscountCouponVos = new ArrayList<>();
         IPage<LifeDiscountCouponUser> iPage = new Page<>(page, size);
         LambdaQueryWrapper<LifeDiscountCouponUser> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(LifeDiscountCouponUser::getUserId, userLoginInfo.getUserId());
-
         //比较工具对象
         //获取七日前时间
         Date now = new Date();
@@ -655,6 +656,12 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
             //并且状态为待使用
             queryWrapper.eq(LifeDiscountCouponUser::getStatus, DiscountCouponEnum.WAITING_USED.getValue());
         }
+        // type:1 仅优惠券(couponId 有值),4 仅代金券(voucherId 有值),不传则都查
+        if (type != null && type == 1) {
+            queryWrapper.and(w -> w.isNotNull(LifeDiscountCouponUser::getCouponId));
+        } else if (type != null && type == 4) {
+            queryWrapper.and(w -> w.isNotNull(LifeDiscountCouponUser::getVoucherId));
+        }
         queryWrapper.orderByDesc(LifeDiscountCouponUser::getCreatedTime);
         IPage<LifeDiscountCouponUser> lifeDiscountCouponUserIPage = lifeDiscountCouponUserMapper.selectPage(iPage, queryWrapper);
         //根据券id去查询该券的基本信息
@@ -665,23 +672,107 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
         ZoneId zoneId = ZoneId.systemDefault();
         LocalDate localNow1 = instant.atZone(zoneId).toLocalDate();
 
-        //所有优惠券
-        List<Integer> couponIdList = lifeDiscountCouponUserIPage.getRecords().stream().map(LifeDiscountCouponUser::getCouponId).collect(Collectors.toList());
+        List<LifeDiscountCouponUser> records = lifeDiscountCouponUserIPage.getRecords();
+        // type=4:仅代金券,查 life_coupon 表
+        if (type != null && type == 4) {
+            List<String> voucherIdList = records.stream().map(LifeDiscountCouponUser::getVoucherId).filter(Objects::nonNull).collect(Collectors.toList());
+            if (voucherIdList.isEmpty()) {
+                return lifeDiscountCouponVos;
+            }
+            List<LifeCoupon> lifeCoupons = lifeCouponMapper.selectList(new LambdaQueryWrapper<LifeCoupon>().in(LifeCoupon::getId, voucherIdList));
+            List<String> storeIdList = lifeCoupons.stream().map(LifeCoupon::getStoreId).filter(s -> !StringUtils.isEmpty(s)).collect(Collectors.toList());
+            List<StoreInfo> storeInfoList = storeInfoMapper.getList(new LambdaQueryWrapper<StoreInfo>().in(!storeIdList.isEmpty(), StoreInfo::getId, storeIdList));
+            for (LifeDiscountCouponUser lifeDiscountCouponUser : records) {
+                LifeCoupon lc = lifeCoupons.stream().filter(c -> c.getId().equals(lifeDiscountCouponUser.getVoucherId())).findFirst().orElse(null);
+                if (lc == null) {
+                    continue;
+                }
+                LifeDiscountCouponVo vo = mapLifeCouponToVo(lc);
+                vo.setUserCouponId(lifeDiscountCouponUser.getId());
+                vo.setVoucherId(lc.getId());
+                vo.setQuantityClaimed(records.size());
+                vo.setExpirationTime(lifeDiscountCouponUser.getExpirationTime());
+                vo.setReachUseTimeFlag(1);
+                if (lc.getStartDate() != null) {
+                    LocalDate startLocal = lc.getStartDate().toInstant().atZone(zoneId).toLocalDate();
+                    if (localNow1.isBefore(startLocal)) {
+                        vo.setReachUseTimeFlag(0);
+                    }
+                }
+                if (lc.getEndDate() != null) {
+                    vo.setValidDate(lc.getEndDate().toInstant().atZone(zoneId).toLocalDate());
+                }
+                StoreInfo storeInfoOne = storeInfoList.stream().filter(s -> s.getId().toString().equals(lc.getStoreId())).findFirst().orElse(null);
+                if (storeInfoOne != null) {
+                    vo.setBusinessSection(storeInfoOne.getBusinessSection());
+                    vo.setBusinessSectionName(storeInfoOne.getBusinessSectionName());
+                }
+                lifeDiscountCouponVos.add(vo);
+            }
+            return lifeDiscountCouponVos;
+        }
+
+        // type=1 或 type=null:优惠券(或混合),查 life_discount_coupon
+        List<Integer> couponIdList = records.stream().map(LifeDiscountCouponUser::getCouponId).filter(Objects::nonNull).collect(Collectors.toList());
         List<LifeDiscountCoupon> lifeDiscountCoupons = lifeDiscountCouponMapper.selectList(new QueryWrapper<LifeDiscountCoupon>().lambda().in(!couponIdList.isEmpty(), LifeDiscountCoupon::getId, couponIdList));
-        if (lifeDiscountCoupons.isEmpty()) {
+        if (type != null && type == 1 && lifeDiscountCoupons.isEmpty()) {
             return lifeDiscountCouponVos;
         }
-        //优惠券的所有门店
-        List<String> storeIdList = lifeDiscountCoupons.stream().map(LifeDiscountCoupon::getStoreId).collect(Collectors.toList());
+        List<String> storeIdList = lifeDiscountCoupons.isEmpty() ? new ArrayList<>() : lifeDiscountCoupons.stream().map(LifeDiscountCoupon::getStoreId).collect(Collectors.toList());
         List<StoreInfo> storeInfoList = storeInfoMapper.getList(new LambdaQueryWrapper<StoreInfo>().in(!storeIdList.isEmpty(), StoreInfo::getId, storeIdList));
-        if (storeInfoList.isEmpty() && lifeDiscountCouponUserIPage.getRecords().isEmpty()) {
+        if (type != null && type == 1 && storeInfoList.isEmpty() && records.isEmpty()) {
             return lifeDiscountCouponVos;
         }
-        for (LifeDiscountCouponUser lifeDiscountCouponUser : lifeDiscountCouponUserIPage.getRecords()) {
-            LifeDiscountCoupon lifeDiscountCouponOne = lifeDiscountCoupons.stream().filter(lifeDiscountCoupon -> lifeDiscountCoupon.getId().equals(lifeDiscountCouponUser.getCouponId())).collect(Collectors.toList()).get(0);
+        List<LifeCoupon> lifeCouponsForMerge = new ArrayList<>();
+        if (type == null) {
+            List<String> voucherIdList = records.stream().map(LifeDiscountCouponUser::getVoucherId).filter(Objects::nonNull).collect(Collectors.toList());
+            if (!voucherIdList.isEmpty()) {
+                lifeCouponsForMerge = lifeCouponMapper.selectList(new LambdaQueryWrapper<LifeCoupon>().in(LifeCoupon::getId, voucherIdList));
+            }
+            if (!lifeCouponsForMerge.isEmpty()) {
+                List<String> voucherStoreIds = lifeCouponsForMerge.stream().map(LifeCoupon::getStoreId).filter(s -> !StringUtils.isEmpty(s)).collect(Collectors.toList());
+                if (!voucherStoreIds.isEmpty()) {
+                    List<StoreInfo> voucherStores = storeInfoMapper.getList(new LambdaQueryWrapper<StoreInfo>().in(StoreInfo::getId, voucherStoreIds));
+                    storeInfoList = new ArrayList<>(storeInfoList);
+                    storeInfoList.addAll(voucherStores);
+                }
+            }
+        }
+        for (LifeDiscountCouponUser lifeDiscountCouponUser : records) {
+            if (lifeDiscountCouponUser.getVoucherId() != null && !lifeCouponsForMerge.isEmpty()) {
+                LifeCoupon lc = lifeCouponsForMerge.stream().filter(c -> c.getId().equals(lifeDiscountCouponUser.getVoucherId())).findFirst().orElse(null);
+                if (lc != null) {
+                    LifeDiscountCouponVo vo = mapLifeCouponToVo(lc);
+                    vo.setUserCouponId(lifeDiscountCouponUser.getId());
+                    vo.setVoucherId(lc.getId());
+                    vo.setQuantityClaimed(records.size());
+                    vo.setExpirationTime(lifeDiscountCouponUser.getExpirationTime());
+                    vo.setReachUseTimeFlag(1);
+                    if (lc.getStartDate() != null) {
+                        LocalDate startLocal = lc.getStartDate().toInstant().atZone(zoneId).toLocalDate();
+                        if (localNow1.isBefore(startLocal)) {
+                            vo.setReachUseTimeFlag(0);
+                        }
+                    }
+                    if (lc.getEndDate() != null) {
+                        vo.setValidDate(lc.getEndDate().toInstant().atZone(zoneId).toLocalDate());
+                    }
+                    StoreInfo storeInfoOne = storeInfoList.stream().filter(s -> s.getId().toString().equals(lc.getStoreId())).findFirst().orElse(null);
+                    if (storeInfoOne != null) {
+                        vo.setBusinessSection(storeInfoOne.getBusinessSection());
+                        vo.setBusinessSectionName(storeInfoOne.getBusinessSectionName());
+                    }
+                    lifeDiscountCouponVos.add(vo);
+                }
+                continue;
+            }
+            LifeDiscountCoupon lifeDiscountCouponOne = lifeDiscountCoupons.stream().filter(lifeDiscountCoupon -> lifeDiscountCoupon.getId().equals(lifeDiscountCouponUser.getCouponId())).findFirst().orElse(null);
+            if (lifeDiscountCouponOne == null) {
+                continue;
+            }
             StoreInfo storeInfoOne = null;
             if (!storeInfoList.isEmpty() && !StringUtils.isEmpty(lifeDiscountCouponOne.getStoreId())) {
-                storeInfoOne = storeInfoList.stream().filter(storeInfo -> storeInfo.getId().toString().equals(lifeDiscountCouponOne.getStoreId())).collect(Collectors.toList()).get(0);
+                storeInfoOne = storeInfoList.stream().filter(storeInfo -> storeInfo.getId().toString().equals(lifeDiscountCouponOne.getStoreId())).findFirst().orElse(null);
             }
             LifeDiscountCouponVo lifeDiscountCouponVo = new LifeDiscountCouponVo();
             if (null != storeInfoOne) {
@@ -689,6 +780,7 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
                 lifeDiscountCouponVo.setBusinessSectionName(storeInfoOne.getBusinessSectionName());
             }
             lifeDiscountCouponVo.setCouponId(lifeDiscountCouponOne.getId());
+            lifeDiscountCouponVo.setVoucherId(lifeDiscountCouponUser.getVoucherId());
             BeanUtils.copyProperties(lifeDiscountCouponOne, lifeDiscountCouponVo);
             lifeDiscountCouponVo.setQuantityClaimed(lifeDiscountCouponUserIPage.getRecords().size());
             lifeDiscountCouponVo.setExpirationTime(lifeDiscountCouponUser.getExpirationTime());
@@ -891,24 +983,39 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
     }
 
     @Override
-    public List<LifeDiscountCouponVo> getStoreAllCouponListPaginateNot(String status, String storeId, UserLoginInfo userLoginInfo, int couponStatus) {
-        List<LifeDiscountCouponVo> lifeDiscountCouponVos = new ArrayList<>();
-        //根据店铺id查询该店铺的优惠券,状态是开启领取的券
-        List<LifeDiscountCoupon> lifeDiscountCoupons = lifeDiscountCouponMapper.selectList(new LambdaQueryWrapper<LifeDiscountCoupon>().eq(LifeDiscountCoupon::getStoreId, storeId).eq(LifeDiscountCoupon::getCouponStatus, couponStatus).ne(LifeDiscountCoupon::getSingleQty, 0).orderByDesc(LifeDiscountCoupon::getCreatedTime));
+    public List<LifeDiscountCouponVo> getStoreAllCouponListPaginateNot(String status, String storeId, UserLoginInfo userLoginInfo, int couponStatus, Integer type) {
+        List<LifeDiscountCouponVo> result = new ArrayList<>();
+        if (Integer.valueOf(4).equals(type)) {
+            // type=4:直接查 life_coupon(代金券),返回代金券数据。life_coupon.type=1 为代金券,data_type 0正式/1草稿 与 couponStatus 1/0 对应
+            LambdaQueryWrapper<LifeCoupon> wrapper = new LambdaQueryWrapper<LifeCoupon>()
+                    .eq(LifeCoupon::getStoreId, storeId)
+                    .eq(LifeCoupon::getType, 1)
+                    .ne(LifeCoupon::getSingleQty, 0)
+                    .eq(LifeCoupon::getDataType, 1 - couponStatus);
+            List<LifeCoupon> lifeCoupons = lifeCouponMapper.selectList(wrapper);
+            for (LifeCoupon lc : lifeCoupons) {
+                LifeDiscountCouponVo vo = mapLifeCouponToVo(lc);
+                if (!StringUtils.isEmpty(status) && !status.equals(vo.getStatus() != null ? vo.getStatus().toString() : "")) {
+                    continue;
+                }
+                result.add(vo);
+            }
+            return result;
+        }
+
+        // type 不为 4:只查 life_discount_coupon 单表(优惠券列表)
+        List<LifeDiscountCoupon> lifeDiscountCoupons = lifeDiscountCouponMapper.selectListSingleTable(storeId, couponStatus, type);
         for (LifeDiscountCoupon lifeDiscountCoupon : lifeDiscountCoupons) {
             LifeDiscountCouponVo lifeDiscountCouponVo = new LifeDiscountCouponVo();
             lifeDiscountCouponVo.setCouponId(lifeDiscountCoupon.getId());
-            //处理一下该优惠券的状态 状态(0:进行中,1:已结束,2:未开始,3:已暂停)
-            // 获取当前时间
+            lifeDiscountCouponVo.setType(lifeDiscountCoupon.getType());
             Date now = new Date();
-            // 将 Date 转换为 LocalDate
             Instant instant = now.toInstant();
             ZoneId zoneId = ZoneId.systemDefault();
             LocalDate localNow = instant.atZone(zoneId).toLocalDate();
             if (!StringUtils.isEmpty(lifeDiscountCoupon.getBeginGetDate()) && !StringUtils.isEmpty(lifeDiscountCoupon.getEndGetDate())) {
                 int startResult = localNow.compareTo(lifeDiscountCoupon.getBeginGetDate());
                 int endResult = localNow.compareTo(lifeDiscountCoupon.getEndGetDate());
-                //如果当前时间小于开始时间
                 if (lifeDiscountCoupon.getGetStatus().toString().equals(DiscountCouponEnum.NO_GET.getValue())) {
                     lifeDiscountCouponVo.setStatus(Integer.parseInt(DiscountCouponEnum.SUSPEND_GET.getValue()));
                 } else if (startResult < 0) {
@@ -920,45 +1027,116 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
                 }
             }
             BeanUtils.copyProperties(lifeDiscountCoupon, lifeDiscountCouponVo);
-            //如果按照状态查询了
+            lifeDiscountCouponVo.setCreatedTime(lifeDiscountCoupon.getCreatedTime() != null ? lifeDiscountCoupon.getCreatedTime() : null);
             if (!StringUtils.isEmpty(status) && !status.equals(lifeDiscountCouponVo.getStatus().toString())) {
                 continue;
             }
-            lifeDiscountCouponVos.add(lifeDiscountCouponVo);
+            result.add(lifeDiscountCouponVo);
         }
-        return lifeDiscountCouponVos;
+        return result;
+    }
+
+    /** 将 life_coupon(代金券)转为 LifeDiscountCouponVo,便于与 life_discount_coupon 统一返回 */
+    private LifeDiscountCouponVo mapLifeCouponToVo(LifeCoupon lc) {
+        LifeDiscountCouponVo vo = new LifeDiscountCouponVo();
+        vo.setVoucherId(lc.getId());
+        vo.setStoreId(lc.getStoreId());
+        vo.setName(lc.getName());
+        vo.setSingleQty(lc.getSingleQty());
+        vo.setCreatedTime(lc.getCreatedTime());
+        vo.setType(4);
+        if (lc.getStartDate() != null) {
+            vo.setStartDate(lc.getStartDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+        }
+        if (lc.getEndDate() != null) {
+            vo.setEndDate(lc.getEndDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+        }
+        if (lc.getPrice() != null) {
+            try {
+                vo.setNominalValue(new BigDecimal(lc.getPrice()));
+            } catch (Exception ignored) { }
+        }
+        if (lc.getStatus() != null) {
+            if (lc.getStatus() == 5) {
+                vo.setStatus(0);
+            } else if (lc.getStatus() == 7 || lc.getStatus() == 6) {
+                vo.setStatus(1);
+            } else if (lc.getStatus() == 2) {
+                vo.setStatus(2);
+            } else {
+                vo.setStatus(3);
+            }
+        }
+        return vo;
     }
 
     @Override
-    public String getCouponRule(String couponId, UserLoginInfo userLoginInfo) {
-        LifeDiscountCoupon lifeDiscountCoupon = lifeDiscountCouponMapper.selectById(couponId);
+    public String getCouponRule(String couponId, UserLoginInfo userLoginInfo, Integer type, String voucherId) {
+        // type=4:仅代金券,根据 life_coupon 构建使用规则文案(代金券 id 优先用 id 参数,未传则用 couponId)
+        if (type != null && type == 4) {
+            String vid = (voucherId != null && !voucherId.isEmpty()) ? voucherId : couponId;
+            if (vid == null || vid.isEmpty()) {
+                return "";
+            }
+            LifeCoupon lc = lifeCouponMapper.selectById(vid);
+            return lc != null ? buildLifeCouponRule(lc) : "";
+        }
+        // type=1:仅优惠券,根据 life_discount_coupon 构建使用规则
+        if (type != null && type == 1) {
+            try {
+                Integer coupon = Integer.parseInt(couponId);
+                LifeDiscountCoupon lifeDiscountCoupon = lifeDiscountCouponMapper.selectById(coupon);
+                if (lifeDiscountCoupon == null) {
+                    return "";
+                }
+                return buildDiscountCouponRule(lifeDiscountCoupon);
+            } catch (NumberFormatException e) {
+                return "";
+            }
+        }
+        // type 不传:根据 life_discount_coupon 与 life_coupon 两张表分别构建使用规则文案并都返回
+        StringBuilder sb = new StringBuilder();
+        try {
+            Integer coup = Integer.parseInt(couponId);
+            LifeDiscountCoupon c = lifeDiscountCouponMapper.selectById(coup);
+            if (c != null) {
+                sb.append("【优惠券使用规则】\n").append(buildDiscountCouponRule(c));
+            }
+        } catch (NumberFormatException ignored) {
+        }
+        String vid = (voucherId != null && !voucherId.isEmpty()) ? voucherId : couponId;
+        if (vid != null && !vid.isEmpty()) {
+            try {
+                LifeCoupon lc = lifeCouponMapper.selectById(vid);
+                if (lc != null) {
+                    if (sb.length() > 0) {
+                        sb.append("\n\n");
+                    }
+                    sb.append("【代金券使用规则】\n").append(buildLifeCouponRule(lc));
+                }
+            } catch (Exception ignored) {
+            }
+        }
+        return sb.toString();
+    }
 
+    /** 根据 life_coupon(代金券)构建使用规则文案,委托给 buildVoucherCouponRule,与 buildDiscountCouponRule 对应的一套代金券逻辑 */
+    private String buildLifeCouponRule(LifeCoupon lc) {
+        return buildVoucherCouponRule(lc);
+    }
+
+    /** 根据 life_discount_coupon 构建使用规则文案(节假日/周中禁用 + 最低消费) */
+    private String buildDiscountCouponRule(LifeDiscountCoupon lifeDiscountCoupon) {
         // 获取 Calendar 实例,默认表示当前日期和时间
         Calendar calendar = Calendar.getInstance();
-        // 将小时设置为 0
         calendar.set(Calendar.HOUR_OF_DAY, 0);
-        // 将分钟设置为 0
         calendar.set(Calendar.MINUTE, 0);
-        // 将秒设置为 0
         calendar.set(Calendar.SECOND, 0);
-        // 将毫秒设置为 0
         calendar.set(Calendar.MILLISECOND, 0);
-        // 获取设置后的 Date 对象
-        Date midnight = calendar.getTime();
-
-        //判断今日是否为节该券禁用节假日
-        //判断当日是否为节假日
-        // 获取当前日期
         Date currentDate = new Date();
-        // 创建 SimpleDateFormat 对象,指定日期格式
         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
-        // 格式化日期
         String formattedDate = dateFormat.format(currentDate);
-        //获取当前年份
-        // 获取 Calendar 实例
-        // 获取当前年份
         int year = calendar.get(Calendar.YEAR);
-        //获取该年份基础节假日数据
         LambdaQueryWrapper<EssentialHolidayComparison> essentialHolidayComparisonLambdaQueryWrapper = new LambdaQueryWrapper<>();
         essentialHolidayComparisonLambdaQueryWrapper.eq(EssentialHolidayComparison::getParticularYear, year);
         List<EssentialHolidayComparison> essentialHolidayComparisons = essentialHolidayComparisonMapper.selectList(essentialHolidayComparisonLambdaQueryWrapper);
@@ -968,7 +1146,6 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
                 festivalName = essentialHolidayComparison.getFestivalName();
             }
         }
-        //获取今日周几
         int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
         String dayName = "";
         switch (dayOfWeek) {
@@ -993,10 +1170,11 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
             case Calendar.SATURDAY:
                 dayName = "六";
                 break;
+            default:
+                break;
         }
 
         LifeDiscountCouponVo lifeDiscountCouponVo = new LifeDiscountCouponVo();
-        //该券的节假日禁用规则
         LambdaQueryWrapper<LifeDiscountCouponUnavailableRules> lambdaQueryWrapper = new LambdaQueryWrapper<>();
         lambdaQueryWrapper.eq(LifeDiscountCouponUnavailableRules::getDiscountCouponId, lifeDiscountCoupon.getId());
         lambdaQueryWrapper.eq(LifeDiscountCouponUnavailableRules::getUnavailableRuleType, DiscountCouponEnum.HOLIDAY_UNAVAILABLE.getValue());
@@ -1008,7 +1186,6 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
         List<String> holidCoupnDis = lifeDiscountCouponUnavailableRules.stream().map(rule -> rule.getUnavailableRuleValue()).collect(Collectors.toList());
         forbiddenRule += String.join(", ", holidCoupnDis);
         for (LifeDiscountCouponUnavailableRules lifeDiscountCouponUnavailableRule : lifeDiscountCouponUnavailableRules) {
-            //如果当日为节假日,并且该券该节假日也禁用,则该券禁用
             if (StringUtils.isEmpty(festivalName) && festivalName.equals(lifeDiscountCouponUnavailableRule.getUnavailableRuleValue())) {
                 lifeDiscountCouponVo.setDisabledStatus(false);
             }
@@ -1016,7 +1193,6 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
         if (lifeDiscountCouponUnavailableRules.size() > 0) {
             forbiddenRule += NOT_USE;
         }
-        //该券的周中禁用规则
         LambdaQueryWrapper<LifeDiscountCouponUnavailableRules> lambdaQueryWeekWrapper = new LambdaQueryWrapper<>();
         lambdaQueryWeekWrapper.eq(LifeDiscountCouponUnavailableRules::getDiscountCouponId, lifeDiscountCoupon.getId());
         lambdaQueryWeekWrapper.eq(LifeDiscountCouponUnavailableRules::getUnavailableRuleType, DiscountCouponEnum.WEEKDAY_UNAVAILABLE.getValue());
@@ -1027,7 +1203,6 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
         List<String> weekCoupnDis = weekLifeDiscountCouponUnavailableRules.stream().map(rule -> rule.getUnavailableRuleValue()).collect(Collectors.toList());
         forbiddenRule += String.join(", ", weekCoupnDis);
         for (LifeDiscountCouponUnavailableRules lifeDiscountCouponUnavailableRule : lifeDiscountCouponUnavailableRules) {
-            //该券该周中日也禁用,则该券禁用
             if (dayName.equals(lifeDiscountCouponUnavailableRule.getUnavailableRuleValue())) {
                 lifeDiscountCouponVo.setDisabledStatus(false);
             }
@@ -1037,10 +1212,111 @@ public class LifeDiscountCouponServiceImpl extends ServiceImpl<LifeDiscountCoupo
         }
         forbiddenRule += MINIMUM_SPENDING_AMOUNT + lifeDiscountCoupon.getMinimumSpendingAmount();
         lifeDiscountCouponVo.setForbiddenRule(forbiddenRule);
+        return forbiddenRule;
+    }
 
+    /**
+     * 代金券使用规则文案(对应表 life_coupon),逻辑与 buildDiscountCouponRule 平行:查 essentialHolidayComparison、lifeDiscountCouponUnavailableRules(按 voucher_id),再拼接面值/使用规则/有效期等。
+     */
+    private String buildVoucherCouponRule(LifeCoupon lifeCoupon) {
+        // 与 buildDiscountCouponRule 一致:Calendar、当日日期、年份
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        Date currentDate = new Date();
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+        String formattedDate = dateFormat.format(currentDate);
+        int year = calendar.get(Calendar.YEAR);
+        // 查 essentialHolidayComparison 获取当日是否节假日
+        LambdaQueryWrapper<EssentialHolidayComparison> essentialHolidayComparisonLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        essentialHolidayComparisonLambdaQueryWrapper.eq(EssentialHolidayComparison::getParticularYear, String.valueOf(year));
+        List<EssentialHolidayComparison> essentialHolidayComparisons = essentialHolidayComparisonMapper.selectList(essentialHolidayComparisonLambdaQueryWrapper);
+        String festivalName = "";
+        for (EssentialHolidayComparison essentialHolidayComparison : essentialHolidayComparisons) {
+            if (essentialHolidayComparison.getFestivalDate() != null && formattedDate.equals(essentialHolidayComparison.getFestivalDate().toString())) {
+                festivalName = essentialHolidayComparison.getFestivalName();
+            }
+        }
+        // 当日周几
+        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
+        String dayName = "";
+        switch (dayOfWeek) {
+            case Calendar.SUNDAY:
+                dayName = "日";
+                break;
+            case Calendar.MONDAY:
+                dayName = "一";
+                break;
+            case Calendar.TUESDAY:
+                dayName = "二";
+                break;
+            case Calendar.WEDNESDAY:
+                dayName = "三";
+                break;
+            case Calendar.THURSDAY:
+                dayName = "四";
+                break;
+            case Calendar.FRIDAY:
+                dayName = "五";
+                break;
+            case Calendar.SATURDAY:
+                dayName = "六";
+                break;
+            default:
+                break;
+        }
+        // 代金券按 voucher_id 查 life_discount_coupon_unavailable_rules(节假日 + 周中),与优惠券按 discount_coupon_id 查对应
+        String voucherId = lifeCoupon.getId();
+        LambdaQueryWrapper<LifeDiscountCouponUnavailableRules> holidayWrapper = new LambdaQueryWrapper<>();
+        holidayWrapper.eq(LifeDiscountCouponUnavailableRules::getVoucherId, voucherId);
+        holidayWrapper.eq(LifeDiscountCouponUnavailableRules::getUnavailableRuleType, DiscountCouponEnum.HOLIDAY_UNAVAILABLE.getValue());
+        List<LifeDiscountCouponUnavailableRules> holidayRules = lifeDiscountCouponUnavailableRulesMapper.selectList(holidayWrapper);
+        String forbiddenRule = "";
+        if (!holidayRules.isEmpty()) {
+            forbiddenRule += HOLIDAY_RULE_TITLE;
+            forbiddenRule += holidayRules.stream().map(LifeDiscountCouponUnavailableRules::getUnavailableRuleValue).collect(Collectors.joining(", "));
+            forbiddenRule += NOT_USE;
+        }
+        LambdaQueryWrapper<LifeDiscountCouponUnavailableRules> weekWrapper = new LambdaQueryWrapper<>();
+        weekWrapper.eq(LifeDiscountCouponUnavailableRules::getVoucherId, voucherId);
+        weekWrapper.eq(LifeDiscountCouponUnavailableRules::getUnavailableRuleType, DiscountCouponEnum.WEEKDAY_UNAVAILABLE.getValue());
+        List<LifeDiscountCouponUnavailableRules> weekRules = lifeDiscountCouponUnavailableRulesMapper.selectList(weekWrapper);
+        if (!weekRules.isEmpty()) {
+            forbiddenRule += WEEK_RULE_TITLE;
+            forbiddenRule += weekRules.stream().map(LifeDiscountCouponUnavailableRules::getUnavailableRuleValue).collect(Collectors.joining(", "));
+            forbiddenRule += NOT_USE;
+        }
+        // life_coupon 表字段:面值、使用规则、使用有效期、购买须知使用时间
+        forbiddenRule += "面值:" + (lifeCoupon.getPrice() != null ? lifeCoupon.getPrice() : "");
+        if (!StringUtils.isEmpty(lifeCoupon.getUseRule())) {
+            forbiddenRule += "\n使用规则:" + lifeCoupon.getUseRule();
+        }
+        if (lifeCoupon.getStartDate() != null || lifeCoupon.getEndDate() != null) {
+            forbiddenRule += "\n使用有效期:";
+            if (lifeCoupon.getStartDate() != null) {
+                forbiddenRule += new SimpleDateFormat("yyyy-MM-dd").format(lifeCoupon.getStartDate());
+            }
+            forbiddenRule += " 至 ";
+            if (lifeCoupon.getEndDate() != null) {
+                forbiddenRule += new SimpleDateFormat("yyyy-MM-dd").format(lifeCoupon.getEndDate());
+            }
+        }
+        if (!StringUtils.isEmpty(lifeCoupon.getBuyUseStartTime()) || !StringUtils.isEmpty(lifeCoupon.getBuyUseEndTime())) {
+            forbiddenRule += "\n购买须知使用时间:";
+            if (!StringUtils.isEmpty(lifeCoupon.getBuyUseStartTime())) {
+                forbiddenRule += lifeCoupon.getBuyUseStartTime();
+            }
+            forbiddenRule += " 至 ";
+            if (!StringUtils.isEmpty(lifeCoupon.getBuyUseEndTime())) {
+                forbiddenRule += lifeCoupon.getBuyUseEndTime();
+            }
+        }
         return forbiddenRule;
     }
 
+    
     /**
      * <p>
      * 获取优惠券状态

+ 339 - 173
alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponStoreFriendServiceImpl.java

@@ -22,6 +22,7 @@ import shop.alien.util.common.constant.DiscountCouponEnum;
 
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
@@ -48,6 +49,8 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
 
     private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
 
+    private final LifeCouponMapper lifeCouponMapper;
+
     private final LifeDiscountCouponStoreFriendMapper lifeDiscountCouponStoreFriendMapper;
 
     private final LifeDiscountCouponUserMapper lifeDiscountCouponUserMapper;
@@ -137,109 +140,152 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
      */
     public boolean setFriendCoupon(UserLoginInfo userLoginInfo, LifeDiscountCouponStoreFriendDto lifeDiscountCouponStoreFriendDto) {
         try {
-            // 从传入的 DTO 对象中获取要处理的优惠券列表
             List<LifeDiscountCouponStoreFriendDto> coupons = lifeDiscountCouponStoreFriendDto.getCouponIds();
-            // 遍历优惠券列表,对每个优惠券进行处理
+            if (CollectionUtils.isEmpty(coupons)) {
+                return true;
+            }
             for (LifeDiscountCouponStoreFriendDto couponDto : coupons) {
-                // 根据优惠券 ID 从数据库中查询对应的优惠券信息
-                LifeDiscountCoupon lifeDiscountCoupon = lifeDiscountCouponMapper.selectById(couponDto.getCouponId());
-                // 检查优惠券的库存数量是否足够发放,如果库存数小于要发放的数量,则拦截操作并返回 false
-                if ((lifeDiscountCoupon.getSingleQty() - couponDto.getSingleQty()) < 0) {
-                    return false;
-                }
-                // 根据优惠券所属店铺 ID 从数据库中查询对应的店铺用户信息
-                StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, lifeDiscountCoupon.getStoreId()));
-                // 检查该优惠券是否已经发放给指定的好友,如果发放过则获取发放记录
-                LifeDiscountCouponStoreFriend lifeDiscountCouponStoreFriend = lifeDiscountCouponStoreFriendMapper.selectOne(
-                        new LambdaQueryWrapper<LifeDiscountCouponStoreFriend>()
-                                // 匹配优惠券 ID
-                                .eq(LifeDiscountCouponStoreFriend::getCouponId, couponDto.getCouponId())
-                                // 匹配店铺用户 ID
-                                .eq(LifeDiscountCouponStoreFriend::getStoreUserId, lifeDiscountCouponStoreFriendDto.getFriendStoreUserId())
-                                // 匹配好友店铺用户 ID
-                                .eq(LifeDiscountCouponStoreFriend::getFriendStoreUserId, userLoginInfo.getUserId())
-                );
-
-                if (lifeDiscountCouponStoreFriend == null) {
-                    // 如果该优惠券还没有发放给指定的好友,创建一个新的发放记录
-                    lifeDiscountCouponStoreFriend = new LifeDiscountCouponStoreFriend();
-                    // 将传入的 DTO 对象的属性复制到新的发放记录对象中
-                    BeanUtils.copyProperties(lifeDiscountCouponStoreFriendDto, lifeDiscountCouponStoreFriend);
-                    // 设置优惠券 ID
-                    lifeDiscountCouponStoreFriend.setCouponId(couponDto.getCouponId());
-                    // 设置优惠券的过期日期
-                    lifeDiscountCouponStoreFriend.setExpirationDate(lifeDiscountCoupon.getExpirationDate());
-                    // 设置优惠券的开始日期
-                    lifeDiscountCouponStoreFriend.setStartDate(lifeDiscountCoupon.getStartDate());
-                    // 设置优惠券的结束日期
-                    lifeDiscountCouponStoreFriend.setEndDate(lifeDiscountCoupon.getEndDate());
-                    // 设置好友店铺用户 ID
-                    lifeDiscountCouponStoreFriend.setFriendStoreUserId(storeUser.getId());
-                    // 设置要发放的优惠券数量
-                    lifeDiscountCouponStoreFriend.setSingleQty(couponDto.getSingleQty());
-                    // 设置店铺用户 ID
-                    lifeDiscountCouponStoreFriend.setStoreUserId(lifeDiscountCouponStoreFriendDto.getFriendStoreUserId());
-                    // 设置优惠券的发布状态
-                    lifeDiscountCouponStoreFriend.setReleaseType(1);
-                    // 将新的发放记录插入到数据库中
-                    lifeDiscountCouponStoreFriendMapper.insert(lifeDiscountCouponStoreFriend);
-                } else {
-                    // 如果该优惠券已经发放给指定的好友,增加发放记录中的优惠券数量
-                    lifeDiscountCouponStoreFriend.setSingleQty(lifeDiscountCouponStoreFriend.getSingleQty() + couponDto.getSingleQty());
-                    // 更新数据库中的发放记录
-                    lifeDiscountCouponStoreFriendMapper.updateById(lifeDiscountCouponStoreFriend);
-                }
-                // 减少优惠券的库存数量
-                lifeDiscountCoupon.setSingleQty(lifeDiscountCoupon.getSingleQty() - couponDto.getSingleQty());
-                // 更新数据库中的优惠券库存信息
-                lifeDiscountCouponMapper.updateById(lifeDiscountCoupon);
-
-                int friendStoreId =  lifeDiscountCouponStoreFriendDto.getFriendStoreUserId();
-                LambdaQueryWrapper<StoreUser> storeUserLambdaQueryWrapper = new LambdaQueryWrapper<>();
-                storeUserLambdaQueryWrapper.eq(StoreUser::getStoreId, friendStoreId);
-                List<StoreUser> storeUserList = storeUserMapper.selectList(storeUserLambdaQueryWrapper);
-
-                if(CollectionUtils.isNotEmpty(storeUserList)){
-                    StoreUser friendStoreUser = storeUserList.get(0);
-                    String friendPhone = friendStoreUser.getPhone();
-                    if(StringUtils.isNotEmpty(friendPhone)){
-                        // 获取发送优惠券的店铺名称
-                        String storeName = userLoginInfo.getUserName(); // 默认使用用户名
-                        // 根据当前登录用户ID查询店铺用户信息
-                        StoreUser currentStoreUser = storeUserMapper.selectById(userLoginInfo.getUserId());
-                        if (currentStoreUser != null && currentStoreUser.getStoreId() != null) {
-                            // 根据storeId查询店铺信息,获取店铺名称
-                            StoreInfo currentStoreInfo = storeInfoMapper.selectById(currentStoreUser.getStoreId());
-                            if (currentStoreInfo != null && currentStoreInfo.getStoreName() != null) {
-                                storeName = currentStoreInfo.getStoreName();
-                            }
-                        }
-                        // 发送好友优惠券通知
-                        LifeNotice lifeMessage = new LifeNotice();
-                        String text = "您的好友"+storeName+"送了您"+couponDto.getSingleQty()+"张店铺优惠券,快去使用吧!";
-                        JSONObject jsonObject = new JSONObject();
-                        jsonObject.put("message", text);
-                        lifeMessage.setReceiverId("store_"+friendPhone);
-                        lifeMessage.setTitle("赠券通知");
-                        lifeMessage.setContext(jsonObject.toJSONString());
-                        lifeMessage.setNoticeType(1);
-                        lifeMessage.setIsRead(0);
-                        lifeMessage.setDeleteFlag(0);
-                        lifeMessage.setSenderId("system");
-                        lifeNoticeMapper.insert(lifeMessage);
+                if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(couponDto.getVoucherId())) {
+                    // 代金券:入参为 voucherId,对应 life_coupon 表
+                    if (!handleVoucherFriendCoupon(userLoginInfo, lifeDiscountCouponStoreFriendDto, couponDto)) {
+                        return false;
+                    }
+                } else if (couponDto.getCouponId() != null) {
+                    // 优惠券:入参为 couponId,对应 life_discount_coupon 表
+                    if (!handleDiscountFriendCoupon(userLoginInfo, lifeDiscountCouponStoreFriendDto, couponDto)) {
+                        return false;
                     }
                 }
             }
-            // 如果所有优惠券都成功处理,返回 true
             return true;
         } catch (BeansException e) {
-            // 捕获 Bean 复制过程中可能出现的异常,并打印异常堆栈信息
             e.printStackTrace();
-            // 出现异常时返回 false
             return false;
         }
     }
 
+    /** 处理代金券(life_coupon)发放好友 */
+    private boolean handleVoucherFriendCoupon(UserLoginInfo userLoginInfo, LifeDiscountCouponStoreFriendDto dto, LifeDiscountCouponStoreFriendDto couponDto) {
+        LifeCoupon lifeCoupon = lifeCouponMapper.selectById(couponDto.getVoucherId());
+        if (lifeCoupon == null) {
+            return false;
+        }
+        int qty = couponDto.getSingleQty() != null ? couponDto.getSingleQty() : 0;
+        if (lifeCoupon.getSingleQty() == null || (lifeCoupon.getSingleQty() - qty) < 0) {
+            return false;
+        }
+        StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, lifeCoupon.getStoreId()));
+        if (storeUser == null) {
+            return false;
+        }
+        LambdaQueryWrapper<LifeDiscountCouponStoreFriend> wrapper = new LambdaQueryWrapper<LifeDiscountCouponStoreFriend>()
+                .eq(LifeDiscountCouponStoreFriend::getVoucherId, couponDto.getVoucherId())
+                .eq(LifeDiscountCouponStoreFriend::getStoreUserId, dto.getFriendStoreUserId())
+                .eq(LifeDiscountCouponStoreFriend::getFriendStoreUserId, userLoginInfo.getUserId());
+        LifeDiscountCouponStoreFriend friend = lifeDiscountCouponStoreFriendMapper.selectOne(wrapper);
+
+        if (friend == null) {
+            friend = new LifeDiscountCouponStoreFriend();
+            friend.setVoucherId(couponDto.getVoucherId());
+            friend.setCouponId(null);
+            friend.setExpirationDate(lifeCoupon.getExpirationDate());
+            if (lifeCoupon.getStartDate() != null) {
+                friend.setStartDate(lifeCoupon.getStartDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+            }
+            if (lifeCoupon.getEndDate() != null) {
+                friend.setEndDate(lifeCoupon.getEndDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+            }
+            friend.setFriendStoreUserId(storeUser.getId());
+            friend.setSingleQty(qty);
+            friend.setStoreUserId(dto.getFriendStoreUserId());
+            friend.setReleaseType(1);
+            lifeDiscountCouponStoreFriendMapper.insert(friend);
+        } else {
+            friend.setSingleQty(friend.getSingleQty() + qty);
+            lifeDiscountCouponStoreFriendMapper.updateById(friend);
+        }
+        lifeCoupon.setSingleQty(lifeCoupon.getSingleQty() - qty);
+        lifeCouponMapper.updateById(lifeCoupon);
+        sendFriendCouponNotice(dto.getFriendStoreUserId(), userLoginInfo, qty, true);
+        return true;
+    }
+
+    /** 处理优惠券(life_discount_coupon)发放好友 */
+    private boolean handleDiscountFriendCoupon(UserLoginInfo userLoginInfo, LifeDiscountCouponStoreFriendDto lifeDiscountCouponStoreFriendDto, LifeDiscountCouponStoreFriendDto couponDto) {
+        LifeDiscountCoupon lifeDiscountCoupon = lifeDiscountCouponMapper.selectById(couponDto.getCouponId());
+        if (lifeDiscountCoupon == null || (lifeDiscountCoupon.getSingleQty() - couponDto.getSingleQty()) < 0) {
+            return false;
+        }
+        StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, lifeDiscountCoupon.getStoreId()));
+        LifeDiscountCouponStoreFriend lifeDiscountCouponStoreFriend = lifeDiscountCouponStoreFriendMapper.selectOne(
+                new LambdaQueryWrapper<LifeDiscountCouponStoreFriend>()
+                        .eq(LifeDiscountCouponStoreFriend::getCouponId, couponDto.getCouponId())
+                        .eq(LifeDiscountCouponStoreFriend::getStoreUserId, lifeDiscountCouponStoreFriendDto.getFriendStoreUserId())
+                        .eq(LifeDiscountCouponStoreFriend::getFriendStoreUserId, userLoginInfo.getUserId())
+        );
+
+        if (lifeDiscountCouponStoreFriend == null) {
+            lifeDiscountCouponStoreFriend = new LifeDiscountCouponStoreFriend();
+            BeanUtils.copyProperties(lifeDiscountCouponStoreFriendDto, lifeDiscountCouponStoreFriend);
+            lifeDiscountCouponStoreFriend.setCouponId(couponDto.getCouponId());
+            lifeDiscountCouponStoreFriend.setVoucherId(null);
+            lifeDiscountCouponStoreFriend.setExpirationDate(lifeDiscountCoupon.getExpirationDate());
+            lifeDiscountCouponStoreFriend.setStartDate(lifeDiscountCoupon.getStartDate());
+            lifeDiscountCouponStoreFriend.setEndDate(lifeDiscountCoupon.getEndDate());
+            lifeDiscountCouponStoreFriend.setFriendStoreUserId(storeUser.getId());
+            lifeDiscountCouponStoreFriend.setSingleQty(couponDto.getSingleQty());
+            lifeDiscountCouponStoreFriend.setStoreUserId(lifeDiscountCouponStoreFriendDto.getFriendStoreUserId());
+            lifeDiscountCouponStoreFriend.setReleaseType(1);
+            lifeDiscountCouponStoreFriendMapper.insert(lifeDiscountCouponStoreFriend);
+        } else {
+            lifeDiscountCouponStoreFriend.setSingleQty(lifeDiscountCouponStoreFriend.getSingleQty() + couponDto.getSingleQty());
+            lifeDiscountCouponStoreFriendMapper.updateById(lifeDiscountCouponStoreFriend);
+        }
+        lifeDiscountCoupon.setSingleQty(lifeDiscountCoupon.getSingleQty() - couponDto.getSingleQty());
+        lifeDiscountCouponMapper.updateById(lifeDiscountCoupon);
+        sendFriendCouponNotice(lifeDiscountCouponStoreFriendDto.getFriendStoreUserId(), userLoginInfo, couponDto.getSingleQty(), false);
+        return true;
+    }
+
+    /** 发送赠券通知:isVoucher true=代金券,false=优惠券 */
+    private void sendFriendCouponNotice(Integer friendStoreUserId, UserLoginInfo userLoginInfo, int qty, boolean isVoucher) {
+        if (friendStoreUserId == null) {
+            return;
+        }
+        LambdaQueryWrapper<StoreUser> storeUserLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        storeUserLambdaQueryWrapper.eq(StoreUser::getStoreId, friendStoreUserId);
+        List<StoreUser> storeUserList = storeUserMapper.selectList(storeUserLambdaQueryWrapper);
+        if (CollectionUtils.isEmpty(storeUserList)) {
+            return;
+        }
+        StoreUser friendStoreUser = storeUserList.get(0);
+        String friendPhone = friendStoreUser.getPhone();
+        if (friendPhone == null || friendPhone.trim().isEmpty()) {
+            return;
+        }
+        String storeName = userLoginInfo.getUserName();
+        StoreUser currentStoreUser = storeUserMapper.selectById(userLoginInfo.getUserId());
+        if (currentStoreUser != null && currentStoreUser.getStoreId() != null) {
+            StoreInfo currentStoreInfo = storeInfoMapper.selectById(currentStoreUser.getStoreId());
+            if (currentStoreInfo != null && currentStoreInfo.getStoreName() != null) {
+                storeName = currentStoreInfo.getStoreName();
+            }
+        }
+        LifeNotice lifeMessage = new LifeNotice();
+        String couponTypeName = isVoucher ? "代金券" : "优惠券";
+        String text = "您的好友" + storeName + "送了您" + qty + "张" + couponTypeName + ",快去使用吧!";
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("message", text);
+        lifeMessage.setReceiverId("store_" + friendPhone);
+        lifeMessage.setTitle("赠券通知");
+        lifeMessage.setContext(jsonObject.toJSONString());
+        lifeMessage.setNoticeType(1);
+        lifeMessage.setIsRead(0);
+        lifeMessage.setDeleteFlag(0);
+        lifeMessage.setSenderId("system");
+        lifeNoticeMapper.insert(lifeMessage);
+    }
+
     /**
      * 为当前用户发放店铺为好友设定的可用优惠券。
      *
@@ -432,23 +478,38 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
 
     @Override
     public LifeDiscountCouponFriendRuleVo saveFriendCouponRule(LifeDiscountCouponFriendRule lifeDiscountCouponFriendRule) {
+        List<LifeDiscountCouponFriendRuleDetail> details = lifeDiscountCouponFriendRule.getDetails();
+        // 按列表中是否传入代金券id区分:有 voucherId 走代金券逻辑,否则走优惠券逻辑
+        if (ObjectUtils.isNotEmpty(details)) {
+            for (LifeDiscountCouponFriendRuleDetail detail : details) {
+                if (detail.getVoucherId() != null && !detail.getVoucherId().trim().isEmpty()) {
+                    detail.setCouponId(null);
+                } else {
+                    detail.setVoucherId(null);
+                }
+            }
+        }
         if (ObjectUtils.isNotEmpty(lifeDiscountCouponFriendRule.getId())) {
-            List<LifeDiscountCouponFriendRuleDetail> details = lifeDiscountCouponFriendRule.getDetails();
             lifeDiscountCouponFriendRuleDetailMapper.delete(new LambdaQueryWrapper<LifeDiscountCouponFriendRuleDetail>().eq(LifeDiscountCouponFriendRuleDetail::getRuleId, lifeDiscountCouponFriendRule.getId()));
-            for (LifeDiscountCouponFriendRuleDetail detail : details) {
-                detail.setRuleId(lifeDiscountCouponFriendRule.getId());
-                detail.setStoreId(lifeDiscountCouponFriendRule.getStoreId());
+            if (ObjectUtils.isNotEmpty(details)) {
+                for (LifeDiscountCouponFriendRuleDetail detail : details) {
+                    detail.setRuleId(lifeDiscountCouponFriendRule.getId());
+                    detail.setStoreId(lifeDiscountCouponFriendRule.getStoreId());
+                }
+                lifeDiscountCouponFriendRuleDetailMapper.insertList(details);
             }
-            lifeDiscountCouponFriendRuleDetailMapper.insertList(details);
             lifeDiscountCouponFriendRuleMapper.updateById(lifeDiscountCouponFriendRule);
-        }else {
+        } else {
             lifeDiscountCouponFriendRuleMapper.insert(lifeDiscountCouponFriendRule);
-            if (ObjectUtils.isNotEmpty(lifeDiscountCouponFriendRule.getDetails())) {
-                lifeDiscountCouponFriendRule.getDetails().forEach(detail -> detail.setRuleId(lifeDiscountCouponFriendRule.getId()));
-                lifeDiscountCouponFriendRuleDetailMapper.insertList(lifeDiscountCouponFriendRule.getDetails());
+            if (ObjectUtils.isNotEmpty(details)) {
+                details.forEach(detail -> {
+                    detail.setRuleId(lifeDiscountCouponFriendRule.getId());
+                    detail.setStoreId(lifeDiscountCouponFriendRule.getStoreId());
+                });
+                lifeDiscountCouponFriendRuleDetailMapper.insertList(details);
             }
         }
-        return null;
+        return getRuleById(lifeDiscountCouponFriendRule.getId().toString());
     }
 
     @Override
@@ -465,17 +526,27 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
      * @return 领取的优惠券列表
      */
     @Override
-    public List<LifeDiscountCouponFriendRuleDetailVo> getReceivedFriendCouponList(String storeId,String friendStoreUserId) {
+    public List<LifeDiscountCouponFriendRuleDetailVo> getReceivedFriendCouponList(String storeId, String friendStoreUserId, Integer type) {
         QueryWrapper<LifeDiscountCouponFriendRuleDetailVo> queryWrapper = new QueryWrapper<>();
         queryWrapper.eq("ldcsf.store_user_id", storeId);
         queryWrapper.eq("ldcsf.delete_flag", 0);
+        if (Integer.valueOf(4).equals(type)) {
+            // 代金券:创建时为启用状态 2,需查出 release_type=1 与 2 的 LifeDiscountCouponStoreFriend 数据
+            queryWrapper.in("ldcsf.release_type", 1, 2);
+            queryWrapper.isNotNull("ldcsf.voucher_id");
+            if (StringUtils.isEmpty(friendStoreUserId)) {
+                queryWrapper.groupBy("ldcsf.friend_store_user_id").orderByDesc("couponNum");
+            } else {
+                queryWrapper.eq("ldcsf.friend_store_user_id", friendStoreUserId).groupBy("ldcsf.voucher_id").orderByDesc("couponNum");
+            }
+            return lifeDiscountCouponFriendRuleDetailMapper.getReceivedFriendVoucherList(queryWrapper);
+        }
+        // 优惠券:保持原逻辑,只查 release_type=1
         queryWrapper.eq("ldcsf.release_type", 1);
-        //查询送过优惠券的店铺
+        queryWrapper.isNotNull("ldcsf.coupon_id");
         if (StringUtils.isEmpty(friendStoreUserId)) {
             queryWrapper.groupBy("ldcsf.friend_store_user_id").orderByDesc("couponNum");
-        }
-        //查询选中店铺送的优惠券
-        else {
+        } else {
             queryWrapper.eq("ldcsf.friend_store_user_id", friendStoreUserId).groupBy("ldcsf.coupon_id").orderByDesc("couponNum");
         }
         return lifeDiscountCouponFriendRuleDetailMapper.getReceivedFriendCouponList(queryWrapper);
@@ -483,15 +554,34 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
 
     @Override
     public List<LifeDiscountCouponFriendRuleVo> getRuleList(String storeId, String acName, String status) {
+        if (StringUtils.isEmpty(storeId)) {
+            return new ArrayList<>();
+        }
         List<LifeDiscountCouponFriendRuleVo> ruleList = lifeDiscountCouponFriendRuleDetailMapper.getRuleList(storeId);
+        if (ruleList == null) {
+            ruleList = new ArrayList<>();
+        }
         if (ObjectUtils.isNotEmpty(ruleList)) {
-            ruleList.forEach(i -> i.setStatus(i.getEndDate().after(new Date()) ? "0" : "1"));
+            Date now = new Date();
+            ruleList.forEach(i -> {
+                if (i.getEndDate() != null) {
+                    i.setStatus(i.getEndDate().after(now) ? "0" : "1");
+                } else {
+                    i.setStatus(null);
+                }
+            });
         }
         if (StringUtils.isNotEmpty(acName)) {
-            ruleList = ruleList.stream().filter(i -> i.getAcName().contains(acName)).collect(Collectors.toList());
+            String name = acName;
+            ruleList = ruleList.stream()
+                    .filter(i -> i.getAcName() != null && i.getAcName().contains(name))
+                    .collect(Collectors.toList());
         }
         if (StringUtils.isNotEmpty(status)) {
-            ruleList = ruleList.stream().filter(i -> i.getStatus().equals(status)).collect(Collectors.toList());
+            String s = status;
+            ruleList = ruleList.stream()
+                    .filter(i -> i.getStatus() != null && i.getStatus().equals(s))
+                    .collect(Collectors.toList());
         }
         return ruleList;
     }
@@ -509,76 +599,152 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
     }
 
     @Override
-    public List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(String storeUserId, String friendStoreUserId,String storeName) {
-        QueryWrapper<LifeDiscountCouponFriendRuleVo> queryWrapper = new QueryWrapper<>();
-        queryWrapper.eq(StringUtils.isNotEmpty(storeUserId),"ldcsf.store_user_id", storeUserId)
-                .eq(StringUtils.isNotEmpty(friendStoreUserId),"ldcsf.friend_store_user_id", friendStoreUserId)
-                .like(StringUtils.isNotEmpty(storeName),"si.store_name", storeName);
+    public List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(String storeUserId, String friendStoreUserId, String storeName, Integer type) {
+        boolean voucherOnly = Integer.valueOf(4).equals(type);
         if (StringUtils.isNotEmpty(storeUserId)) {
-            return lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponList(queryWrapper);
+            // 好友赠我
+            if (voucherOnly) {
+                QueryWrapper<LifeDiscountCouponFriendRuleVo> q = new QueryWrapper<>();
+                q.eq("ldcsf.store_user_id", storeUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.voucher_id");
+                if (StringUtils.isNotEmpty(storeName)) q.like("si.store_name", storeName);
+                return lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListVoucher(q);
+            }
+            QueryWrapper<LifeDiscountCouponFriendRuleVo> qCoupon = new QueryWrapper<>();
+            qCoupon.eq("ldcsf.store_user_id", storeUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.coupon_id");
+            if (StringUtils.isNotEmpty(storeName)) qCoupon.like("si.store_name", storeName);
+            List<LifeDiscountCouponFriendRuleVo> list = lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponList(qCoupon);
+            if (type != null) return list;
+            QueryWrapper<LifeDiscountCouponFriendRuleVo> qVoucher = new QueryWrapper<>();
+            qVoucher.eq("ldcsf.store_user_id", storeUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.voucher_id");
+            if (StringUtils.isNotEmpty(storeName)) qVoucher.like("si.store_name", storeName);
+            List<LifeDiscountCouponFriendRuleVo> voucherList = lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListVoucher(qVoucher);
+            list = new ArrayList<>(list);
+            list.addAll(voucherList);
+            list.sort((a, b) -> (b.getEndDate() != null && a.getEndDate() != null) ? b.getEndDate().compareTo(a.getEndDate()) : 0);
+            return list;
         }
         if (StringUtils.isNotEmpty(friendStoreUserId)) {
-            return lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListwzhy(queryWrapper);
-        }
-        else {
-            return new ArrayList<>();
+            // 我赠好友
+            if (voucherOnly) {
+                QueryWrapper<LifeDiscountCouponFriendRuleVo> q = new QueryWrapper<>();
+                q.eq("ldcsf.friend_store_user_id", friendStoreUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.voucher_id");
+                if (StringUtils.isNotEmpty(storeName)) q.like("si.store_name", storeName);
+                return lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListwzhyVoucher(q);
+            }
+            QueryWrapper<LifeDiscountCouponFriendRuleVo> qCoupon = new QueryWrapper<>();
+            qCoupon.eq("ldcsf.friend_store_user_id", friendStoreUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.coupon_id");
+            if (StringUtils.isNotEmpty(storeName)) qCoupon.like("si.store_name", storeName);
+            List<LifeDiscountCouponFriendRuleVo> list = lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListwzhy(qCoupon);
+            if (type != null) return list;
+            QueryWrapper<LifeDiscountCouponFriendRuleVo> qVoucher = new QueryWrapper<>();
+            qVoucher.eq("ldcsf.friend_store_user_id", friendStoreUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.voucher_id");
+            if (StringUtils.isNotEmpty(storeName)) qVoucher.like("si.store_name", storeName);
+            List<LifeDiscountCouponFriendRuleVo> voucherList = lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListwzhyVoucher(qVoucher);
+            list = new ArrayList<>(list);
+            list.addAll(voucherList);
+            list.sort((a, b) -> (b.getEndDate() != null && a.getEndDate() != null) ? b.getEndDate().compareTo(a.getEndDate()) : 0);
+            return list;
         }
+        return new ArrayList<>();
     }
 
     /**
-     *  商家优惠券活动
-     * 好评送券:用户对店铺好评且通过AI审核后,将店铺可用券发放到用户卡包
+     * 好评送券:按优惠券/代金券ID发放到用户券包,发放成功后扣减库存;根据是优惠券还是代金券发送对应通知。
      */
-    @Override
-    public int issueCouponForGoodRating(Integer userId, Integer storeId) {
-        if (userId == null || storeId == null) {
-            return 0;
-        }
-        LocalDate currentDate = LocalDate.now();
-        LambdaQueryWrapper<LifeDiscountCoupon> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(LifeDiscountCoupon::getStoreId, String.valueOf(storeId))
-                .eq(LifeDiscountCoupon::getGetStatus, DiscountCouponEnum.CAN_GET.getValue())
-                .ge(LifeDiscountCoupon::getValidDate, currentDate)
-                .gt(LifeDiscountCoupon::getSingleQty, 0);
-        List<LifeDiscountCoupon> coupons = lifeDiscountCouponMapper.selectList(queryWrapper);
-        if (CollectionUtils.isEmpty(coupons)) {
-            return 0;
-        }
-        int granted = 0;
-        int commenterUserId = userId.intValue();
-        StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
-        LifeUser lifeUser = lifeUserMapper.selectById(commenterUserId);
-        for (LifeDiscountCoupon coupon : coupons) {
-            try {
-                LifeDiscountCouponUser lifeDiscountCouponUser = new LifeDiscountCouponUser();
-                lifeDiscountCouponUser.setCouponId(coupon.getId());
-                lifeDiscountCouponUser.setUserId(commenterUserId);
-                lifeDiscountCouponUser.setReceiveTime(new Date());
-                lifeDiscountCouponUser.setExpirationTime(coupon.getValidDate());
-                lifeDiscountCouponUser.setStatus(Integer.parseInt(DiscountCouponEnum.WAITING_USED.getValue()));
-                lifeDiscountCouponUser.setDeleteFlag(0);
-                lifeDiscountCouponUserMapper.insert(lifeDiscountCouponUser);
-                coupon.setSingleQty(coupon.getSingleQty() - 1);
-                lifeDiscountCouponMapper.updateById(coupon);
-                granted++;
-            } catch (Exception e) {
-                // 单张发放失败不影响其余
-            }
-        }
-        if (granted > 0 && lifeUser != null && storeInfo != null) {
-            LifeNotice lifeNotice = new LifeNotice();
-            lifeNotice.setSenderId("system");
-            lifeNotice.setReceiverId("user_" + lifeUser.getUserPhone());
-            String text = "您对好友店铺「" + storeInfo.getStoreName() + "」的好评已通过审核,已为您发放" + granted + "张优惠券,快去我的券包查看吧~";
-            JSONObject jsonObject = new JSONObject();
-            jsonObject.put("message", text);
-            lifeNotice.setContext(jsonObject.toJSONString());
-            lifeNotice.setNoticeType(1);
-            lifeNotice.setTitle("好评送券");
-            lifeNotice.setIsRead(0);
-            lifeNotice.setDeleteFlag(0);
-            lifeNoticeMapper.insert(lifeNotice);
-        }
-        return granted;
-    }
+//    @Override
+//    public int issueCouponForGoodRating(Integer userId, Integer storeId, Integer couponId, Integer voucherId) {
+//        if (userId == null || storeId == null) {
+//            return 0;
+//        }
+//        if ((couponId == null || couponId <= 0) && (voucherId == null || voucherId <= 0)) {
+//            return 0;
+//        }
+//        int commenterUserId = userId.intValue();
+//        StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
+//        LifeUser lifeUser = lifeUserMapper.selectById(commenterUserId);
+//        int grantedCoupon = 0;
+//        int grantedVoucher = 0;
+//
+//        // 按优惠券ID发放:发放1张,扣减库存,发优惠券通知
+//        if (couponId != null && couponId > 0) {
+//            LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(couponId);
+//            if (coupon != null && String.valueOf(storeId).equals(coupon.getStoreId())
+//                    && coupon.getSingleQty() != null && coupon.getSingleQty() > 0) {
+//                try {
+//                    LifeDiscountCouponUser lifeDiscountCouponUser = new LifeDiscountCouponUser();
+//                    lifeDiscountCouponUser.setCouponId(coupon.getId());
+//                    lifeDiscountCouponUser.setUserId(commenterUserId);
+//                    lifeDiscountCouponUser.setReceiveTime(new Date());
+//                    lifeDiscountCouponUser.setExpirationTime(coupon.getValidDate());
+//                    lifeDiscountCouponUser.setStatus(Integer.parseInt(DiscountCouponEnum.WAITING_USED.getValue()));
+//                    lifeDiscountCouponUser.setDeleteFlag(0);
+//                    lifeDiscountCouponUserMapper.insert(lifeDiscountCouponUser);
+//                    coupon.setSingleQty(coupon.getSingleQty() - 1);
+//                    lifeDiscountCouponMapper.updateById(coupon);
+//                    grantedCoupon = 1;
+//                } catch (Exception e) {
+//
+//                }
+//            }
+//        }
+//
+//        // 按代金券ID发放:发放1张,扣减库存,发代金券通知
+//        if (voucherId != null && voucherId > 0) {
+//            LifeCoupon lifeCoupon = lifeCouponMapper.selectById(voucherId);
+//            if (lifeCoupon != null && String.valueOf(storeId).equals(lifeCoupon.getStoreId())
+//                    && lifeCoupon.getSingleQty() != null && lifeCoupon.getSingleQty() > 0) {
+//                try {
+//                    LifeDiscountCouponUser lifeDiscountCouponUser = new LifeDiscountCouponUser();
+//                    lifeDiscountCouponUser.setVoucherId(lifeCoupon.getId());
+//                    lifeDiscountCouponUser.setUserId(commenterUserId);
+//                    lifeDiscountCouponUser.setReceiveTime(new Date());
+//                    if (lifeCoupon.getEndDate() != null) {
+//                        lifeDiscountCouponUser.setExpirationTime(lifeCoupon.getEndDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+//                    }
+//                    lifeDiscountCouponUser.setStatus(Integer.parseInt(DiscountCouponEnum.WAITING_USED.getValue()));
+//                    lifeDiscountCouponUser.setDeleteFlag(0);
+//                    lifeDiscountCouponUserMapper.insert(lifeDiscountCouponUser);
+//                    lifeCoupon.setSingleQty(lifeCoupon.getSingleQty() - 1);
+//                    lifeCouponMapper.updateById(lifeCoupon);
+//                    grantedVoucher = 1;
+//                } catch (Exception e) {
+//
+//                }
+//            }
+//        }
+//
+//        // 根据发放的是优惠券还是代金券发送对应通知
+//        if (lifeUser != null && storeInfo != null) {
+//            if (grantedCoupon > 0) {
+//                LifeNotice couponNotice = new LifeNotice();
+//                couponNotice.setSenderId("system");
+//                couponNotice.setReceiverId("user_" + lifeUser.getUserPhone());
+//                String couponText = "您对店铺「" + storeInfo.getStoreName() + "」的好评已通过审核,已为您发放优惠券,快去我的券包查看吧~";
+//                JSONObject couponJson = new JSONObject();
+//                couponJson.put("message", couponText);
+//                couponNotice.setContext(couponJson.toJSONString());
+//                couponNotice.setNoticeType(1);
+//                couponNotice.setTitle("好评送券");
+//                couponNotice.setIsRead(0);
+//                couponNotice.setDeleteFlag(0);
+//                lifeNoticeMapper.insert(couponNotice);
+//            }
+//            if (grantedVoucher > 0) {
+//                LifeNotice voucherNotice = new LifeNotice();
+//                voucherNotice.setSenderId("system");
+//                voucherNotice.setReceiverId("user_" + lifeUser.getUserPhone());
+//                String voucherText = "您对店铺「" + storeInfo.getStoreName() + "」的好评已通过审核,已为您发放代金券,快去我的券包查看吧~";
+//                JSONObject voucherJson = new JSONObject();
+//                voucherJson.put("message", voucherText);
+//                voucherNotice.setContext(voucherJson.toJSONString());
+//                voucherNotice.setNoticeType(1);
+//                voucherNotice.setTitle("好评送代金券");
+//                voucherNotice.setIsRead(0);
+//                voucherNotice.setDeleteFlag(0);
+//                lifeNoticeMapper.insert(voucherNotice);
+//            }
+//        }
+//
+//        return grantedCoupon + grantedVoucher;
+//    }
 }

+ 137 - 14
alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java

@@ -45,6 +45,7 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
     private final WebSocketProcess webSocketProcess;
     private final AiFeedbackAssignUtils aiFeedbackAssignUtils;
     private final LifeSysMapper lifeSysMapper;
+    private final LifeUserMapper lifeUserMapper;
 
     @Override
     public R<String> submitFeedback(LifeFeedbackDto dto) {
@@ -606,10 +607,18 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
                 return R.fail("反馈ID不能为空");
             }
 
-            // 2. 更新状态为已解决
+            // 2. 查询原始反馈记录(用于发送通知)
+            LifeFeedback feedback = lifeFeedbackMapper.selectById(statusDto.getFeedbackId());
+            if (feedback == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 3. 更新状态
             LifeFeedback updateFeedback = new LifeFeedback();
             updateFeedback.setId(statusDto.getFeedbackId());
-            updateFeedback.setHandleStatus(1); // 已解决
+            // 如果DTO中有状态值,使用DTO的值;否则默认为已解决(1)
+            Integer handleStatus = statusDto.getHandleStatus() != null ? statusDto.getHandleStatus() : 1;
+            updateFeedback.setHandleStatus(handleStatus);
             updateFeedback.setUpdateTime(new Date());
 
             boolean result = this.updateById(updateFeedback);
@@ -621,6 +630,11 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
             String logContent = "问题已解决";
             saveFeedbackLog(statusDto.getFeedbackId(), 0, logContent);
 
+            // 5. 如果状态为已解决,发送问题已解决通知给用户
+            if (handleStatus == 1) {
+                sendFeedbackResolvedNotice(feedback);
+            }
+
             return R.success("更新成功");
         } catch (Exception e) {
             log.error("中台-更新反馈状态失败", e);
@@ -656,6 +670,7 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
     private void sendFeedbackReplyNotice(LifeFeedback feedback, String replyContent) {
         try {
             String receiverId = null;
+            String userPhone = null;
             
             // 根据反馈来源判断是用户端还是商家端
             if (feedback.getFeedbackSource() == null) {
@@ -663,25 +678,30 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
                 return;
             }
             
-            // userId对应store_user表的id,统一从store_user表查询
             if (feedback.getUserId() == null) {
                 log.warn("用户ID为空,无法发送通知,feedbackId={}", feedback.getId());
                 return;
             }
             
-            StoreUser storeUser = storeUserMapper.selectById(feedback.getUserId());
-            if (storeUser == null || storeUser.getPhone() == null || storeUser.getPhone().trim().isEmpty()) {
-                log.warn("未找到商户用户信息或手机号为空,无法发送通知,userId={}", feedback.getUserId());
-                return;
-            }
-            
-            // 根据feedbackSource设置不同的接收者ID格式
+            // 根据feedbackSource查询对应的用户表
             if (feedback.getFeedbackSource() == 0) {
-                // 用户端 - 使用user_手机号格式
-                receiverId = "user_" + storeUser.getPhone();
+                // 用户端 - 查询life_user表
+                LifeUser lifeUser = lifeUserMapper.selectById(feedback.getUserId());
+                if (lifeUser == null || lifeUser.getUserPhone() == null || lifeUser.getUserPhone().trim().isEmpty()) {
+                    log.warn("未找到用户信息或手机号为空,无法发送通知,userId={}", feedback.getUserId());
+                    return;
+                }
+                userPhone = lifeUser.getUserPhone();
+                receiverId = "user_" + userPhone;
             } else if (feedback.getFeedbackSource() == 1) {
-                // 商家端 - 使用store_手机号格式
-                receiverId = "store_" + storeUser.getPhone();
+                // 商家端 - 查询store_user表
+                StoreUser storeUser = storeUserMapper.selectById(feedback.getUserId());
+                if (storeUser == null || storeUser.getPhone() == null || storeUser.getPhone().trim().isEmpty()) {
+                    log.warn("未找到商户用户信息或手机号为空,无法发送通知,userId={}", feedback.getUserId());
+                    return;
+                }
+                userPhone = storeUser.getPhone();
+                receiverId = "store_" + userPhone;
             } else {
                 log.warn("未知的反馈来源,feedbackSource={}, feedbackId={}", feedback.getFeedbackSource(), feedback.getId());
                 return;
@@ -728,6 +748,109 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
     }
 
     /**
+     * 发送问题已解决通知给用户
+     * @param feedback 反馈记录
+     */
+    private void sendFeedbackResolvedNotice(LifeFeedback feedback) {
+        try {
+            String receiverId = null;
+            
+            // 根据反馈来源判断是用户端还是商家端
+            if (feedback.getFeedbackSource() == null) {
+                log.warn("反馈来源为空,无法发送通知,feedbackId={}", feedback.getId());
+                return;
+            }
+            
+            if (feedback.getUserId() == null) {
+                log.warn("用户ID为空,无法发送通知,feedbackId={}", feedback.getId());
+                return;
+            }
+            
+            // 根据feedbackSource查询对应的用户表
+            if (feedback.getFeedbackSource() == 0) {
+                // 用户端 - 查询life_user表
+                LifeUser lifeUser = lifeUserMapper.selectById(feedback.getUserId());
+                if (lifeUser == null || lifeUser.getUserPhone() == null || lifeUser.getUserPhone().trim().isEmpty()) {
+                    log.warn("未找到用户信息或手机号为空,无法发送通知,userId={}", feedback.getUserId());
+                    return;
+                }
+                receiverId = "user_" + lifeUser.getUserPhone();
+            } else if (feedback.getFeedbackSource() == 1) {
+                // 商家端 - 查询store_user表
+                StoreUser storeUser = storeUserMapper.selectById(feedback.getUserId());
+                if (storeUser == null || storeUser.getPhone() == null || storeUser.getPhone().trim().isEmpty()) {
+                    log.warn("未找到商户用户信息或手机号为空,无法发送通知,userId={}", feedback.getUserId());
+                    return;
+                }
+                receiverId = "store_" + storeUser.getPhone();
+            } else {
+                log.warn("未知的反馈来源,feedbackSource={}, feedbackId={}", feedback.getFeedbackSource(), feedback.getId());
+                return;
+            }
+            
+            // 根据反馈类型生成通知消息
+            String feedbackTypeText = "";
+            if (feedback.getFeedbackType() != null) {
+                switch (feedback.getFeedbackType()) {
+                    case 0:
+                        feedbackTypeText = "BUG";
+                        break;
+                    case 1:
+                        feedbackTypeText = "优化";
+                        break;
+                    case 2:
+                        feedbackTypeText = "功能";
+                        break;
+                    default:
+                        feedbackTypeText = "BUG/优化/功能";
+                        break;
+                }
+            } else {
+                feedbackTypeText = "BUG/优化/功能";
+            }
+            String message = "您反馈的" + feedbackTypeText + "问题已解决,感谢您的反馈";
+            
+            // 构建通知消息
+            JSONObject messageJson = new JSONObject();
+            messageJson.put("feedbackId", feedback.getId());
+            messageJson.put("message", message);
+
+            // 创建通知记录
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setReceiverId(receiverId);
+            lifeNotice.setContext(messageJson.toJSONString());
+            lifeNotice.setTitle("意见反馈解决通知");
+            lifeNotice.setSenderId("system");
+            lifeNotice.setIsRead(0);
+            lifeNotice.setNoticeType(1); // 1-系统通知
+            lifeNotice.setBusinessId(feedback.getId());
+
+            // 保存通知
+            lifeNoticeMapper.insert(lifeNotice);
+
+            // 通过WebSocket发送实时通知
+            WebSocketVo webSocketVo = new WebSocketVo();
+            webSocketVo.setSenderId("system");
+            webSocketVo.setReceiverId(receiverId);
+            webSocketVo.setCategory("notice");
+            webSocketVo.setNoticeType("1");
+            webSocketVo.setIsRead(0);
+            webSocketVo.setText(JSONObject.toJSONString(lifeNotice));
+
+            try {
+                webSocketProcess.sendMessage(receiverId, JSONObject.toJSONString(webSocketVo));
+                log.info("问题已解决通知发送成功,feedbackId={}, receiverId={}", feedback.getId(), receiverId);
+            } catch (Exception e) {
+                log.error("发送WebSocket通知失败,feedbackId={}, receiverId={}, error={}",
+                        feedback.getId(), receiverId, e.getMessage());
+            }
+
+        } catch (Exception e) {
+            log.error("发送问题已解决通知异常,feedbackId={}, error={}", feedback.getId(), e.getMessage(), e);
+        }
+    }
+
+    /**
      * 调用AI分配跟踪人员
      * @param feedback 反馈记录
      * @return 分配的跟踪人员ID(对应life_sys表的id),失败返回null

+ 5 - 4
alien-store/src/main/java/shop/alien/store/service/impl/LifeUserViolationServiceImpl.java

@@ -104,6 +104,8 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
     @Override
     public int reporting(LifeUserViolation lifeuserViolation) throws Exception {
         try {
+            lifeuserViolation.setProcessingStatus("0");
+            lifeuserViolation.setDeleteFlag(0);
             if ("4".equals(lifeuserViolation.getReportContextType())) {
                 LambdaQueryWrapper<SecondGoodsRecord> goodsWrapper = new LambdaQueryWrapper<>();
                 goodsWrapper.eq(SecondGoodsRecord::getGoodsId, lifeuserViolation.getBusinessId());
@@ -129,9 +131,8 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 //                lifeUserViolationMapper.updateById(lifeuserViolation);
 
 
-                //String phoneId = Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getString("userType") + "_" + JwtUtil.getCurrentUserInfo().getString("phone");
-                    // 举报人消息
-                  /*  LifeNotice lifeNotice = getLifeNotice(lifeuserViolation);
+                // 举报人消息
+                    LifeNotice lifeNotice = getLifeNotice(lifeuserViolation);
                     lifeNoticeMapper.insert(lifeNotice);
                     WebSocketVo websocketVo = new WebSocketVo();
                     websocketVo.setSenderId("system");
@@ -156,7 +157,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
                             websocketVoReported.setText(com.alibaba.fastjson2.JSONObject.from(lifeNoticeReported).toJSONString());
                             webSocketProcess.sendMessage(lifeNoticeReported.getReceiverId(), com.alibaba.fastjson2.JSONObject.from(websocketVoReported).toJSONString());
                         }
-                    }*/
+                    }
                 return result;
             }
         } catch (Exception e) {

+ 4 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreCuisineServiceImpl.java

@@ -176,6 +176,10 @@ public class StoreCuisineServiceImpl extends ServiceImpl<StoreCuisineMapper, Sto
         data.setPeopleLimit(base.getPeopleLimit());
         data.setUsageRule(base.getUsageRule());
         data.setRawJson(base.getRawJson());
+        data.setCreateTime(base.getCreatedTime());
+        data.setAuditTime(base.getAuditTime());
+        data.setAuditStatus(base.getStatus());
+        data.setShelfStatus(base.getShelfStatus());
         // 补充门店电话/名称(仅用于详情返回,不落库)
         StoreInfo storeInfo = storeInfoMapper.selectById(base.getStoreId());
         if (storeInfo != null) {

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

@@ -340,10 +340,17 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         }
 
         // 统计评价数量(common_rating 表,业务类型1=商铺评价)
+        // 排除:1. 被删除的评论(delete_flag != 0)
+        //       2. 审核状态为2(驳回)的评论(audit_status = 2)
+        // 包含:审核状态为0(待审核)、1(通过)或null的评论
         LambdaQueryWrapper<CommonRating> ratingWrapper = new LambdaQueryWrapper<>();
         ratingWrapper.eq(CommonRating::getBusinessType, 1); // 业务类型:1-商铺评价
         ratingWrapper.eq(CommonRating::getBusinessId, id); // 门店ID
         ratingWrapper.eq(CommonRating::getDeleteFlag, 0); // 未删除
+        // 只包含审核状态为0(待审核)、1(通过)或null的评论,排除审核状态为2(驳回)的评论
+        ratingWrapper.and(wrapper -> wrapper.in(CommonRating::getAuditStatus, 0, 1)
+                .or()
+                .isNull(CommonRating::getAuditStatus));
         Integer ratingCountLong = commonRatingMapper.selectCount(ratingWrapper);
         storeMainInfoVo.setRatingCount(ratingCountLong != null ? ratingCountLong.intValue() : 0);
 
@@ -6725,12 +6732,12 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
 
     @Override
     public int uploadBusinessLicense(StoreImg storeImg) {
-        // 删除旧的营业执照图片(逻辑删除)
-        LambdaUpdateWrapper<StoreImg> deleteWrapper = new LambdaUpdateWrapper<>();
-        deleteWrapper.eq(StoreImg::getStoreId, storeImg.getStoreId())
-                .eq(StoreImg::getImgType, 14)
-                .set(StoreImg::getDeleteFlag, 1);
-        storeImgMapper.update(null, deleteWrapper);
+        // 不再立即删除旧的营业执照图片,改为在审核通过后再删除
+        // LambdaUpdateWrapper<StoreImg> deleteWrapper = new LambdaUpdateWrapper<>();
+        // deleteWrapper.eq(StoreImg::getStoreId, storeImg.getStoreId())
+        //         .eq(StoreImg::getImgType, 14)
+        //         .set(StoreImg::getDeleteFlag, 1);
+        // storeImgMapper.update(null, deleteWrapper);
         
         // 不再立即插入新的营业执照图片,等审核通过后再插入
         // 插入营业执照历史记录(licenseStatus=1表示营业执照,licenseExecuteStatus=2表示审核中)
@@ -6815,12 +6822,12 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         // 限制最多9张
         int maxCount = Math.min(storeImgList.size(), 9);
         
-        // 删除旧的其他资质证明图片(逻辑删除)
-        LambdaUpdateWrapper<StoreImg> deleteWrapper = new LambdaUpdateWrapper<>();
-        deleteWrapper.eq(StoreImg::getStoreId, storeId)
-                .eq(StoreImg::getImgType, 35)
-                .set(StoreImg::getDeleteFlag, 1);
-        storeImgMapper.update(null, deleteWrapper);
+        // 不再立即删除旧的其他资质证明图片,等审核通过后再替换
+        // LambdaUpdateWrapper<StoreImg> deleteWrapper = new LambdaUpdateWrapper<>();
+        // deleteWrapper.eq(StoreImg::getStoreId, storeId)
+        //         .eq(StoreImg::getImgType, 35)
+        //         .set(StoreImg::getDeleteFlag, 1);
+        // storeImgMapper.update(null, deleteWrapper);
         
         // 获取门店信息用于AI审核
         StoreInfo storeInfoForAi = storeInfoMapper.selectById(storeId);

+ 20 - 4
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityAchievementServiceImpl.java

@@ -18,6 +18,7 @@ import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCas
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseItemVo;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseVo;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityCasePreviewVo;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityAchievementMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
 import shop.alien.store.dto.StoreOperationalActivityAchievementDto;
@@ -127,11 +128,26 @@ public class StoreOperationalActivityAchievementServiceImpl implements StoreOper
     }
 
     @Override
-    public IPage<StoreOperationalActivityAchievementCaseVo> listCasePage(Integer activityStatus, Integer pageNum, Integer pageSize) {
+    public IPage<StoreOperationalActivityAchievementCaseVo> listCasePage(Integer storeId, Integer activityStatus, Integer pageNum, Integer pageSize) {
+        if (storeId == null) {
+            throw new IllegalArgumentException("店铺ID不能为空");
+        }
         int current = pageNum == null || pageNum <= 0 ? 1 : pageNum;
         int size = pageSize == null || pageSize <= 0 ? 10 : pageSize;
         Page<StoreOperationalActivityAchievementCaseVo> page = new Page<>(current, size);
-        return achievementMapper.selectCasePage(page, activityStatus);
+        return achievementMapper.selectCasePage(page, storeId, activityStatus);
+    }
+
+    @Override
+    public StoreOperationalActivityCasePreviewVo listCasePreview(Integer storeId) {
+        if (storeId == null) {
+            throw new IllegalArgumentException("店铺ID不能为空");
+        }
+        StoreOperationalActivityCasePreviewVo vo = new StoreOperationalActivityCasePreviewVo();
+        IPage<StoreOperationalActivityAchievementCaseVo> page = listCasePage(storeId, null, 1, 1);
+        vo.setTotal(page.getTotal());
+        vo.setList(page.getRecords() != null ? page.getRecords() : new ArrayList<>());
+        return vo;
     }
 
     @Override
@@ -166,7 +182,7 @@ public class StoreOperationalActivityAchievementServiceImpl implements StoreOper
     }
 
     @Override
-    public IPage<StoreOperationalActivityAchievementCaseVo> listStoreCasePage(Integer storeId, Integer hasResult,
+    public IPage<StoreOperationalActivityAchievementCaseVo> listStoreCasePage(Integer storeId, Integer activityStatus,
                                                                                String activityName, Integer pageNum, Integer pageSize) {
         if (storeId == null || storeId <= 0) {
             throw new IllegalArgumentException("商户ID不能为空");
@@ -174,7 +190,7 @@ public class StoreOperationalActivityAchievementServiceImpl implements StoreOper
         int current = pageNum == null || pageNum <= 0 ? 1 : pageNum;
         int size = pageSize == null || pageSize <= 0 ? 10 : pageSize;
         Page<StoreOperationalActivityAchievementCaseVo> page = new Page<>(current, size);
-        return achievementMapper.selectStoreCasePage(page, storeId, hasResult, activityName);
+        return achievementMapper.selectStoreCasePage(page, storeId, activityStatus, activityName);
     }
 
     private List<String> splitMediaUrls(String mediaUrls) {

+ 15 - 1
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityServiceImpl.java

@@ -237,6 +237,9 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
         if (dto.getUserId() == null) {
             throw new IllegalArgumentException("用户ID不能为空");
         }
+        if (dto.getPhone() == null || dto.getPhone().trim().isEmpty()) {
+            throw new IllegalArgumentException("手机号不能为空");
+        }
 
         StoreOperationalActivity activity = activityMapper.selectById(dto.getActivityId());
         if (activity == null) {
@@ -256,6 +259,11 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
                 throw new IllegalArgumentException("已成功报名,请勿重复报名");
             }
 
+            Integer samePhoneCount = signupMapper.countByActivityIdAndPhone(dto.getActivityId(), dto.getPhone().trim());
+            if (samePhoneCount != null && samePhoneCount > 0) {
+                throw new IllegalArgumentException("同一活动同一手机号已报名,不允许重复报名");
+            }
+
             Integer approvedCount = signupMapper.countSignupByActivityId(dto.getActivityId());
             Integer limitPeople = activity.getActivityLimitPeople();
             if (limitPeople != null && limitPeople > 0 && approvedCount != null && approvedCount >= limitPeople) {
@@ -504,6 +512,12 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
         if (activityStatus != null && activityStatus == 5 && auditStatus == 2) {
             return hasAchievement ? 3 : 4;
         }
+        if (activityStatus != null && activityStatus == 5) {
+            if (auditStatus == 1) {
+                return 5;
+            }
+        }
+
         if (activityStatus != null && activityStatus == 2) {
             if (auditStatus != null && auditStatus == 1) {
                 return 5;
@@ -513,7 +527,7 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
             }
         }
         if (auditStatus != null && auditStatus == 0
-                && startTime != null && nowDay.before(DateUtils.toDateOnly(startTime))) {
+                && startTime != null && !nowDay.after(DateUtils.toDateOnly(startTime))) {
             return 7;
         }
         return null;

+ 190 - 2
alien-store/src/main/java/shop/alien/store/service/impl/TrackEventServiceImpl.java

@@ -44,6 +44,9 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     private final LifeDiscountCouponUserMapper lifeDiscountCouponUserMapper;
     private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
     private final LifeDiscountCouponStoreFriendMapper lifeDiscountCouponStoreFriendMapper;
+    private final LifeBlacklistMapper lifeBlacklistMapper;
+    private final LifeUserMapper lifeUserMapper;
+    private final StoreInfoMapper storeInfoMapper;
     
     private static final String REDIS_QUEUE_KEY = "track:event:queue";
 
@@ -343,9 +346,21 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     /**
      * 计算好友数量(互相关注的用户数)
      * 注意:好友数量是累计数据,统计截止到endDate的所有互相关注关系
+     * 需要过滤被拉黑的用户,与用户资料页面保持一致
      */
     private long calculateFriendCount(String storePhoneId, Date startDate, Date endDate) {
         try {
+            // 获取店铺的拉黑信息(用于过滤被拉黑的用户)
+            StoreUser storeUser = getStoreUserByPhoneId(storePhoneId);
+            if (storeUser == null) {
+                return 0L;
+            }
+            String blockerType = "1"; // 店铺类型
+            String blockerId = String.valueOf(storeUser.getId());
+            
+            // 获取被拉黑的用户ID集合
+            Set<String> blockedUserIds = getBlockedUserIds(blockerType, blockerId);
+            
             // 查询店铺关注的所有用户(截止到统计日期)
             LambdaQueryWrapper<LifeFans> followWrapper = new LambdaQueryWrapper<>();
             followWrapper.eq(LifeFans::getFansId, storePhoneId)
@@ -353,6 +368,7 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
                     .eq(LifeFans::getDeleteFlag, 0);
             Set<String> followedIds = lifeFansMapper.selectList(followWrapper).stream()
                     .map(LifeFans::getFollowedId)
+                    .filter(id -> isUserValid(id) && !isBlocked(id, blockedUserIds))
                     .collect(Collectors.toSet());
             
             if (followedIds.isEmpty()) {
@@ -366,6 +382,7 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
                     .eq(LifeFans::getDeleteFlag, 0);
             Set<String> fansIds = lifeFansMapper.selectList(fansWrapper).stream()
                     .map(LifeFans::getFansId)
+                    .filter(id -> isUserValid(id) && !isBlocked(id, blockedUserIds))
                     .collect(Collectors.toSet());
             
             // 互相关注的用户数 = 交集
@@ -380,14 +397,35 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     /**
      * 计算关注数量(店铺用户关注的人数)
      * 注意:关注数量是累计数据,统计截止到endDate的所有关注关系
+     * 需要过滤被拉黑的用户,与用户资料页面保持一致
      */
     private long calculateFollowCount(String storePhoneId, Date startDate, Date endDate) {
         try {
+            // 获取店铺的拉黑信息(用于过滤被拉黑的用户)
+            StoreUser storeUser = getStoreUserByPhoneId(storePhoneId);
+            if (storeUser == null) {
+                return 0L;
+            }
+            String blockerType = "1"; // 店铺类型
+            String blockerId = String.valueOf(storeUser.getId());
+            
+            // 获取被拉黑的用户ID集合
+            Set<String> blockedUserIds = getBlockedUserIds(blockerType, blockerId);
+            
+            // 查询店铺关注的所有用户(截止到统计日期),并过滤被拉黑的用户
             LambdaQueryWrapper<LifeFans> wrapper = new LambdaQueryWrapper<>();
             wrapper.eq(LifeFans::getFansId, storePhoneId)
                     .lt(LifeFans::getCreatedTime, endDate)
                     .eq(LifeFans::getDeleteFlag, 0);
-            return lifeFansMapper.selectCount(wrapper);
+            List<LifeFans> fansList = lifeFansMapper.selectList(wrapper);
+            
+            // 过滤被拉黑的用户和已删除的用户
+            long count = fansList.stream()
+                    .map(LifeFans::getFollowedId)
+                    .filter(id -> isUserValid(id) && !isBlocked(id, blockedUserIds))
+                    .count();
+            
+            return count;
         } catch (Exception e) {
             log.error("计算关注数量失败: storePhoneId={}", storePhoneId, e);
             return 0L;
@@ -397,14 +435,35 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
     /**
      * 计算粉丝数量(关注店铺用户的人数)
      * 注意:粉丝数量是累计数据,统计截止到endDate的所有粉丝关系
+     * 需要过滤被拉黑的用户,与用户资料页面保持一致
      */
     private long calculateFansCount(String storePhoneId, Date startDate, Date endDate) {
         try {
+            // 获取店铺的拉黑信息(用于过滤被拉黑的用户)
+            StoreUser storeUser = getStoreUserByPhoneId(storePhoneId);
+            if (storeUser == null) {
+                return 0L;
+            }
+            String blockerType = "1"; // 店铺类型
+            String blockerId = String.valueOf(storeUser.getId());
+            
+            // 获取被拉黑的用户ID集合
+            Set<String> blockedUserIds = getBlockedUserIds(blockerType, blockerId);
+            
+            // 查询关注店铺的所有用户(截止到统计日期),并过滤被拉黑的用户
             LambdaQueryWrapper<LifeFans> wrapper = new LambdaQueryWrapper<>();
             wrapper.eq(LifeFans::getFollowedId, storePhoneId)
                     .lt(LifeFans::getCreatedTime, endDate)
                     .eq(LifeFans::getDeleteFlag, 0);
-            return lifeFansMapper.selectCount(wrapper);
+            List<LifeFans> fansList = lifeFansMapper.selectList(wrapper);
+            
+            // 过滤被拉黑的用户和已删除的用户
+            long count = fansList.stream()
+                    .map(LifeFans::getFansId)
+                    .filter(id -> isUserValid(id) && !isBlocked(id, blockedUserIds))
+                    .count();
+            
+            return count;
         } catch (Exception e) {
             log.error("计算粉丝数量失败: storePhoneId={}", storePhoneId, e);
             return 0L;
@@ -1053,5 +1112,134 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
             return 0L;
         }
     }
+    
+    /**
+     * 根据phoneId获取StoreUser
+     */
+    private StoreUser getStoreUserByPhoneId(String storePhoneId) {
+        try {
+            if (storePhoneId == null || !storePhoneId.startsWith("store_")) {
+                return null;
+            }
+            String phone = storePhoneId.substring(6); // 去掉 "store_" 前缀
+            LambdaQueryWrapper<StoreUser> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(StoreUser::getPhone, phone)
+                    .eq(StoreUser::getDeleteFlag, 0)
+                    .last("LIMIT 1");
+            return storeUserMapper.selectOne(wrapper);
+        } catch (Exception e) {
+            log.error("获取StoreUser失败: storePhoneId={}", storePhoneId, e);
+            return null;
+        }
+    }
+    
+    /**
+     * 获取被拉黑的用户phoneId集合
+     * @param blockerType 拉黑方类型("1"=店铺, "2"=用户)
+     * @param blockerId 拉黑方ID
+     * @return 被拉黑的用户phoneId集合
+     */
+    private Set<String> getBlockedUserIds(String blockerType, String blockerId) {
+        try {
+            LambdaQueryWrapper<LifeBlacklist> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LifeBlacklist::getBlockerType, blockerType)
+                    .eq(LifeBlacklist::getBlockerId, blockerId)
+                    .eq(LifeBlacklist::getDeleteFlag, 0);
+            List<LifeBlacklist> blacklist = lifeBlacklistMapper.selectList(wrapper);
+            
+            // 将被拉黑的用户ID转换为phoneId集合
+            Set<String> blockedPhoneIds = new HashSet<>();
+            for (LifeBlacklist item : blacklist) {
+                String blockedType = item.getBlockedType();
+                String blockedId = item.getBlockedId();
+                
+                // 根据blockedType和blockedId查询对应的phoneId
+                if ("1".equals(blockedType)) {
+                    // 店铺类型
+                    StoreUser storeUser = storeUserMapper.selectById(blockedId);
+                    if (storeUser != null && storeUser.getPhone() != null) {
+                        blockedPhoneIds.add("store_" + storeUser.getPhone());
+                    }
+                } else if ("2".equals(blockedType)) {
+                    // 用户类型 - 直接查询LifeUser表获取phoneId
+                    LifeUser lifeUser = lifeUserMapper.selectById(blockedId);
+                    if (lifeUser != null && lifeUser.getUserPhone() != null) {
+                        blockedPhoneIds.add("user_" + lifeUser.getUserPhone());
+                    }
+                }
+            }
+            return blockedPhoneIds;
+        } catch (Exception e) {
+            log.error("获取被拉黑用户列表失败: blockerType={}, blockerId={}", blockerType, blockerId, e);
+            return new HashSet<>();
+        }
+    }
+    
+    /**
+     * 判断phoneId是否被拉黑
+     * @param phoneId 用户phoneId
+     * @param blockedUserIds 被拉黑的用户phoneId集合
+     * @return true表示被拉黑,false表示未被拉黑
+     */
+    private boolean isBlocked(String phoneId, Set<String> blockedUserIds) {
+        return blockedUserIds != null && blockedUserIds.contains(phoneId);
+    }
+    
+    /**
+     * 检查用户是否存在且未被删除(与用户资料页面的SQL逻辑保持一致)
+     * @param phoneId 用户phoneId(格式:store_手机号 或 user_手机号)
+     * @return true表示用户存在且未被删除,false表示用户不存在或已被删除
+     */
+    private boolean isUserValid(String phoneId) {
+        try {
+            if (phoneId == null || !phoneId.contains("_")) {
+                return false;
+            }
+            
+            String[] parts = phoneId.split("_", 2);
+            if (parts.length < 2) {
+                return false;
+            }
+            
+            String type = parts[0];
+            String phone = parts[1];
+            
+            if ("store".equals(type)) {
+                // 检查店铺用户是否存在且未被删除
+                LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
+                userWrapper.eq(StoreUser::getPhone, phone)
+                        .eq(StoreUser::getDeleteFlag, 0)
+                        .last("LIMIT 1");
+                StoreUser storeUser = storeUserMapper.selectOne(userWrapper);
+                
+                if (storeUser == null || storeUser.getStoreId() == null) {
+                    return false;
+                }
+                
+                // 检查店铺信息是否存在且未被删除
+                LambdaQueryWrapper<shop.alien.entity.store.StoreInfo> infoWrapper = new LambdaQueryWrapper<>();
+                infoWrapper.eq(shop.alien.entity.store.StoreInfo::getId, storeUser.getStoreId())
+                        .eq(shop.alien.entity.store.StoreInfo::getDeleteFlag, 0)
+                        .last("LIMIT 1");
+                shop.alien.entity.store.StoreInfo storeInfo = storeInfoMapper.selectOne(infoWrapper);
+                
+                return storeInfo != null;
+            } else if ("user".equals(type)) {
+                // 检查普通用户是否存在且未被删除
+                LambdaQueryWrapper<LifeUser> userWrapper = new LambdaQueryWrapper<>();
+                userWrapper.eq(LifeUser::getUserPhone, phone)
+                        .eq(LifeUser::getDeleteFlag, 0)
+                        .last("LIMIT 1");
+                LifeUser lifeUser = lifeUserMapper.selectOne(userWrapper);
+                
+                return lifeUser != null;
+            }
+            
+            return false;
+        } catch (Exception e) {
+            log.error("检查用户有效性失败: phoneId={}", phoneId, e);
+            return false;
+        }
+    }
 
 }

+ 145 - 0
alien-store/src/main/java/shop/alien/store/util/ai/AiUserSessionUtil.java

@@ -0,0 +1,145 @@
+package shop.alien.store.util.ai;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.http.*;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.HttpServerErrorException;
+import org.springframework.web.client.RestTemplate;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * AI用户会话工具类
+ * 调用AI服务获取用户会话列表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Component
+@RefreshScope
+@RequiredArgsConstructor
+public class AiUserSessionUtil {
+
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+
+    private RestTemplate restTemplate;
+
+    @Value("${ai.service.user-session-url:http://124.93.18.180:9000/ai/smart-customer/api/v1/udian_qa/user-sessions}")
+    private String userSessionUrl;
+
+    /**
+     * 初始化 RestTemplate,设置超时时间
+     */
+    @PostConstruct
+    public void initRestTemplate() {
+        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
+        // 设置连接超时时间为 30 秒
+        factory.setConnectTimeout(30000);
+        // 设置读取超时时间为 30 秒
+        factory.setReadTimeout(30000);
+        this.restTemplate = new RestTemplate(factory);
+    }
+
+    /**
+     * 获取用户会话列表
+     *
+     * @param userId 用户ID
+     * @return 用户会话列表响应,包含 success, user_id, total, limit, offset, sessions
+     */
+    public JSONObject getUserSessions(Integer userId) {
+        if (userId == null || userId <= 0) {
+            log.warn("用户ID为空或无效,无法查询会话列表");
+            return buildErrorResponse("用户ID不能为空");
+        }
+
+        // 获取AI服务访问令牌
+        String accessToken = aiAuthTokenUtil.getAccessToken();
+        if (!StringUtils.hasText(accessToken)) {
+            log.error("获取AI服务访问令牌失败,无法调用用户会话接口");
+            return buildErrorResponse("获取AI服务访问令牌失败");
+        }
+
+        try {
+            // 构建请求体
+            Map<String, Object> requestBody = new HashMap<>();
+            requestBody.put("user_id", userId);
+
+            // 构建请求头,添加Authorization
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.set("Authorization", "Bearer " + accessToken);
+
+            HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
+
+            log.info("调用AI用户会话接口,用户ID: {}", userId);
+            ResponseEntity<String> response = restTemplate.postForEntity(userSessionUrl, request, String.class);
+
+            if (response != null && response.getStatusCode() == HttpStatus.OK) {
+                String responseBody = response.getBody();
+                log.info("AI用户会话接口响应: {}", responseBody);
+
+                if (StringUtils.hasText(responseBody)) {
+                    JSONObject jsonObject = JSONObject.parseObject(responseBody);
+                    if (jsonObject != null) {
+                        Boolean success = jsonObject.getBoolean("success");
+                        if (Boolean.TRUE.equals(success)) {
+                            log.info("成功获取用户会话列表,用户ID: {}, 总会话数: {}", 
+                                    userId, jsonObject.getInteger("total"));
+                            return jsonObject;
+                        } else {
+                            String message = jsonObject.getString("message");
+                            log.warn("AI接口返回失败,用户ID: {}, message: {}", userId, message);
+                            return jsonObject;
+                        }
+                    }
+                }
+            } else {
+                log.error("AI用户会话接口调用失败,HTTP状态码: {}", 
+                        response != null ? response.getStatusCode() : "null");
+                return buildErrorResponse("AI用户会话接口调用失败");
+            }
+        } catch (HttpClientErrorException e) {
+            log.error("调用AI用户会话接口失败,HTTP客户端错误,状态码: {}, 响应体: {}, 用户ID: {}", 
+                    e.getStatusCode(), e.getResponseBodyAsString(), userId, e);
+            return buildErrorResponse("调用AI用户会话接口失败: " + e.getMessage());
+        } catch (HttpServerErrorException e) {
+            log.error("调用AI用户会话接口失败,HTTP服务器错误,状态码: {}, 响应体: {}, 用户ID: {}", 
+                    e.getStatusCode(), e.getResponseBodyAsString(), userId, e);
+            return buildErrorResponse("调用AI用户会话接口失败: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("调用AI用户会话接口异常,用户ID: {}", userId, e);
+            return buildErrorResponse("调用AI用户会话接口异常: " + e.getMessage());
+        }
+
+        return buildErrorResponse("获取用户会话列表失败");
+    }
+
+    /**
+     * 构建错误响应
+     *
+     * @param message 错误消息
+     * @return 错误响应JSON对象
+     */
+    private JSONObject buildErrorResponse(String message) {
+        JSONObject response = new JSONObject();
+        response.put("success", false);
+        response.put("message", message);
+        response.put("user_id", null);
+        response.put("total", 0);
+        response.put("limit", 20);
+        response.put("offset", 0);
+        response.put("sessions", new java.util.ArrayList<>());
+        return response;
+    }
+}
+