Explorar o código

Merge branch 'sit' into sit-OrderFood

lutong hai 1 mes
pai
achega
a021fd9f9a
Modificáronse 100 ficheiros con 5179 adicións e 373 borrados
  1. 2 2
      alien-entity/src/main/java/shop/alien/entity/store/CommonComment.java
  2. 9 1
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCoupon.java
  3. 7 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponFriendRule.java
  4. 8 1
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponFriendRuleDetail.java
  5. 8 4
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponStoreFriend.java
  6. 8 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponUnavailableRules.java
  7. 7 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponUser.java
  8. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeUserDynamics.java
  9. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreBanner.java
  10. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreClockIn.java
  11. 93 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreContract.java
  12. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreCuisine.java
  13. 1 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java
  14. 5 0
      alien-entity/src/main/java/shop/alien/entity/store/StorePrice.java
  15. 5 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreStaffConfig.java
  16. 17 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/CuisineTypeResponseDto.java
  17. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeDiscountCouponDto.java
  18. 4 1
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeDiscountCouponStoreFriendDto.java
  19. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreBannerDto.java
  20. 35 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreContractQueryDto.java
  21. 7 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreRenovationRequirementDto.java
  22. 8 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreStaffConfigListQueryDto.java
  23. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LawyerConsultationOrderVO.java
  24. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponFriendRuleDetailVo.java
  25. 16 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponFriendRuleVo.java
  26. 13 1
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponVo.java
  27. 31 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreContractVo.java
  28. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreMainInfoVo.java
  29. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreUserVo.java
  30. 16 4
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java
  31. 4 4
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/DiscountCouponPlatformVo.java
  32. 1 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/LifeCouponPlatformVo.java
  33. 25 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/OperationalActivityUpdateResultVo.java
  34. 6 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseDetailVo.java
  35. 6 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseVo.java
  36. 21 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityCasePreviewVo.java
  37. 11 6
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDTO.java
  38. 10 2
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDetailVo.java
  39. 6 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupVO.java
  40. 16 1
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityVO.java
  41. 27 0
      alien-entity/src/main/java/shop/alien/mapper/CommonCommentMapper.java
  42. 37 0
      alien-entity/src/main/java/shop/alien/mapper/CommonRatingMapper.java
  43. 30 20
      alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponFriendRuleDetailMapper.java
  44. 28 0
      alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponMapper.java
  45. 53 14
      alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponStoreFriendMapper.java
  46. 5 1
      alien-entity/src/main/java/shop/alien/mapper/LifeUserDynamicsMapper.java
  47. 6 5
      alien-entity/src/main/java/shop/alien/mapper/LifeUserMapper.java
  48. 3 3
      alien-entity/src/main/java/shop/alien/mapper/LifeUserViolationMapper.java
  49. 36 0
      alien-entity/src/main/java/shop/alien/mapper/StoreContractMapper.java
  50. 13 2
      alien-entity/src/main/java/shop/alien/mapper/StoreImgMapper.java
  51. 1 0
      alien-entity/src/main/java/shop/alien/mapper/StoreUserMapper.java
  52. 6 5
      alien-entity/src/main/java/shop/alien/mapper/second/SecondGoodsMapper.java
  53. 5 3
      alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivityAchievementMapper.java
  54. 10 0
      alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivitySignupMapper.java
  55. 17 0
      alien-entity/src/main/resources/mapper/CommonRatingMapper.xml
  56. 4 3
      alien-entity/src/main/resources/mapper/LifeDiscountCouponFriendRuleDetailMapper.xml
  57. 2 1
      alien-entity/src/main/resources/mapper/LifeDiscountCouponFriendRuleMapper.xml
  58. 27 4
      alien-entity/src/main/resources/mapper/LifeDiscountCouponMapper.xml
  59. 2 1
      alien-entity/src/main/resources/mapper/LifeDiscountCouponStoreFriendMapper.xml
  60. 2 2
      alien-entity/src/main/resources/mapper/LifeDiscountCouponUnavailableRulesMapper.xml
  61. 3 1
      alien-entity/src/main/resources/mapper/OrderReviewMapper.xml
  62. 84 0
      alien-entity/src/main/resources/mapper/StoreContractMapper.xml
  63. 29 60
      alien-entity/src/main/resources/mapper/StoreInfoMapper.xml
  64. 1 0
      alien-entity/src/main/resources/mapper/StoreUserMapper.xml
  65. 17 14
      alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivityAchievementMapper.xml
  66. 15 1
      alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivityMapper.xml
  67. 9 0
      alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivitySignupMapper.xml
  68. 125 54
      alien-job/src/main/java/shop/alien/job/store/StoreMembershipCardJob.java
  69. 75 1
      alien-job/src/main/java/shop/alien/job/store/StoreOperationalActivityJob.java
  70. 76 1
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/LawyerConsultationOrderServiceImpl.java
  71. 2 2
      alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsServiceImpl.java
  72. 45 3
      alien-store-platform/doc/OPERATIONAL_ACTIVITY_README.md
  73. 5 4
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/LifeCouponPlatformController.java
  74. 9 8
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java
  75. 259 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/PlatformStoreOperationalStatisticsController.java
  76. 128 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StoreContractController.java
  77. 6 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/LifeCouponPlatformService.java
  78. 2 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/LifeDiscountCouponPlatformService.java
  79. 79 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/PlatformStoreOperationalStatisticsService.java
  80. 73 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StoreContractService.java
  81. 108 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/LifeCouponPlatformServiceImpl.java
  82. 17 5
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/LifeDiscountCouponPlatformServiceImpl.java
  83. 8 5
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/MerchantAuthServiceImpl.java
  84. 178 13
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java
  85. 118 2
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivitySignupServiceImpl.java
  86. 1488 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/PlatformStoreOperationalStatisticsServiceImpl.java
  87. 243 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreContractServiceImpl.java
  88. 9 9
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreManageServiceImpl.java
  89. 18 3
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformUserRoleServiceImpl.java
  90. 80 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/util/AiAuthTokenUtil.java
  91. 4 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/util/AiContentModerationUtil.java
  92. 831 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/util/PlatformStatisticsComparisonImageUtil.java
  93. BIN=BIN
      alien-store-platform/src/main/resources/fonts/NotoSansCJKsc-Regular.otf
  94. 7 0
      alien-store/pom.xml
  95. 2 2
      alien-store/src/main/java/shop/alien/store/controller/AiAuditController.java
  96. 78 43
      alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java
  97. 105 0
      alien-store/src/main/java/shop/alien/store/controller/AiUserSessionController.java
  98. 10 40
      alien-store/src/main/java/shop/alien/store/controller/AliController.java
  99. 48 7
      alien-store/src/main/java/shop/alien/store/controller/BarPerformanceController.java
  100. 23 2
      alien-store/src/main/java/shop/alien/store/controller/CommonRatingController.java

+ 2 - 2
alien-entity/src/main/java/shop/alien/entity/store/CommonComment.java

@@ -28,11 +28,11 @@ public class CommonComment implements Serializable {
     @TableId(value = "id", type = IdType.AUTO)
     private Long id;
 
-    @ApiModelProperty(value = "评论来源类型:1-评价的评论 2-动态评论 3-打卡评论")
+    @ApiModelProperty(value = "评论来源类型:1-评价的评论 2-动态评论 3-打卡评论,4-二手商品评论")
     @TableField("source_type")
     private Integer sourceType;
 
-    @ApiModelProperty(value = "来源关联ID:source_type=1时=rating.id,=2时=动态ID,=3打卡id")
+    @ApiModelProperty(value = "来源关联ID:source_type=1时=rating.id,=2时=动态ID,=3打卡id,=4时=二手商品ID")
     @TableField("source_id")
     private Long sourceId;
 

+ 9 - 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;
 
@@ -146,6 +146,14 @@ public class LifeDiscountCoupon extends Model<LifeDiscountCoupon> {
     @TableField("valid_date")
     private LocalDate validDate;
 
+    @ApiModelProperty(value = "优惠券类型:1-满减券,2-折扣券")
+    @TableField("coupon_type")
+    private Integer couponType;
+
+    @ApiModelProperty(value = "折扣率(0-100,用于折扣券,例如80表示8折)")
+    @TableField("discount_rate")
+    private BigDecimal discountRate;
+
     @Override
     protected Serializable pkVal() {
         return this.id;

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

@@ -59,6 +59,13 @@ public class LifeDiscountCouponFriendRule {
     @TableLogic
     private Integer deleteFlag;
 
+    /**
+     * 优惠券类型:1-满减券,2-折扣券(仅优惠券有值,代金券为null)
+     */
+    @ApiModelProperty(value = "优惠券类型:1-满减券,2-折扣券(仅优惠券有值,代金券为null)")
+    @TableField("coupon_type")
+    private Integer couponType;
+
     @TableField(exist = false)
     private List<LifeDiscountCouponFriendRuleDetail> details;
 }

+ 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")

+ 8 - 4
alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponStoreFriend.java

@@ -32,15 +32,19 @@ public class LifeDiscountCouponStoreFriend extends Model<LifeDiscountCouponStore
     @TableId(value = "id", type = IdType.AUTO)
     private Integer id;
 
-    @ApiModelProperty(value = "店铺用户id")
+    @ApiModelProperty(value = "店铺ID(store_info.id),收到券的店铺")
     @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")
+    @ApiModelProperty(value = "代金券id,代金券时使用(life_coupon.id)")
+    @TableField("voucher_id")
+    private String voucherId;
+
+    @ApiModelProperty(value = "店铺用户ID(store_user.id),送券的用户")
     @TableField("friend_store_user_id")
     private Integer friendStoreUserId;
 
@@ -81,7 +85,7 @@ public class LifeDiscountCouponStoreFriend extends Model<LifeDiscountCouponStore
     @TableField(value = "updated_user_id", fill = FieldFill.INSERT_UPDATE)
     private Integer updatedUserId;
 
-    @ApiModelProperty(value = "发布状态")
+    @ApiModelProperty(value = "发布状态:0 未发布,1发布")
     @TableField("release_type")
     private Integer releaseType;
 

+ 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;

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

@@ -141,4 +141,8 @@ public class LifeUserDynamics {
     @ApiModelProperty(value = "审核失败原因")
     @TableField("reason")
     private String reason;
+
+    @ApiModelProperty(value = "封面图")
+    @TableField("cover_image")
+    private String coverImage;
 }

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

@@ -68,5 +68,9 @@ public class StoreBanner extends Model<StoreBanner> {
     @ApiModelProperty(value = "修改人ID")
     @TableField("updated_user_id")
     private Integer updatedUserId;
+
+    @ApiModelProperty(value = "店铺ID")
+    @TableField("store_id")
+    private Integer storeId;
 }
 

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

@@ -59,6 +59,10 @@ public class StoreClockIn extends Model<StoreClockIn> {
     @TableField("like_count")
     private Integer likeCount;
 
+    @ApiModelProperty(value = "浏览数量")
+    @TableField("view_count")
+    private Integer viewCount;
+
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic

+ 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;
 }
 
 

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

@@ -144,4 +144,9 @@ public class StoreStaffConfig extends Model<StoreStaffConfig> {
     @ApiModelProperty(value = "平均评分(1-5星,支持0.5星)")
     @TableField("service_score")
     private java.math.BigDecimal serviceScore;
+
+    @ApiModelProperty(value = "审核时间")
+    @TableField("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 createdTime;
+
+        @ApiModelProperty("审核时间")
+        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+        private Date auditTime;
+
+        @ApiModelProperty("审核状态")
+        private Integer auditStatus;
+
+        @ApiModelProperty("上下架状态")
+        private Integer shelfStatus;
+
     }
 }
 

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

@@ -113,4 +113,10 @@ public class LifeDiscountCouponDto {
 
     @ApiModelProperty(value = "优惠券状态:0:草稿,1:正式")
     private Integer couponStatus;
+
+    @ApiModelProperty(value = "优惠券类型:1-满减券,2-折扣券")
+    private Integer couponType;
+
+    @ApiModelProperty(value = "折扣率(0-100,用于折扣券,例如80表示8折)")
+    private BigDecimal discountRate;
 }

+ 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;
 

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

@@ -33,5 +33,8 @@ public class StoreBannerDto implements Serializable {
 
     @ApiModelProperty(value = "图片URL,多个可用逗号分隔或前端自定义格式")
     private String imgUrls;
+
+    @ApiModelProperty(value = "店铺ID")
+    private Integer storeId;
 }
 

+ 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;
+}
+

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

@@ -1,5 +1,6 @@
 package shop.alien.entity.store.dto;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@@ -71,6 +72,9 @@ public class StoreRenovationRequirementDto {
     @ApiModelProperty(value = "动态状态(0:草稿, 1:已发布, 2:已下架)")
     private Integer status;
 
+    @ApiModelProperty(value = "审核状态(0:待审核, 1:审核通过, 2:审核失败)")
+    private Integer auditStatus;
+
     @ApiModelProperty(value = "浏览数")
     private Integer viewCount;
 
@@ -109,5 +113,8 @@ public class StoreRenovationRequirementDto {
 
     @ApiModelProperty(value = "是否已与发布商铺发生沟通(true:已沟通, false:未沟通)")
     private Boolean hasCommunicated;
+
+    @ApiModelProperty(value = "审核失败原因")
+    private String auditReason;
 }
 

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

@@ -5,6 +5,8 @@ import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import java.util.Date;
+
 /**
  * 商家端-员工列表查询 DTO(QueryString 绑定)
  */
@@ -33,6 +35,12 @@ public class StoreStaffConfigListQueryDto {
 
     @ApiModelProperty(value = "员工名称")
     private String staffName;
+
+    @ApiModelProperty(value = "创建时间开始(yyyy-MM-dd HH:mm:ss)")
+    private Date startCreatedTime;
+
+    @ApiModelProperty(value = "创建时间结束(yyyy-MM-dd HH:mm:ss)")
+    private Date endCreatedTime;
 }
 
 

+ 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;
+
     /**
      * 获取执业年限(根据执业开始日期自动计算)
      * 返回当前时间减去执业开始时间的年数

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

@@ -22,4 +22,10 @@ public class LifeDiscountCouponFriendRuleDetailVo extends LifeDiscountCouponFrie
     private Integer couponNum;
 
     private Integer friendStoreUserId;
+
+    /** 代金券id(type=4 时返回,对应 life_coupon.id) */
+    private String voucherId;
+
+    /** 优惠券类型:1-满减券,2-折扣券(仅优惠券有值,代金券为null) */
+    private Integer couponType;
 }

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

@@ -42,11 +42,27 @@ 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;
 
     @ApiModelProperty(value = "最低消费")
     private BigDecimal minimumSpendingAmount;
 
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @ApiModelProperty(value = "有效期(优惠券/代金券的结束日期)")
+    private Date validDate;
+
+    @ApiModelProperty(value = "优惠券类型:1-满减券,2-折扣券(仅优惠券有值,代金券为null)")
+    private Integer couponType;
+
+    @ApiModelProperty(value = "折扣率(0-100,用于折扣券,例如80表示8折,仅折扣券有值)")
+    private BigDecimal discountRate;
+
     private List<LifeDiscountCouponFriendRuleDetailVo> lifeDiscountCouponFriendRuleDetailVos;
 }

+ 13 - 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;
 
@@ -162,6 +168,12 @@ public class LifeDiscountCouponVo {
     @ApiModelProperty(value = "到期日期")
     private LocalDate expirationTime;
 
+    @ApiModelProperty(value = "优惠券类型:1-满减券,2-折扣券")
+    private Integer couponType;
+
+    @ApiModelProperty(value = "折扣率(0-100,用于折扣券,例如80表示8折)")
+    private BigDecimal discountRate;
+
     @ApiModelProperty(value = "创建时间")
     @TableField(value = "created_time")
     @JsonFormat(pattern = "yyyy/MM/dd", timezone = "GMT+8")

+ 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;
+}
+

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

@@ -57,6 +57,9 @@ public class StoreMainInfoVo extends StoreInfo {
     @ApiModelProperty(value = "门店头像")
     String headImgUrl;
 
+    @ApiModelProperty(value = "商家头像")
+    String storeUserHeadImg;
+
     @ApiModelProperty(value = "门店地址")
     String storeAddress;
 

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

@@ -38,6 +38,9 @@ public class StoreUserVo extends StoreUser {
     @ApiModelProperty(value = "账号是否启用")
     private boolean switchStatus;
 
+    @ApiModelProperty(value = "状态展示文案:启用/禁用,供前端直接展示")
+    private String statusName;
+
     @ApiModelProperty(value = "验证码")
     private String verificationCode;
 

+ 16 - 4
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java

@@ -55,19 +55,27 @@ public class StoreOperationalActivity {
     @TableField("activity_rule")
     private String activityRule;
 
-    @ApiModelProperty(value = "奖励类型:COUPON-优惠券, RED_PACKET-红包")
+    @ApiModelProperty(value = "奖励类型:COUPON-优惠券, RED_PACKET-红包, VOUCHER-代金券")
     @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;
 
-    @ApiModelProperty(value = "状态:1-待审核, 2-未开始, 3-审核拒绝, 4-已售罄, 5-进行中, 6-已下架, 7-已结束, 8-审核成功")
+    @ApiModelProperty(value = "代金券发放数量")
+    @TableField("voucher_quantity")
+    private Integer voucherQuantity;
+
+    @ApiModelProperty(value = "状态:0-草稿, 1-待审核, 2-未开始, 3-审核拒绝, 4-已售罄, 5-进行中, 6-已下架, 7-已结束, 8-审核成功(2+手动下架)")
     @TableField("status")
     private Integer status;
 
@@ -78,7 +86,7 @@ public class StoreOperationalActivity {
 
     @ApiModelProperty(value = "创建时间")
     @TableField(value = "created_time", fill = FieldFill.INSERT)
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.S", timezone = "GMT+8")
     private Date createdTime;
 
     @ApiModelProperty(value = "创建人ID")
@@ -140,5 +148,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;
 
 }

+ 1 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/LifeCouponPlatformVo.java

@@ -23,6 +23,7 @@ public class LifeCouponPlatformVo {
     // 优惠券列表
     public Integer couponsFromType; // 默认1
     public Integer couponStatus;//优惠券状态:1:进行中 2:已结束 3:草稿
+    public Integer discountCouponType; // 优惠券类型:1=满减券,2=折扣券,不传=全部优惠券
 
     // 优惠券还是代金券
     public Integer couponType; // 1代金券 2优惠券

+ 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;
+}

+ 11 - 6
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDTO.java

@@ -1,6 +1,5 @@
 package shop.alien.entity.storePlatform.vo;
 
-import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.databind.JsonNode;
 import io.swagger.annotations.ApiModel;
@@ -34,11 +33,11 @@ public class StoreOperationalActivityDTO {
     private String promotionalImage;
 
     @ApiModelProperty(value = "活动开始时间", required = true)
-    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date startTime;
 
     @ApiModelProperty(value = "活动结束时间", required = true)
-    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date endTime;
 
     @ApiModelProperty(value = "用户可参与次数,0表示不限制")
@@ -47,7 +46,7 @@ public class StoreOperationalActivityDTO {
     @ApiModelProperty(value = "活动规则:VERIFY_AND_COMMENT-核销并评论, VERIFY-核销, CHECK_IN-打卡", required = true)
     private String activityRule;
 
-    @ApiModelProperty(value = "奖励类型:COUPON-优惠券, RED_PACKET-红包")
+    @ApiModelProperty(value = "奖励类型:COUPON-优惠券, RED_PACKET-红包, VOUCHER-代金券")
     private String rewardType;
 
     @ApiModelProperty(value = "优惠券ID")
@@ -56,6 +55,12 @@ public class StoreOperationalActivityDTO {
     @ApiModelProperty(value = "优惠券发放数量")
     private Integer couponQuantity;
 
+    @ApiModelProperty(value = "代金券ID")
+    private Integer voucherId;
+
+    @ApiModelProperty(value = "代金券发放数量")
+    private Integer voucherQuantity;
+
     @ApiModelProperty(value = "状态:0-禁用, 1-启用")
     private Integer status;
 
@@ -87,11 +92,11 @@ public class StoreOperationalActivityDTO {
     private String activityType;
 
     @ApiModelProperty(value = "报名开始时间")
-    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date signupStartTime;
 
     @ApiModelProperty(value = "报名结束时间")
-    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date signupEndTime;
 
     @ApiModelProperty(value = "活动限制人数")

+ 10 - 2
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDetailVo.java

@@ -1,6 +1,5 @@
 package shop.alien.entity.storePlatform.vo;
 
-import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import io.swagger.annotations.ApiModel;
@@ -18,7 +17,7 @@ import java.util.List;
  */
 @Data
 @JsonInclude
-@ApiModel(value = "StoreOperationalActivityDetailVo", description = "运营活动详情返回对象")
+@ApiModel(value = "StoreOperationalActivityDetailVo", description = "运营活动详情返回对象,包含活动基本信息、优惠券信息、代金券信息等")
 public class StoreOperationalActivityDetailVo {
 
     @ApiModelProperty(value = "主键")
@@ -56,6 +55,12 @@ public class StoreOperationalActivityDetailVo {
     @ApiModelProperty(value = "优惠券发放数量")
     private Integer couponQuantity;
 
+    @ApiModelProperty(value = "代金券ID")
+    private Integer voucherId;
+
+    @ApiModelProperty(value = "代金券发放数量")
+    private Integer voucherQuantity;
+
     @ApiModelProperty(value = "状态")
     private Integer status;
 
@@ -89,6 +94,9 @@ public class StoreOperationalActivityDetailVo {
     @ApiModelProperty(value = "优惠券名称")
     private String couponName;
 
+    @ApiModelProperty(value = "代金券名称")
+    private String voucherName;
+
     @ApiModelProperty(value = "商户名称")
     private String storeName;
 

+ 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;
 

+ 16 - 1
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityVO.java

@@ -16,7 +16,7 @@ import shop.alien.entity.storePlatform.StoreOperationalActivity;
  */
 @Data
 @JsonInclude
-@ApiModel(value = "StoreOperationalActivityVO", description = "运营活动返回对象")
+@ApiModel(value = "StoreOperationalActivityVO", description = "运营活动返回对象,包含活动基本信息、优惠券信息、代金券信息、活动图片等")
 public class StoreOperationalActivityVO extends StoreOperationalActivity {
 
     @ApiModelProperty(value = "活动状态名称")
@@ -31,6 +31,9 @@ public class StoreOperationalActivityVO extends StoreOperationalActivity {
     @ApiModelProperty(value = "优惠券名称")
     private String couponName;
 
+    @ApiModelProperty(value = "代金券名称")
+    private String voucherName;
+
     @ApiModelProperty(value = "商户名称")
     private String storeName;
 
@@ -43,6 +46,9 @@ public class StoreOperationalActivityVO extends StoreOperationalActivity {
     @ApiModelProperty(value = "剩余优惠券数量")
     private Integer remainingCouponQuantity;
 
+    @ApiModelProperty(value = "剩余代金券数量")
+    private Integer remainingVoucherQuantity;
+
     @ApiModelProperty(value = "活动标题图片")
     private StoreImg activityTitleImg;
 
@@ -63,5 +69,14 @@ public class StoreOperationalActivityVO extends StoreOperationalActivity {
 
     @ApiModelProperty(value = "上传图片类型:0-本地上传, 1-AI生成")
     private Integer uploadImgType;
+
+    @ApiModelProperty(value = "优惠券类型:1-优惠券, 2-红包, 3-平台优惠券, 4-代金券")
+    private Integer couponType;
+
+    @ApiModelProperty(value = "优惠券类型(满减券/折扣券):1=满减券,2=折扣券(仅当优惠券存在时有值)")
+    private Integer discountCouponType;
+
+    @ApiModelProperty(value = "折扣率(0-100,用于折扣券,例如80表示8折,仅折扣券有值)")
+    private java.math.BigDecimal discountRate;
 }
 

+ 27 - 0
alien-entity/src/main/java/shop/alien/mapper/CommonCommentMapper.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.toolkit.Constants;
@@ -58,6 +59,29 @@ public interface CommonCommentMapper extends BaseMapper<CommonComment> {
     List<CommonCommentVo> getCommentCount(Integer type);
 
     /**
+     * 批量查询评论数量(按sourceId分组统计)
+     * 
+     * @param sourceType 来源类型
+     * @param sourceIds 来源ID列表
+     * @return sourceId -> commentCount 的映射列表
+     */
+    @Select("<script>" +
+            "SELECT cc.source_id AS sourceId, COUNT(*) AS commentCount " +
+            "FROM common_comment cc " +
+            "WHERE cc.source_type = #{sourceType} " +
+            "AND cc.delete_flag = 0 " +
+            "<if test='sourceIds != null and sourceIds.size() > 0'>" +
+            "AND cc.source_id IN " +
+            "<foreach collection='sourceIds' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            "</if>" +
+            "GROUP BY cc.source_id" +
+            "</script>")
+    List<CommonCommentVo> batchGetCommentCount(@Param("sourceType") Integer sourceType, 
+                                                @Param("sourceIds") List<Integer> sourceIds);
+
+    /**
      * 批量逻辑删除评论(使用原生 SQL 绕过 @TableLogic 注解限制)
      *
      * @param sourceType 来源类型
@@ -67,5 +91,8 @@ public interface CommonCommentMapper extends BaseMapper<CommonComment> {
     @Update("UPDATE common_comment SET delete_flag = 1, updated_time = NOW() " +
             "WHERE source_type = #{sourceType} AND source_id = #{sourceId} AND delete_flag = 0")
     int logicDeleteBySourceId(@Param("sourceType") Integer sourceType, @Param("sourceId") Integer sourceId);
+
+    @Select("select * from common_comment ${ew.customSqlSegment}")
+    CommonComment selectAll(@Param(Constants.WRAPPER) LambdaQueryWrapper<CommonComment> and);
 }
 

+ 37 - 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;
@@ -12,6 +13,7 @@ import org.apache.ibatis.annotations.Update;
 import shop.alien.entity.store.CommonRating;
 import shop.alien.entity.store.vo.StoreInfoScoreVo;
 
+import java.util.Date;
 import java.util.Map;
 
 /**
@@ -72,5 +74,40 @@ 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)
+     * @param activityAuditTime 活动审核时间,为null时统计所有历史好评
+     * @return 已通过的好评条数(business_type=1, audit_status=1, score>=4.5),包括已删除的评论
+     */
+    @Select("<script>" +
+            "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 " +
+            "<if test='activityAuditTime != null'>" +
+            "AND updated_time >= #{activityAuditTime} " +
+            "</if>" +
+            "</script>")
+    int countPassedGoodRatingsByUserAndStore(@Param("userId") Long userId, @Param("storeId") Integer storeId, @Param("activityAuditTime") Date activityAuditTime);
 }
 

+ 30 - 20
alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponFriendRuleDetailMapper.java

@@ -8,7 +8,6 @@ import shop.alien.entity.store.LifeDiscountCouponFriendRuleDetail;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import shop.alien.entity.store.vo.LifeDiscountCouponFriendRuleDetailVo;
 import shop.alien.entity.store.vo.LifeDiscountCouponFriendRuleVo;
-import shop.alien.entity.store.vo.LifeGroupBuyThaliVo;
 
 import java.util.List;
 
@@ -22,28 +21,39 @@ public interface LifeDiscountCouponFriendRuleDetailMapper extends BaseMapper<Lif
 
     void insertList(List<LifeDiscountCouponFriendRuleDetail> lifeDiscountCouponFriendRuleDetailList);
 
-    @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}")
+    @Select("select si.store_name storeName,ldc.name couponName,ldc.id couponId,ldc.coupon_type couponType,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, " +
+            "COALESCE(a.coupon_type, c.coupon_type) AS couponType, " +
+            "c.discount_rate AS discountRate " +
+            "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, " +
+            "c.coupon_type as couponType, " +
+            "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);
 }

+ 28 - 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,27 @@ 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 过滤)
+     * @param couponType   优惠券类型:1=满减券,2=折扣券,可为 null(不按 couponType 过滤)
+     * @return 符合条件的 life_discount_coupon 列表
+     */
+    List<LifeDiscountCoupon> selectListSingleTable(@Param("storeId") String storeId, @Param("couponStatus") Integer couponStatus, @Param("type") Integer type, @Param("couponType") Integer couponType);
 }

+ 53 - 14
alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponStoreFriendMapper.java

@@ -35,37 +35,76 @@ 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" +
+    /** 好友赠我 - 优惠券 */
+    @Select("select ldc.valid_date endDate,ldc.valid_date validDate,ldc.nominal_value nominalValue,ldc.minimum_spending_amount minimumSpendingAmount,\n" +
             "si.store_name storeName,\n" +
+            "si.id storeId,\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" +
+            "ldc.coupon_type couponType,\n" +
+            "ldc.discount_rate discountRate\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 = ldc.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" +
+            "on CAST(si.id AS CHAR) COLLATE utf8mb4_unicode_ci = ldc.store_id and si.delete_flag = 0\n" +
             "${ew.customSqlSegment}")
     List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(@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" +
+    /** 好友赠我 - 代金券(type=4) */
+    @Select("select lc.end_date endDate,lc.end_date validDate,CAST(lc.price AS DECIMAL(10,2)) nominalValue,null minimumSpendingAmount,\n" +
+            "si.store_name storeName,\n" +
+            "si.id storeId,\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 CAST(si.id AS CHAR) COLLATE utf8mb4_unicode_ci = lc.store_id and si.delete_flag = 0\n" +
+            "${ew.customSqlSegment}")
+    List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponListVoucher(@Param(Constants.WRAPPER) QueryWrapper<LifeDiscountCouponFriendRuleVo> lifeDiscountCouponFriendRuleVoQueryWrapper);
+
+    /** 我赠好友 - 优惠券 */
+    @Select("select ldc.valid_date endDate,ldc.valid_date validDate,ldc.nominal_value nominalValue,ldc.minimum_spending_amount minimumSpendingAmount,\n" +
             "si.store_name storeName,\n" +
+            "si.id storeId,\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" +
+            "ldc.coupon_type couponType,\n" +
+            "ldc.discount_rate discountRate\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_user su\n" +
+            "on su.id = ldcsf.friend_store_user_id and su.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" +
+            "on si.id = su.store_id and si.delete_flag = 0\n" +
             "${ew.customSqlSegment}")
     List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponListwzhy(@Param(Constants.WRAPPER) QueryWrapper<LifeDiscountCouponFriendRuleVo> lifeDiscountCouponFriendRuleVoQueryWrapper);
+
+    /** 我赠好友 - 代金券(type=4) */
+    @Select("select lc.end_date endDate,lc.end_date validDate,CAST(lc.price AS DECIMAL(10,2)) nominalValue,null minimumSpendingAmount,\n" +
+            "si.store_name storeName,\n" +
+            "si.id storeId,\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_user su\n" +
+            "on su.id = ldcsf.friend_store_user_id and su.delete_flag = 0\n" +
+            "left join store_info si\n" +
+            "on si.id = su.store_id and si.delete_flag = 0\n" +
+            "${ew.customSqlSegment}")
+    List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponListwzhyVoucher(@Param(Constants.WRAPPER) QueryWrapper<LifeDiscountCouponFriendRuleVo> lifeDiscountCouponFriendRuleVoQueryWrapper);
 }

+ 5 - 1
alien-entity/src/main/java/shop/alien/mapper/LifeUserDynamicsMapper.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.toolkit.Constants;
@@ -18,7 +19,7 @@ public interface LifeUserDynamicsMapper extends BaseMapper<LifeUserDynamics> {
             "select lud.id, lud.top_status, lud.top_time, lud.title, lud.phone_id phoneId, " +
             "lud.context, lud.image_path, lud.address, lud.address_name, lud.address_context, " +
             "lud.liulan_count, lud.dianzan_count, lud.type, lud.created_time, substring_index(lud.phone_id, '_', 1) flag, " +
-            "substring_index(lud.phone_id, '_', -1) phone, lud.draft , lud.address_province, lud.transfer_count " +
+            "substring_index(lud.phone_id, '_', -1) phone, lud.draft , lud.address_province, lud.transfer_count, lud.cover_image " +
             "from life_user_dynamics lud " +
             "where lud.delete_flag = 0 and lud.enable_status = 0 and lud.draft = 0 and " +
             "not exists (select 1 from life_user_violation luv where luv.delete_flag = 0 and luv.processing_status = 1 " +
@@ -93,4 +94,7 @@ public interface LifeUserDynamicsMapper extends BaseMapper<LifeUserDynamics> {
     List<LifeUserDynamicsVo> getDynamicsDetail(@Param("id") Integer id);
 
     List<LifeUserDynamicsVo> getStoreDynamicslist(@Param("userId") String userId, @Param("phoneId") String phoneId);
+
+    @Select("select * from life_user_dynamics ${ew.customSqlSegment}")
+    LifeUserDynamics selectAll(@Param(Constants.WRAPPER) LambdaQueryWrapper<LifeUserDynamics> and);
 }

+ 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 );
 }

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

@@ -45,4 +45,5 @@ public interface StoreUserMapper extends BaseMapper<StoreUser> {
      * @return 子账号的 store_user.id 列表
      */
     List<Integer> selectSubAccountUserIdsByMainAccountIdWithRole(@Param("mainAccountId") Integer mainAccountId);
+
 }

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

@@ -2,16 +2,13 @@ package shop.alien.mapper.second;
 
 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; // 引入 Mapper 注解
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import shop.alien.entity.second.SecondGoods;
-import shop.alien.entity.second.SecondShield;
 import shop.alien.entity.second.vo.SecondGoodsVo;
 import shop.alien.entity.second.vo.SellGoodsVo;
-import shop.alien.entity.store.StoreInfo;
 
 import java.util.List;
 
@@ -99,8 +96,12 @@ public interface SecondGoodsMapper extends BaseMapper<SecondGoods> {
             "sg.*, " +
             "sgc1.category_name as categoryOneName, " +
             "sgc2.category_name as categoryTwoName, "+
-            "ROUND(ST_Distance_Sphere(ST_GeomFromText(CONCAT('POINT(',#{currentLongitude},' ',#{currentLatitude} , ')' )), ST_GeomFromText(CONCAT('POINT(', REPLACE(sg.position, ',', ' '), ')' ))) / 1000, 2) AS distance "+
+            "ROUND(ST_Distance_Sphere(ST_GeomFromText(CONCAT('POINT(',#{currentLongitude},' ',#{currentLatitude} , ')' )), ST_GeomFromText(CONCAT('POINT(', REPLACE(sg.position, ',', ' '), ')' ))) / 1000, 2) AS distance, "+
+            "CONCAT('user_', u.user_phone) as user_phone"+
             " FROM second_goods sg " +
+            "inner join life_user u " +
+            "on u.id =  sg.user_id " +
+            "and u.delete_flag = 0 " +
             "left JOIN second_goods_category sgc1 " +
             "on sg.category_one_id = sgc1.id " +
             "left JOIN second_goods_category sgc2 " +

+ 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>
 

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

@@ -10,9 +10,10 @@
             <result property="moneyLow" column="money_low" />
             <result property="moneyHigh" column="money_high" />
             <result property="deleteFlag" column="delete_flag" />
+            <result property="couponType" column="coupon_type" />
     </resultMap>
 
     <sql id="Base_Column_List">
-        id,ac_name,money_low,money_high,delete_flag
+        id,ac_name,money_low,money_high,delete_flag,coupon_type
     </sql>
 </mapper>

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

@@ -27,9 +27,32 @@
         <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>
+        <if test="couponType != null">
+          AND coupon_type = #{couponType}
+        </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>

+ 29 - 60
alien-entity/src/main/resources/mapper/StoreInfoMapper.xml

@@ -5,68 +5,43 @@
 <mapper namespace="shop.alien.mapper.StoreInfoMapper">
 
     <!--
-        门店证照查询
-        证照类型与状态、提交时间、到期时间映射关系:
-          - img_type = 14: 营业执照
-          - img_type = 35: 其他资质证明
+        门店证照查询(存档审计)
+        以 store_license_history 为驱动表,展示所有上传记录
+        证照类型映射:license_status 1→营业执照(img_type=14)  2→其他资质证明(img_type=35)
+        审核状态:license_execute_status 1→审核通过  2→审核中  3→审核拒绝
     -->
     <select id="getStoreLicensePage"
             resultType="shop.alien.entity.store.vo.StoreLicenseInfoVo">
         SELECT
             CASE
-                WHEN si.img_type = 14 THEN '营业执照'
-                WHEN si.img_type = 35 THEN '其他资质证明'
+                WHEN slh.license_status = 1 THEN '营业执照'
+                WHEN slh.license_status = 2 THEN '其他资质证明'
                 ELSE ''
             END AS img_description,
-            si.img_type AS img_type,
+            CASE
+                WHEN slh.license_status = 1 THEN 14
+                WHEN slh.license_status = 2 THEN 35
+                ELSE 0
+            END AS img_type,
             s.id AS id,
             s.store_name AS store_name,
             s.store_tel AS store_tel,
             su.name AS name,
-            si.img_url,
+            slh.img_url,
             s.business_section_name,
             s.business_classify_name,
             s.business_types_name,
+            slh.license_execute_status AS states,
+            slh.created_time AS submit_date,
             CASE
-                WHEN si.img_type = 14 THEN s.business_license_status
-                WHEN si.img_type = 35 THEN slh.license_execute_status
-                ELSE ''
-            END AS states,
-            CASE
-                WHEN si.img_type = 14 THEN s.update_business_license_time
-                WHEN si.img_type = 35 THEN slh.created_time
-                ELSE NULL
-            END AS submit_date,
-            CASE
-                WHEN si.img_type = 14 THEN s.business_license_expiration_time
-                WHEN si.img_type = 35 THEN NULL
+                WHEN slh.license_status = 1 THEN s.business_license_expiration_time
                 ELSE NULL
             END AS expiration_time,
-            CASE
-                WHEN si.img_type = 14 THEN s.business_license_reason
-                WHEN si.img_type = 35 THEN slh.reason_refusal
-                ELSE NULL
-            END AS expiration_reason
-        FROM store_info s
-                 LEFT JOIN store_img si
-                           ON s.id = si.store_id
-                               AND si.img_type IN (14, 35)
-                               AND si.delete_flag = 0
-                 LEFT JOIN store_user su ON s.id = su.store_id
-                 LEFT JOIN (
-                     SELECT slh.store_id, slh.license_execute_status, slh.created_time, slh.reason_refusal, slh.img_url
-                     FROM store_license_history slh
-                     INNER JOIN (
-                         SELECT store_id, MAX(created_time) AS max_time
-                         FROM store_license_history
-                         WHERE license_status = 2 AND delete_flag = 0
-                         GROUP BY store_id
-                     ) slh2 ON slh.store_id = slh2.store_id 
-                             AND slh.created_time = slh2.max_time
-                     WHERE slh.license_status = 2 AND slh.delete_flag = 0
-                 ) slh ON s.id = slh.store_id AND si.img_type = 35
-        WHERE s.delete_flag = 0
-            AND si.img_type IS NOT NULL
+            slh.reason_refusal AS expiration_reason
+        FROM store_license_history slh
+                 INNER JOIN store_info s ON slh.store_id = s.id AND s.delete_flag = 0
+                 LEFT JOIN store_user su ON s.id = su.store_id AND su.delete_flag = 0
+        WHERE slh.delete_flag = 0
         <if test="storeName != null and storeName != ''">
             AND s.store_name LIKE CONCAT('%', #{storeName}, '%')
         </if>
@@ -79,28 +54,22 @@
         <if test="businessSection != null and businessSection != ''">
             AND s.business_section = #{businessSection}
         </if>
-        <if test="imgType != null">
-            AND si.img_type = #{imgType}
+        <if test="imgType != null and imgType == 14">
+            AND slh.license_status = 1
+        </if>
+        <if test="imgType != null and imgType == 35">
+            AND slh.license_status = 2
         </if>
         <if test="states != null and states != ''">
-            AND (
-                (si.img_type = 14 AND s.business_license_status = #{states})
-                OR (si.img_type = 35 AND slh.license_execute_status = #{states})
-            )
+            AND slh.license_execute_status = #{states}
         </if>
         <if test="startSubmitDate != null and startSubmitDate != ''">
-            AND (
-                (si.img_type = 14 AND s.update_business_license_time &gt;= #{startSubmitDate})
-                OR (si.img_type = 35 AND slh.created_time &gt;= #{startSubmitDate})
-            )
+            AND slh.created_time &gt;= #{startSubmitDate}
         </if>
         <if test="endSubmitDate != null and endSubmitDate != ''">
-            AND (
-                (si.img_type = 14 AND s.update_business_license_time &lt;= #{endSubmitDate})
-                OR (si.img_type = 35 AND slh.created_time &lt;= #{endSubmitDate})
-            )
+            AND slh.created_time &lt;= #{endSubmitDate}
         </if>
-        ORDER BY submit_date DESC, s.store_name ASC
+        ORDER BY slh.created_time DESC, s.store_name ASC
     </select>
 
 </mapper>

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

@@ -26,4 +26,5 @@
           AND u.account_type = 2
           AND u.delete_flag = 0
     </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}, '%')

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

@@ -14,18 +14,32 @@
         <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="voucher_quantity" property="voucherQuantity" />
         <result column="status" property="status" />
         <result column="delete_flag" property="deleteFlag" />
         <result column="created_time" property="createdTime" />
         <result column="created_user_id" property="createdUserId" />
         <result column="updated_time" property="updatedTime" />
         <result column="updated_user_id" property="updatedUserId" />
+        <result column="approval_comments" property="approvalComments" />
+        <result column="activity_type" property="activityType" />
+        <result column="signup_start_time" property="signupStartTime" />
+        <result column="signup_end_time" property="signupEndTime" />
+        <result column="activity_limit_people" property="activityLimitPeople" />
+        <result column="activity_details" property="activityDetails" />
+        <result column="result_type" property="resultType" />
+        <result column="result_text" property="resultText" />
+        <result column="result_media_url" property="resultMediaUrl" />
+        <result column="audit_time" property="auditTime" />
+        <result column="upload_img_type" property="uploadImgType" />
+        <result column="audit_status" property="auditStatus" />
     </resultMap>
 
     <!-- 通用查询结果列 -->
     <sql id="Base_Column_List">
-        id, store_id, activity_name, promotional_image, start_time, end_time, participation_limit, activity_rule, reward_type, coupon_id, coupon_quantity, status, delete_flag, created_time, created_user_id, updated_time, updated_user_id
+        id, store_id, activity_name, promotional_image, start_time, end_time, participation_limit, activity_rule, reward_type, coupon_id, voucher_id, coupon_quantity, voucher_quantity, status, delete_flag, created_time, created_user_id, updated_time, updated_user_id, approval_comments, activity_type, signup_start_time, signup_end_time, activity_limit_people, activity_details, result_type, result_text, result_media_url, audit_time, upload_img_type, audit_status
     </sql>
 
 </mapper>

+ 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

+ 125 - 54
alien-job/src/main/java/shop/alien/job/store/StoreMembershipCardJob.java

@@ -122,78 +122,149 @@ public class StoreMembershipCardJob {
     @XxlJob("cancellationOfBusinessJob")
     public void cancellationOfBusinessJob() {
         log.info("删除已申请注销超过7天的商家与用户: " + new Date());
+        int successCount = 0;
+        int failCount = 0;
 
-        // 获取全部申请注销的商家
+        // 获取全部申请注销的商家用户
         List<StoreUser> storeUsers = storeUserMapper.selectList(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getLogoutFlag, LOGOUT_FLAY));
         for (StoreUser storeUser : storeUsers) {
-            if (null != storeUser.getLogoutTime()) {
-                // 获取申请注销时间
-                Date logoutTime = storeUser.getLogoutTime();
-                // 获取申请注销 8 天后的时间
-                Calendar calendar = Calendar.getInstance();
-                calendar.setTime(logoutTime);
-                calendar.add(Calendar.DAY_OF_YEAR, 8);
-                Date sevenDay = calendar.getTime();
-                // 获取当前时间
-                Date date = new Date();
-                if (date.compareTo(sevenDay) >= 0) {
-                    // 删除已过注销时间的商家
-                    storeUserMapper.deleteById(storeUser.getId());
-                    alienStoreFeign.delMer(Boolean.TRUE, storeUser.getId().toString());
-                    //删除用户redis中的token
-                    baseRedisService.delete("store_" + storeUser.getPhone());
+            try {
+                if (null != storeUser.getLogoutTime()) {
+                    // 获取申请注销时间
+                    Date logoutTime = storeUser.getLogoutTime();
+                    // 获取申请注销 7 天后的时间(与通知一致)
+                    Calendar calendar = Calendar.getInstance();
+                    calendar.setTime(logoutTime);
+                    calendar.add(Calendar.DAY_OF_YEAR, 7);
+                    Date sevenDay = calendar.getTime();
+                    // 获取当前时间
+                    Date date = new Date();
+                    if (date.compareTo(sevenDay) >= 0) {
+                        // 删除已过注销时间的商家用户
+                        log.info("删除已注销超过7天的商家用户: userId={}, phone={}, logoutTime={}", 
+                                storeUser.getId(), storeUser.getPhone(), logoutTime);
+                        storeUserMapper.deleteById(storeUser.getId());
+                        alienStoreFeign.delMer(Boolean.TRUE, storeUser.getId().toString());
+                        //删除用户redis中的token
+                        baseRedisService.delete("store_" + storeUser.getPhone());
+                        successCount++;
+                    }
                 }
+            } catch (Exception e) {
+                failCount++;
+                log.error("删除商家用户失败: userId={}, phone={}, error={}", 
+                        storeUser.getId(), storeUser.getPhone(), e.getMessage(), e);
             }
         }
+        
+        // 获取全部申请注销的店铺
         List<StoreInfo> storeInfos = storeInfoMapper.selectList(new LambdaQueryWrapper<StoreInfo>().eq(StoreInfo::getLogoutFlag, LOGOUT_FLAY));
         for (StoreInfo storeInfo : storeInfos) {
-            if (null != storeInfo.getLogoutTime()) {
-                // 获取申请注销时间
-                Date logoutTime = storeInfo.getLogoutTime();
-                // 获取申请注销 8 天后的时间
-                Calendar calendar = Calendar.getInstance();
-                calendar.setTime(logoutTime);
-                calendar.add(Calendar.DAY_OF_YEAR, 8);
-                Date sevenDay = calendar.getTime();
-                // 获取当前时间
-                Date date = new Date();
-                if (date.compareTo(sevenDay) >= 0) {
-                    //删除该账号的店铺
-                    storeInfoMapper.deleteById(storeInfo.getId());
-                    StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, storeInfo.getId()));
-                    if (storeUser != null) {
-                        storeUser.setStoreId(null);
-                        storeUserMapper.updateById(storeUser);
-                        LambdaQueryWrapper<LifeFans> queryWrapper = new LambdaQueryWrapper<LifeFans>().eq(LifeFans::getFollowedId, "store_" + storeUser.getPhone())
-                                .or().eq(LifeFans::getFansId, "store_" + storeUser.getPhone());
-                        lifeFansMapper.delete(queryWrapper);
+            try {
+                if (null != storeInfo.getLogoutTime()) {
+                    // 获取申请注销时间
+                    Date logoutTime = storeInfo.getLogoutTime();
+                    // 获取申请注销 7 天后的时间(与通知一致)
+                    Calendar calendar = Calendar.getInstance();
+                    calendar.setTime(logoutTime);
+                    calendar.add(Calendar.DAY_OF_YEAR, 7);
+                    Date sevenDay = calendar.getTime();
+                    // 获取当前时间
+                    Date date = new Date();
+                    if (date.compareTo(sevenDay) >= 0) {
+                        log.info("开始删除已注销超过7天的店铺: storeId={}, storeName={}, logoutTime={}", 
+                                storeInfo.getId(), storeInfo.getStoreName(), logoutTime);
+                        
+                        // 先清理关联的商户用户注销状态,确保可以重新入驻
+                        List<StoreUser> relatedStoreUsers = storeUserMapper.selectList(
+                                new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, storeInfo.getId()));
+                        int deletedUsers = 0;
+                        int deletedFans = 0;
+                        
+                        for (StoreUser storeUser : relatedStoreUsers) {
+                            try {
+                                // 清理注销标记和时间,允许重新入驻
+                                storeUser.setLogoutFlag(0);
+                                storeUser.setLogoutTime(null);
+                                storeUser.setStoreId(null);  // 解除店铺绑定
+                                storeUserMapper.updateById(storeUser);
+                                deletedUsers++;
+                                log.info("清理商户用户注销状态: userId={}, phone={}", storeUser.getId(), storeUser.getPhone());
+                            } catch (Exception e) {
+                                log.error("清理商户用户注销状态失败: userId={}, phone={}, error={}", 
+                                        storeUser.getId(), storeUser.getPhone(), e.getMessage(), e);
+                            }
+                        }
+                        
+                        // 清理粉丝关系
+                        for (StoreUser storeUser : relatedStoreUsers) {
+                            try {
+                                if (storeUser.getPhone() != null) {
+                                    LambdaQueryWrapper<LifeFans> queryWrapper = new LambdaQueryWrapper<LifeFans>()
+                                            .eq(LifeFans::getFollowedId, "store_" + storeUser.getPhone())
+                                            .or().eq(LifeFans::getFansId, "store_" + storeUser.getPhone());
+                                    int deleted = lifeFansMapper.delete(queryWrapper);
+                                    deletedFans += deleted;
+                                }
+                            } catch (Exception e) {
+                                log.error("清理粉丝关系失败: userId={}, phone={}, error={}", 
+                                        storeUser.getId(), storeUser.getPhone(), e.getMessage(), e);
+                            }
+                        }
+                        
+                        // 清理Redis地理位置(通过Feign调用)
+                        try {
+                            alienStoreFeign.delMer(Boolean.TRUE, storeInfo.getId().toString());
+                        } catch (Exception e) {
+                            log.warn("清理Redis地理位置失败: storeId={}, error={}", storeInfo.getId(), e.getMessage());
+                        }
+                        
+                        // 最后删除店铺记录
+                        storeInfoMapper.deleteById(storeInfo.getId());
+                        successCount++;
+                        log.info("店铺删除完成: storeId={}, deletedUsers={}, deletedFans={}", 
+                                storeInfo.getId(), deletedUsers, deletedFans);
                     }
                 }
+            } catch (Exception e) {
+                failCount++;
+                log.error("删除店铺失败: storeId={}, storeName={}, error={}", 
+                        storeInfo.getId(), storeInfo.getStoreName(), e.getMessage(), e);
             }
         }
 
         // 获取全部申请注销的用户
         List<LifeUser> lifeUsers = lifeUserMapper.selectList(new LambdaQueryWrapper<LifeUser>().eq(LifeUser::getLogoutFlag, LOGOUT_FLAY));
         for (LifeUser lifeUser : lifeUsers) {
-            if (null != lifeUser.getLogoutTime()) {
-                // 获取申请注销时间
-                Date logoutTime = lifeUser.getLogoutTime();
-                // 获取申请注销 7 天后的时间
-                Calendar calendar = Calendar.getInstance();
-                calendar.setTime(logoutTime);
-                calendar.add(Calendar.DAY_OF_YEAR, 7);
-                Date sevenDay = calendar.getTime();
-                // 获取当前时间
-                Date date = new Date();
-                if (date.compareTo(sevenDay) >= 0) {
-                    // 删除已过注销时间的用户
-                    lifeUserMapper.deleteById(lifeUser.getId());
-                    // 清理粉丝
-                    LambdaQueryWrapper<LifeFans> queryWrapper = new LambdaQueryWrapper<LifeFans>().eq(LifeFans::getFollowedId, "user_" + lifeUser.getUserPhone())
-                            .or().eq(LifeFans::getFansId, "user_" + lifeUser.getUserPhone());
-                    lifeFansMapper.delete(queryWrapper);
+            try {
+                if (null != lifeUser.getLogoutTime()) {
+                    // 获取申请注销时间
+                    Date logoutTime = lifeUser.getLogoutTime();
+                    // 获取申请注销 7 天后的时间
+                    Calendar calendar = Calendar.getInstance();
+                    calendar.setTime(logoutTime);
+                    calendar.add(Calendar.DAY_OF_YEAR, 7);
+                    Date sevenDay = calendar.getTime();
+                    // 获取当前时间
+                    Date date = new Date();
+                    if (date.compareTo(sevenDay) >= 0) {
+                        // 删除已过注销时间的用户
+                        lifeUserMapper.deleteById(lifeUser.getId());
+                        // 清理粉丝
+                        LambdaQueryWrapper<LifeFans> queryWrapper = new LambdaQueryWrapper<LifeFans>()
+                                .eq(LifeFans::getFollowedId, "user_" + lifeUser.getUserPhone())
+                                .or().eq(LifeFans::getFansId, "user_" + lifeUser.getUserPhone());
+                        lifeFansMapper.delete(queryWrapper);
+                        successCount++;
+                    }
                 }
+            } catch (Exception e) {
+                failCount++;
+                log.error("删除用户失败: userId={}, phone={}, error={}", 
+                        lifeUser.getId(), lifeUser.getUserPhone(), e.getMessage(), e);
             }
         }
+        
+        log.info("定时任务执行完成: 成功={}, 失败={}", successCount, failCount);
     }
 }

+ 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 值:

+ 2 - 2
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsServiceImpl.java

@@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollectionUtil;
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
-import com.alipay.api.domain.GoodsVO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -1464,7 +1463,8 @@ public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, Secon
         queryWrapper.notIn(CollectionUtil.isNotEmpty(shieldedGoodsIds), "sg.id", shieldedGoodsIds)
                 .notIn(CollectionUtil.isNotEmpty(userIdList), "sg.user_id", userIdList)
                 .eq("sg.goods_status", SecondGoodsStatusEnum.LISTED.getCode())// 3-上架
-                .eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED);
+                .eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED)
+                .isNull("sg.trade_id");
         // 添加对 searchData 的模糊查询
         if (!StringUtils.isEmpty(secondGoodsVo.getSearchData())) {
             String searchData = "%" + secondGoodsVo.getSearchData() + "%";

+ 45 - 3
alien-store-platform/doc/OPERATIONAL_ACTIVITY_README.md

@@ -20,9 +20,11 @@ CREATE TABLE `store_operational_activity` (
     `end_time` DATETIME NOT NULL COMMENT '活动结束时间',
     `participation_limit` int DEFAULT 0 COMMENT '用户可参与次数,0表示不限制',
     `activity_rule` VARCHAR(50) NOT NULL COMMENT '活动规则:VERIFY_AND_COMMENT-核销并评论, VERIFY-核销, CHECK_IN-打卡',
-    `reward_type` VARCHAR(50) DEFAULT NULL COMMENT '奖励类型:COUPON-优惠券, RED_PACKET-红包',
-    `coupon_id` int DEFAULT NULL COMMENT '优惠券ID,关联优惠券表',
+    `reward_type` VARCHAR(50) DEFAULT NULL COMMENT '奖励类型:COUPON-优惠券, RED_PACKET-红包, VOUCHER-代金券',
+    `coupon_id` int DEFAULT NULL COMMENT '优惠券ID,关联优惠券表 life_discount_coupon',
+    `voucher_id` int DEFAULT NULL COMMENT '代金券ID,关联代金券表 life_coupon(评论有礼时可二选一或同时配置)',
     `coupon_quantity` int DEFAULT NULL COMMENT '优惠券发放数量',
+    `voucher_quantity` int DEFAULT NULL COMMENT '代金券发放数量',
     `status` int DEFAULT 1 COMMENT '状态:0-禁用, 1-启用',
     `delete_flag` int NOT NULL DEFAULT '0' COMMENT '删除标记, 0:未删除, 1:已删除',
     `created_time` datetime(1) NOT NULL COMMENT '创建时间',
@@ -55,9 +57,11 @@ CREATE TABLE `store_operational_activity` (
 - `endTime`: 活动结束时间
 - `participationLimit`: 用户可参与次数(0表示不限制)
 - `activityRule`: 活动规则(VERIFY_AND_COMMENT-核销并评论, VERIFY-核销, CHECK_IN-打卡)
-- `rewardType`: 奖励类型(COUPON-优惠券, RED_PACKET-红包)
+- `rewardType`: 奖励类型(COUPON-优惠券, RED_PACKET-红包, VOUCHER-代金券
 - `couponId`: 优惠券ID
+- `voucherId`: 代金券ID
 - `couponQuantity`: 优惠券发放数量
+- `voucherQuantity`: 代金券发放数量
 - `status`: 状态(0-禁用, 1-启用)
 - `deleteFlag`: 删除标记(逻辑删除)
 - `createdTime`: 创建时间(自动填充)
@@ -243,6 +247,7 @@ GET /operationalActivity/page?current=1&size=10&storeId=1&status=1
 ### 奖励类型(reward_type)
 - `COUPON`: 优惠券
 - `RED_PACKET`: 红包
+- `VOUCHER`: 代金券
 
 ### 活动状态(status)
 - `0`: 禁用
@@ -262,6 +267,7 @@ GET /operationalActivity/page?current=1&size=10&storeId=1&status=1
 3. **状态管理**: 活动状态由系统根据当前时间自动判断
 4. **参数校验**: DTO 中包含必填项校验,需要确保必填字段不为空
 5. **优惠券关联**: 如果奖励类型为优惠券,需要填写 couponId 和 couponQuantity
+6. **代金券关联**: 如果奖励类型为代金券,需要填写 voucherId 和 voucherQuantity。评论有礼活动可同时配置优惠券和代金券
 
 ---
 
@@ -275,6 +281,38 @@ GET /operationalActivity/page?current=1&size=10&storeId=1&status=1
 
 ---
 
+## 数据库表结构修改 SQL
+
+### 添加代金券相关字段
+
+如果数据库表中还没有 `voucher_id` 和 `voucher_quantity` 字段,请执行以下 SQL:
+
+```sql
+-- 添加代金券ID字段(如果不存在)
+ALTER TABLE `store_operational_activity` 
+ADD COLUMN `voucher_id` int DEFAULT NULL COMMENT '代金券ID,关联代金券表 life_coupon(评论有礼时可二选一或同时配置)' 
+AFTER `coupon_id`;
+
+-- 添加代金券发放数量字段(如果不存在)
+ALTER TABLE `store_operational_activity` 
+ADD COLUMN `voucher_quantity` int DEFAULT NULL COMMENT '代金券发放数量' 
+AFTER `coupon_quantity`;
+
+-- 添加代金券ID索引(可选)
+ALTER TABLE `store_operational_activity` 
+ADD INDEX `idx_voucher_id` (`voucher_id`);
+```
+
+### 更新奖励类型注释
+
+```sql
+-- 更新 reward_type 字段注释,添加 VOUCHER-代金券
+ALTER TABLE `store_operational_activity` 
+MODIFY COLUMN `reward_type` VARCHAR(50) DEFAULT NULL COMMENT '奖励类型:COUPON-优惠券, RED_PACKET-红包, VOUCHER-代金券';
+```
+
+---
+
 ## 更新日志
 
 ### 2025-11-26
@@ -283,3 +321,7 @@ GET /operationalActivity/page?current=1&size=10&storeId=1&status=1
 - 支持活动状态管理
 - 支持分页查询
 
+### 最新更新
+- 添加代金券支持:创建活动时可同时配置优惠券和代金券
+- 更新 DTO、实体类、Mapper 以支持代金券字段
+- 更新 AI 参数模板,包含代金券发放数量信息

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

@@ -251,7 +251,7 @@ public class LifeCouponPlatformController {
      * @author alien-cloud
      * @date 2025-11-18
      */
-    @ApiOperation("代金劵/优惠券列表")
+    @ApiOperation("代金劵/优惠券列表。discountCouponType=1仅满减券,discountCouponType=2仅折扣券,不传返回全部优惠券")
     @PostMapping("/getCouponList")
     private R<DiscountCouponPlatformVo> getCouponList(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
                                                       @RequestBody LifeCouponPlatformVo lifeCouponPlatformVo) {
@@ -260,12 +260,13 @@ public class LifeCouponPlatformController {
             return R.data(null, "当前用户未入驻!!!");
         }
         DiscountCouponPlatformVo vo = new DiscountCouponPlatformVo();
-        if (lifeCouponPlatformVo.getCouponType() == 1) {
-            vo.setCouponList(lifeCouponService.getCouponList(lifeCouponPlatformVo.getPageNum(), lifeCouponPlatformVo.getPageSize(), lifeCouponPlatformVo.getStoreId(),
+        // couponType: 1=代金券,2=优惠券,null或其他=优惠券(默认)
+        if (lifeCouponPlatformVo.getCouponType() != null && lifeCouponPlatformVo.getCouponType() == 1) {
+            vo.setCouponList(lifeCouponService.getCouponListAsDiscountVo(lifeCouponPlatformVo.getPageNum(), lifeCouponPlatformVo.getPageSize(), lifeCouponPlatformVo.getStoreId(),
                     lifeCouponPlatformVo.getStatus(), lifeCouponPlatformVo.getName()));
         } else {
             vo.setDiscountList(lifeDiscountCouponPlatformService.getStoreAllCouponList(lifeCouponPlatformVo.getStoreId(), userLoginInfo,
-                    lifeCouponPlatformVo.getPageNum(), lifeCouponPlatformVo.getPageSize(), lifeCouponPlatformVo.getName(), lifeCouponPlatformVo.getCouponsFromType(), lifeCouponPlatformVo.getCouponStatus()));
+                    lifeCouponPlatformVo.getPageNum(), lifeCouponPlatformVo.getPageSize(), lifeCouponPlatformVo.getName(), lifeCouponPlatformVo.getCouponsFromType(), lifeCouponPlatformVo.getCouponStatus(), lifeCouponPlatformVo.getDiscountCouponType()));
         }
 
         return R.data(vo);

+ 9 - 8
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java

@@ -4,14 +4,13 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 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;
 
-import java.util.List;
 import java.util.Optional;
 
 import static shop.alien.storeplatform.service.impl.OperationalActivityServiceImpl.failureReasonHolder;
@@ -33,7 +32,7 @@ public class OperationalActivityController {
 
     private final OperationalActivityService activityService;
 
-    @ApiOperation("创建运营活动")
+    @ApiOperation(value = "创建运营活动", notes = "支持配置优惠券和代金券作为奖励,可同时配置或二选一")
     @ApiOperationSupport(order = 1)
     @PostMapping("/create")
     public R<String> createActivity(@RequestBody StoreOperationalActivityDTO dto) {
@@ -59,15 +58,17 @@ public class OperationalActivityController {
         }
     }
 
-    @ApiOperation("更新运营活动")
+    @ApiOperation(value = "更新运营活动", notes = "支持更新优惠券和代金券配置")
     @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) {
@@ -96,7 +97,7 @@ public class OperationalActivityController {
         }
     }
 
-    @ApiOperation("根据ID获取活动详情")
+    @ApiOperation(value = "根据ID获取活动详情", notes = "返回活动详细信息,包括优惠券和代金券的名称、ID、数量等信息")
     @ApiOperationSupport(order = 4)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "id", value = "活动ID", dataType = "Integer", paramType = "query", required = true)
@@ -113,7 +114,7 @@ public class OperationalActivityController {
         }
     }
 
-    @ApiOperation("根据商户ID获取活动列表")
+    @ApiOperation(value = "根据商户ID获取活动列表", notes = "分页查询活动列表,返回活动基本信息、优惠券和代金券信息、活动图片等")
     @ApiOperationSupport(order = 5)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "storeId", value = "商户ID", dataType = "Integer", paramType = "query", required = true)

+ 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);
+
+    /**
      * 更新优惠券状态
      * 修改优惠券的状态,如上架、下架等
      *

+ 2 - 1
alien-store-platform/src/main/java/shop/alien/storeplatform/service/LifeDiscountCouponPlatformService.java

@@ -89,9 +89,10 @@ public interface LifeDiscountCouponPlatformService extends IService<LifeDiscount
      * @param couponName 优惠券名称(模糊查询)
      * @param couponsFromType 优惠券来源类型
      * @param couponStatus 优惠券状态(1:进行中, 2:已结束, 3:已暂停)
+     * @param discountCouponType 优惠券类型:1=满减券,2=折扣券,null=全部优惠券
      * @return IPage<LifeDiscountCouponVo> 分页优惠券列表
      */
-    IPage<LifeDiscountCouponVo> getStoreAllCouponList(String storeId, UserLoginInfo userLoginInfo, int page, int size, String couponName, Integer couponsFromType, Integer couponStatus);
+    IPage<LifeDiscountCouponVo> getStoreAllCouponList(String storeId, UserLoginInfo userLoginInfo, int page, int size, String couponName, Integer couponsFromType, Integer couponStatus, Integer discountCouponType);
 
 }
 

+ 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;
+    }
+
     /**
      * 更新优惠券状态
      * 修改优惠券的状态,支持上架、下架等操作

+ 17 - 5
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/LifeDiscountCouponPlatformServiceImpl.java

@@ -177,6 +177,10 @@ public class LifeDiscountCouponPlatformServiceImpl extends ServiceImpl<LifeDisco
             LifeDiscountCoupon lifeDiscountCoupon = new LifeDiscountCoupon();
             lifeDiscountCoupon.setId(Integer.parseInt(lifeDiscountCouponDto.getCouponId()));
             BeanUtils.copyProperties(lifeDiscountCouponDto, lifeDiscountCoupon);
+            // 如果最低消费为null,设置为0(表示无门槛)
+            if (lifeDiscountCoupon.getMinimumSpendingAmount() == null) {
+                lifeDiscountCoupon.setMinimumSpendingAmount(BigDecimal.ZERO);
+            }
 
             // 根据开始领取时间判断可领取状态
             // 判断是否在领取时间内
@@ -442,7 +446,7 @@ public class LifeDiscountCouponPlatformServiceImpl extends ServiceImpl<LifeDisco
      * @return IPage<LifeDiscountCouponVo> 分页优惠券列表
      */
     @Override
-    public IPage<LifeDiscountCouponVo> getStoreAllCouponList(String storeId, UserLoginInfo userLoginInfo, int page, int size, String couponName, Integer couponsFromType, Integer couponStatus) {
+    public IPage<LifeDiscountCouponVo> getStoreAllCouponList(String storeId, UserLoginInfo userLoginInfo, int page, int size, String couponName, Integer couponsFromType, Integer couponStatus, Integer discountCouponType) {
 
         IPage<LifeDiscountCoupon> iPage = new Page<>(page, size);
         List<LifeDiscountCouponVo> lifeDiscountCouponVos = new ArrayList<>();
@@ -452,6 +456,10 @@ public class LifeDiscountCouponPlatformServiceImpl extends ServiceImpl<LifeDisco
         if (!StringUtils.isEmpty(couponName)) {
             lifeDiscountCouponLambdaQueryWrapper.like(LifeDiscountCoupon::getName, couponName);
         }
+        //如果指定了优惠券类型(满减券或折扣券),添加筛选条件
+        if (discountCouponType != null) {
+            lifeDiscountCouponLambdaQueryWrapper.eq(LifeDiscountCoupon::getCouponType, discountCouponType);
+        }
         //获取当日日期
         Date now = new Date();
         //如果根据类型查询
@@ -491,25 +499,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;
                 }
             }

+ 8 - 5
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/MerchantAuthServiceImpl.java

@@ -51,11 +51,12 @@ public class MerchantAuthServiceImpl implements MerchantAuthService {
                        .eq(LifeUser::getLogoutFlag, 0);  // 只查询未注销的用户
             size = lifeUserMapper.selectCount(userWrapper);
         } else {
-            // 商家端:查询已入驻或审核中的商家
-            // 1. 先根据身份证和姓名查询商户用户
+            // 商家端:查询已入驻或审核中的商家(排除已注销和审核失败的店铺)
+            // 1. 先根据身份证和姓名查询商户用户(排除已注销的用户)
             LambdaQueryWrapper<StoreUser> storeUserWrapper = new LambdaQueryWrapper<>();
             storeUserWrapper.eq(StoreUser::getIdCard, idCard)
-                           .eq(StoreUser::getName, name);
+                           .eq(StoreUser::getName, name)
+                           .eq(StoreUser::getLogoutFlag, 0);  // 排除已注销的商户用户
             List<StoreUser> storeUserList = storeUserMapper.selectList(storeUserWrapper);
             
             // 2. 获取这些商户用户绑定的店铺ID
@@ -64,11 +65,13 @@ public class MerchantAuthServiceImpl implements MerchantAuthService {
                     .filter(Objects::nonNull)  // 过滤掉空的店铺ID
                     .collect(Collectors.toList());
             
-            // 3. 如果有店铺ID,查询非审核失败状态的店铺
+            // 3. 如果有店铺ID,查询非审核失败且非注销状态的店铺
             if (!storeIds.isEmpty()) {
                 LambdaQueryWrapper<StoreInfo> storeInfoWrapper = new LambdaQueryWrapper<>();
                 storeInfoWrapper.in(StoreInfo::getId, storeIds)
-                               .notIn(StoreInfo::getStoreApplicationStatus, 2);  // 排除审核失败的店铺(状态2)
+                               .notIn(StoreInfo::getStoreApplicationStatus, 2)  // 排除审核失败的店铺(状态2)
+                               .eq(StoreInfo::getLogoutFlag, 0)  // 排除已注销的店铺(logoutFlag=0表示未注销)
+                               .ne(StoreInfo::getStoreStatus, -1);  // 排除注销中状态的店铺(storeStatus=-1表示注销中)
                 size = storeInfoMapper.selectCount(storeInfoWrapper);
             }
         }

+ 178 - 13
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -16,17 +16,29 @@ 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.LifeCoupon;
 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.LifeCouponMapper;
 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 +73,18 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
     private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
 
+    private final LifeCouponMapper lifeCouponMapper;
+
+    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;
@@ -88,6 +110,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
             + "用户可参与次数:%s\n"
             + "活动规则:%s\n"
             + "优惠券发放数量:%s\n"
+            + "代金券发放数量:%s\n"
             + "图片描述:%s";
 
     @Override
@@ -139,6 +162,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                     
                     // 审核结束后,设置审核时间
                     Date auditTime = new Date();
+                    activity.setAuditStatus(auditResult.isPassed() ? 1 : 2);
                     activity.setAuditTime(auditTime);
                     
                     // 审核通过,根据活动时间自动设置状态
@@ -169,6 +193,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参数
@@ -180,7 +213,8 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                                 dto.getEndTime(),
                                 dto.getParticipationLimit(),
                                 dto.getActivityRule(),
-                                dto.getCouponQuantity(),
+                                dto.getCouponQuantity() != null ? dto.getCouponQuantity() : 0,
+                                dto.getVoucherQuantity() != null ? dto.getVoucherQuantity() : 0,
                                 dto.getImgDescribe()
                         );
                         requestBody.put("text", filled);
@@ -191,16 +225,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 +319,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 +332,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
                 // 审核结束后,设置审核时间
                 Date auditTime = new Date();
+                activity.setAuditStatus(auditResult.isPassed() ? 1 : 2);
                 activity.setAuditTime(auditTime);
 
                 // 审核通过,根据活动时间自动设置状态
@@ -328,6 +363,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                 }
 
                 result = activityMapper.updateById(activity);
+                sendActivityAuditNotice(activity, auditResult.isPassed(), auditResult.getFailureReason());
                 // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
                 if (dto.getUploadImgType() == 2) {
                     // 格式化输入AI参数
@@ -339,7 +375,8 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                             dto.getEndTime(),
                             dto.getParticipationLimit(),
                             dto.getActivityRule(),
-                            dto.getCouponQuantity(),
+                            dto.getCouponQuantity() != null ? dto.getCouponQuantity() : 0,
+                            dto.getVoucherQuantity() != null ? dto.getVoucherQuantity() : 0,
                             dto.getImgDescribe()
                     );
                     requestBody.put("text", filled);
@@ -350,16 +387,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 +448,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);
     }
 
@@ -449,11 +494,25 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
             vo.setStatusName("已结束");
         }
 
-        // 设置优惠券名称(判空处理)
+        // 设置优惠券名称和类型(判空处理)
         if (activity.getCouponId() != null) {
             LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
             if (coupon != null) {
                 vo.setCouponName(coupon.getName());
+                // 设置优惠券类型(满减券/折扣券):1=满减券,2=折扣券
+                vo.setDiscountCouponType(coupon.getCouponType());
+                // 如果是折扣券,设置折扣率
+                if (coupon.getCouponType() != null && coupon.getCouponType() == 2) {
+                    vo.setDiscountRate(coupon.getDiscountRate());
+                }
+            }
+        }
+
+        // 设置代金券名称(判空处理)
+        if (activity.getVoucherId() != null) {
+            LifeCoupon voucher = lifeCouponMapper.selectById(String.valueOf(activity.getVoucherId()));
+            if (voucher != null) {
+                vo.setVoucherName(voucher.getName());
             }
         }
 
@@ -525,6 +584,34 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                 }
             }
 
+            // 设置代金券名称(判空处理)
+            if (activity.getVoucherId() != null) {
+                LifeCoupon voucher = lifeCouponMapper.selectById(String.valueOf(activity.getVoucherId()));
+                if (voucher != null) {
+                    vo.setVoucherName(voucher.getName());
+                }
+            }
+
+            // 查询活动标题图片
+            StoreImg activityTitleImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
+                    .eq(StoreImg::getStoreId, vo.getStoreId())
+                    .eq(StoreImg::getImgType, 26)
+                    .eq(StoreImg::getDeleteFlag, 0)
+                    .eq(StoreImg::getBusinessId, activity.getId()));
+            if (activityTitleImg != null) {
+                vo.setActivityTitleImgUrl(activityTitleImg.getImgUrl());
+            }
+
+            // 查询活动详情图片
+            StoreImg activityDetailImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
+                    .eq(StoreImg::getStoreId, vo.getStoreId())
+                    .eq(StoreImg::getImgType, 27)
+                    .eq(StoreImg::getDeleteFlag, 0)
+                    .eq(StoreImg::getBusinessId, activity.getId()));
+            if (activityDetailImg != null) {
+                vo.setActivityDetailImgUrl(activityDetailImg.getImgUrl());
+            }
+
             voRecords.add(vo);
         }
 
@@ -600,5 +687,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);
+        }
+    }
 }
 

+ 118 - 2
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivitySignupServiceImpl.java

@@ -1,5 +1,6 @@
 package shop.alien.storeplatform.service.impl;
 
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -8,14 +9,19 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
+import shop.alien.entity.store.LifeNotice;
 import shop.alien.entity.store.LifeUser;
+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.StoreOperationalActivitySignupVO;
+import shop.alien.mapper.LifeNoticeMapper;
 import shop.alien.mapper.LifeUserMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivitySignupMapper;
+import shop.alien.storeplatform.feign.AlienStoreFeign;
 import shop.alien.storeplatform.service.OperationalActivitySignupService;
+import shop.alien.util.common.Constants;
 
 import java.util.Date;
 import java.util.List;
@@ -35,6 +41,8 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
     private final StoreOperationalActivitySignupMapper signupMapper;
     private final StoreOperationalActivityMapper activityMapper;
     private final LifeUserMapper lifeUserMapper;
+    private final LifeNoticeMapper lifeNoticeMapper;
+    private final AlienStoreFeign alienStoreFeign;
 
     @Override
     public IPage<StoreOperationalActivitySignupVO> querySignupList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize) {
@@ -85,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())
@@ -132,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())
@@ -140,6 +156,7 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
         if (activity != null) {
             vo.setActivityName(activity.getActivityName());
             vo.setActivityType(activity.getActivityType());
+            vo.setAuditTime(activity.getAuditTime());
         }
 
         // 查询用户昵称
@@ -169,7 +186,18 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
                 .set(StoreOperationalActivitySignup::getStatus, 2) // 2-通过
                 .set(StoreOperationalActivitySignup::getAuditTime, new Date()); // 设置审核时间为当前时间
 
-        return signupMapper.update(null, wrapper);
+        int result = signupMapper.update(null, wrapper);
+        
+        // 审核成功后发送通知
+        if (result > 0) {
+            try {
+                sendApprovalNotice(id, true);
+            } catch (Exception e) {
+                log.error("发送审核通过通知失败,signupId={}, error={}", id, e.getMessage(), e);
+            }
+        }
+        
+        return result;
     }
 
     @Override
@@ -192,7 +220,18 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
             wrapper.set(StoreOperationalActivitySignup::getRejectReason, "审核未通过");
         }
 
-        return signupMapper.update(null, wrapper);
+        int result = signupMapper.update(null, wrapper);
+        
+        // 审核拒绝后发送通知
+        if (result > 0) {
+            try {
+                sendApprovalNotice(id, false);
+            } catch (Exception e) {
+                log.error("发送审核拒绝通知失败,signupId={}, error={}", id, e.getMessage(), e);
+            }
+        }
+        
+        return result;
     }
 
     /**
@@ -216,5 +255,82 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
                 return "未知";
         }
     }
+
+    /**
+     * 发送审核通知
+     *
+     * @param signupId 报名ID
+     * @param approved 是否通过:true-通过,false-拒绝
+     */
+    private void sendApprovalNotice(Integer signupId, boolean approved) {
+        log.info("OperationalActivitySignupServiceImpl.sendApprovalNotice: signupId={}, approved={}", signupId, approved);
+
+        // 查询报名信息
+        StoreOperationalActivitySignup signup = signupMapper.selectById(signupId);
+        if (signup == null) {
+            log.warn("报名记录不存在,无法发送通知,signupId={}", signupId);
+            return;
+        }
+
+        // 获取用户信息
+        if (signup.getUserId() == null) {
+            log.warn("报名记录中用户ID为空,无法发送通知,signupId={}", signupId);
+            return;
+        }
+
+        LifeUser lifeUser = lifeUserMapper.selectById(signup.getUserId());
+        if (lifeUser == null || lifeUser.getUserPhone() == null) {
+            log.warn("用户信息不存在或手机号为空,无法发送通知,signupId={}, userId={}", signupId, signup.getUserId());
+            return;
+        }
+
+        String phone = lifeUser.getUserPhone();
+        String receiverId = "user_" + phone;
+
+        // 构建通知内容
+        String message;
+        if (approved) {
+            // 审核通过
+            message = "您报名参与的活动已通过商家报名条件,请耐心等待活动开始。";
+        } else {
+            // 审核拒绝
+            message = "您报名参与的活动未通过商家报名条件,如想继续参与请重新报名。";
+        }
+
+        // 创建通知对象
+        JSONObject contextJson = new JSONObject();
+        contextJson.put("signupId", signupId);
+        contextJson.put("activityId", signup.getActivityId());
+        contextJson.put("status", approved ? "通过" : "拒绝");
+        contextJson.put("message", message);
+
+        LifeNotice lifeNotice = new LifeNotice();
+        lifeNotice.setSenderId("system");
+        lifeNotice.setReceiverId(receiverId);
+        lifeNotice.setBusinessId(signupId);
+        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("活动审核通知发送成功,signupId={}, receiverId={}, approved={}", signupId, receiverId, approved);
+        } catch (Exception e) {
+            log.error("发送WebSocket通知失败,signupId={}, receiverId={}, error={}", signupId, receiverId, e.getMessage(), e);
+        }
+    }
 }
 

+ 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());
+        }
+    }
+}
+

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

@@ -98,14 +98,14 @@ public class StoreManageServiceImpl implements StoreManageService {
 
         // 1. 获取经营板块信息
         Integer businessSection = storeInfoDto.getBusinessSection();
-        StoreDictionary businessSectionDict = storeDictionaryMapper.selectOne(
-                new LambdaQueryWrapper<StoreDictionary>()
-                        .eq(StoreDictionary::getDictId, businessSection)
-                        .eq(StoreDictionary::getTypeName, "business_section")
-        );
-        if (businessSectionDict == null) {
-            throw new IllegalArgumentException("经营板块不存在:" + businessSection);
-        }
+//        StoreDictionary businessSectionDict = storeDictionaryMapper.selectOne(
+//                new LambdaQueryWrapper<StoreDictionary>()
+//                        .eq(StoreDictionary::getDictId, businessSection)
+//                        .eq(StoreDictionary::getTypeName, "business_section")
+//        );
+//        if (businessSectionDict == null) {
+//            throw new IllegalArgumentException("经营板块不存在:" + businessSection);
+//        }
 
         // 2. 获取经营种类信息
         List<String> businessTypes = storeInfoDto.getBusinessTypes();
@@ -162,7 +162,7 @@ public class StoreManageServiceImpl implements StoreManageService {
 
         // 7. 设置经营板块及类型
         storeInfo.setBusinessSection(businessSection);
-        storeInfo.setBusinessSectionName(businessSectionDict.getDictDetail());
+        storeInfo.setBusinessSectionName(storeInfoDto.getBusinessSectionName());
         storeInfo.setBusinessTypes(String.join(",", businessTypes));
         storeInfo.setBusinessTypesName(String.join(",", businessTypeNames));
 

+ 18 - 3
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformUserRoleServiceImpl.java

@@ -204,7 +204,7 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
 
                 if (storeUser != null) {
                     // 如果用户有 storeId 字段值,说明是主账号,不应删除
-                    if (storeUser.getStoreId() != null && storeUser.getStoreId() > 0) {
+                    if (storeUser.getStoreId() != null && storeUser.getStoreId() > 0 || storeUser.getAccountType()==1) {
                         log.info("用户是主账号,不删除 store_user 记录: userId={}, storeId={}", userId, storeUser.getStoreId());
                     } else {
                         // 不是主账号,可以安全删除
@@ -830,9 +830,24 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
             LambdaUpdateWrapper<StorePlatformUserRole> updateWrapper = new LambdaUpdateWrapper<>();
             updateWrapper.eq(StorePlatformUserRole::getId, id)
                     .eq(StorePlatformUserRole::getDeleteFlag, 0) // 只更新未删除的记录
-                    .set(StorePlatformUserRole::getStatus, status); // 根据传入的 status 值更新
-
+                    .set(StorePlatformUserRole::getStatus, status);
             int updateResult = storePlatformUserRoleMapper.update(null, updateWrapper);
+            // 根据传入的 status 值更新
+            StoreUser storeUser = storeUserMapper.selectById(userRole.getUserId());
+            if (storeUser != null && storeUser.getPhone() != null) {
+                // 删除Redis中的token,key格式:storePlatform_手机号
+                String tokenKey = "store_" + storeUser.getPhone();
+                String existingToken = baseRedisService.getString(tokenKey);
+                if (existingToken != null) {
+                    baseRedisService.delete(tokenKey);
+                    log.info("清除角色编辑后的用户token成功 userId={}, phone={}, tokenKey={}",
+                            userRole.getUserId(), storeUser.getPhone(), tokenKey);
+                } else {
+                    log.warn("用户token不存在或已过期,userId={}, phone={}, tokenKey={}",
+                            userRole.getUserId(), storeUser.getPhone(), tokenKey);
+                }
+            }
+
             if (updateResult > 0) {
                 String statusText = status == 0 ? "启用" : "禁用";
                 log.info("成功{}子账号: id={}, userId={}, storeId={}, roleId={}, status={}", 

+ 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;
+    }
+}
+
+

+ 4 - 1
alien-store-platform/src/main/java/shop/alien/storeplatform/util/AiContentModerationUtil.java

@@ -13,7 +13,9 @@ import org.springframework.util.StringUtils;
 import org.springframework.web.client.RestTemplate;
 
 import java.util.ArrayList;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * 通用图文审核工具类
@@ -167,7 +169,8 @@ public class AiContentModerationUtil {
             }
 
             // 检查是否有任何项目被标记为违规
-            List<String> violationReasons = new ArrayList<>();
+            Set<String> violationReasons = new LinkedHashSet<>();
+//            List<String> violationReasons = new ArrayList<>();
             boolean hasViolations = false;
 
             for (int i = 0; i < results.size(); i++) {

+ 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=BIN
alien-store-platform/src/main/resources/fonts/NotoSansCJKsc-Regular.otf


+ 7 - 0
alien-store/pom.xml

@@ -304,6 +304,13 @@
     </dependencies>
 
     <build>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <includes><include>**/*</include></includes>
+                <filtering>false</filtering>
+            </resource>
+        </resources>
         <plugins>
             <plugin>
                 <!-- 指定项目编译时的java版本和编码方式 -->

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

@@ -118,8 +118,8 @@ public class AiAuditController {
         requestBody.put("text", text);
         HttpHeaders aiHeaders = new HttpHeaders();
         aiHeaders.setContentType(MediaType.APPLICATION_JSON);
-//        aiHeaders.set("Authorization", "Bearer " + accessToken);
-        aiHeaders.set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1cHN0b3JlQGFkbWluLmNvbSIsImlkIjo2LCJ0aW1lIjoxNzYyOTI1NDAzLjY1MTY5MjZ9.07lz8Ox2cGC28UCmqcKCt5R6Rfwtgs-Eiu0ttgWRxws");
+        aiHeaders.set("Authorization", "Bearer " + accessToken);
+//        aiHeaders.set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1cHN0b3JlQGFkbWluLmNvbSIsImlkIjo2LCJ0aW1lIjoxNzYyOTI1NDAzLjY1MTY5MjZ9.07lz8Ox2cGC28UCmqcKCt5R6Rfwtgs-Eiu0ttgWRxws");
 
         HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, aiHeaders);
 

+ 78 - 43
alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java

@@ -24,14 +24,22 @@ import shop.alien.entity.store.LifeBlacklist;
 import shop.alien.entity.store.StoreImg;
 import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StoreInfoVo;
+import shop.alien.entity.store.CommonRating;
+import shop.alien.mapper.CommonRatingMapper;
 import shop.alien.mapper.LifeBlacklistMapper;
 import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreUserMapper;
 import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.CommonRatingService;
 import shop.alien.store.service.StoreImgService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
 
@@ -56,6 +64,7 @@ public class AiSearchController {
     private final RestTemplate restTemplate;
     private final StoreImgService storeImgService;
     private final CommonRatingService commonRatingService;
+    private final CommonRatingMapper commonRatingMapper;
 
     private final LifeBlacklistMapper lifeBlacklistMapper;
 
@@ -88,6 +97,7 @@ public class AiSearchController {
 
             ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(aiSearchExactUrl, request, String.class);
             String body = stringResponseEntity.getBody();
+            log.info("调用AI检索 处理前v 接口返回------{}", body);
             JSONObject jsonObject = JSONObject.parseObject(body);
             JSONObject jsonObject1 = new JSONObject();
             // 生活服务类别:转换为StoreInfoVo,确保返回的字段名按照StoreInfoVo定义
@@ -134,24 +144,28 @@ public class AiSearchController {
 
         HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, null);
         try {
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+            log.info("调用AI模糊搜索接口 请求参数------{} AI开始时间: {}", requestBody, formatter.format(Instant.now()));
             ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(aiSearchFuzzyUrl, request, String.class);
             String body = stringResponseEntity.getBody();
+            log.info("调用AI列表接口 处理前v 接口返回------{}  AI结束时间: {}", body, formatter.format(Instant.now()));
             JSONObject jsonObject = JSONObject.parseObject(body);
             JSONObject jsonObject1 = new JSONObject();
             // 模糊搜索:从related_results和matched_results字段获取数据
             List<StoreInfoVo> result = convertToStoreInfoList(jsonObject.getJSONArray("results"),map.get("userId"));
 
-            // 查找图片并设置到result中(图片类型1-入口图)
-            fillStoreImages(result, 1);
-
-            // 填充评论总数
-            fillRatingCount(result);
+            // 并发处理图片和评论数据,提升性能
+            CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> fillStoreImages(result, 1));
+            CompletableFuture<Void> ratingFuture = CompletableFuture.runAsync(() -> fillRatingCountBatch(result));
+            
+            // 等待两个任务完成
+            CompletableFuture.allOf(imageFuture, ratingFuture).join();
 
             jsonObject1.put("records", result);
 
             jsonObject1.put("total", jsonObject.get("total"));
             jsonObject1.put("size", map.get("pageSize"));
-            log.info("调用AI模糊搜索接口 接口返回------{}", body);
+            log.info("调用AI模糊搜索接口 接口返回------{}  最终时间: {}", body, formatter.format(Instant.now()));
             return R.data(jsonObject1);
         } catch (Exception e) {
             log.error("调用AI模糊搜索接口 接口异常------", e);
@@ -201,24 +215,7 @@ public class AiSearchController {
                 if(collect.contains(storeInfo.getId())){
                     continue;
                 }
-                Integer totalCount = 0;
-                double storeScore;
-                Object ratingObj =  commonRatingService.getRatingCount(storeInfo.getId(), 1);
-                if (ratingObj != null) {
-                    Map<String, Object> ratingMap = (Map<String, Object>) ratingObj;
-                    Object totalCountObj = ratingMap.get("totalCount");
-                    if (totalCountObj != null) {
-                        // 安全转换为整数
-                        try {
-                            totalCount = Integer.parseInt(totalCountObj.toString().trim());
-                        } catch (NumberFormatException e) {
-                            totalCount = 0; // 转换失败时默认值
-                        }
-                    } else {
-                        totalCount = 0;
-                    }
-                }
-                storeInfo.setTotalNum(totalCount.toString());
+                // 移除这里的getRatingCount调用,统一在fillRatingCountBatch中批量处理
                 storeInfoList.add(storeInfo);
             }
         }
@@ -280,34 +277,72 @@ public class AiSearchController {
     }
 
     /**
-     * 填充评论总数到StoreInfoVo列表中
+     * 填充评论总数到StoreInfoVo列表中(批量查询优化版本)
+     * 使用批量查询替代N+1查询,大幅提升性能
      *
      * @param result StoreInfoVo列表
      */
-    private void fillRatingCount(List<StoreInfoVo> result) {
+    private void fillRatingCountBatch(List<StoreInfoVo> result) {
         if (result == null || result.isEmpty()) {
             return;
         }
 
-        for (StoreInfoVo storeInfo : result) {
-            if (storeInfo.getId() != null) {
-                try {
-                    // 调用评论服务获取评论总数,businessId传id,businessType传1
-                    Object ratingCountObj = commonRatingService.getRatingCount(storeInfo.getId(), 1);
-
-                    // 将返回的Object转换为Map
-                    if (ratingCountObj instanceof Map) {
-                        Map<String, Object> ratingCountMap = (Map<String, Object>) ratingCountObj;
-                        // 从map中取出totalCount字段
-                        Object totalCount = ratingCountMap.get("totalCount");
-                        if (totalCount != null) {
-                            // 赋值给totalNum字段(转为String类型)
-                            storeInfo.setTotalNum(String.valueOf(totalCount));
+        // 提取所有storeId
+        List<Integer> storeIdList = result.stream()
+                .map(StoreInfoVo::getId)
+                .filter(id -> id != null)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (storeIdList.isEmpty()) {
+            return;
+        }
+
+        try {
+            // 批量查询所有store的评价记录(只查询审核通过且展示的)
+            LambdaQueryWrapper<CommonRating> wrapper = new LambdaQueryWrapper<>();
+            wrapper.in(CommonRating::getBusinessId, storeIdList)
+                    .eq(CommonRating::getBusinessType, 1)
+                    .eq(CommonRating::getAuditStatus, 1)
+                    .eq(CommonRating::getIsShow, 1);
+            List<CommonRating> allRatings = commonRatingMapper.selectList(wrapper);
+
+            // 按businessId分组统计评论总数
+            Map<Integer, Long> ratingCountMap = allRatings.stream()
+                    .collect(Collectors.groupingBy(
+                            CommonRating::getBusinessId,
+                            Collectors.counting()
+                    ));
+
+            // 设置评论总数到对应的StoreInfoVo
+            for (StoreInfoVo storeInfo : result) {
+                if (storeInfo.getId() != null) {
+                    Long count = ratingCountMap.get(storeInfo.getId());
+                    storeInfo.setTotalNum(count != null ? String.valueOf(count) : "0");
+                }
+            }
+        } catch (Exception e) {
+            log.error("批量获取评论总数失败", e);
+            // 如果批量查询失败,回退到单个查询(兜底策略)
+            for (StoreInfoVo storeInfo : result) {
+                if (storeInfo.getId() != null) {
+                    try {
+                        Object ratingCountObj = commonRatingService.getRatingCount(storeInfo.getId(), 1);
+                        if (ratingCountObj instanceof Map) {
+                            Map<String, Object> ratingCountMap = (Map<String, Object>) ratingCountObj;
+                            Object totalCount = ratingCountMap.get("totalCount");
+                            if (totalCount != null) {
+                                storeInfo.setTotalNum(String.valueOf(totalCount));
+                            } else {
+                                storeInfo.setTotalNum("0");
+                            }
+                        } else {
+                            storeInfo.setTotalNum("0");
                         }
+                    } catch (Exception ex) {
+                        log.warn("获取评论总数失败,storeId: {}", storeInfo.getId(), ex);
+                        storeInfo.setTotalNum("0");
                     }
-                } catch (Exception e) {
-                    log.warn("获取评论总数失败,storeId: {}", storeInfo.getId(), e);
-                    // 如果获取失败,继续处理下一个,不影响其他数据
                 }
             }
         }

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

@@ -0,0 +1,105 @@
+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不能为空");
+            }
+            //type: 0是ai客服,1是运营客服
+            if (!request.containsKey("type")) {
+                return R.fail("type不能为空");
+            }
+
+            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");
+            }
+
+            Object typeObj = request.get("type");
+            if (typeObj == null) {
+                return R.fail("type不能为空");
+            }
+
+            Integer type;
+            if (typeObj instanceof Integer) {
+                type = (Integer) typeObj;
+            } else if (typeObj instanceof Number) {
+                type = ((Number) typeObj).intValue();
+            } else {
+                try {
+                    type = Integer.parseInt(typeObj.toString());
+                } catch (NumberFormatException e) {
+                    return R.fail("type格式错误");
+                }
+            }
+
+            // type: 0是ai客服,1是运营客服
+            if (type != 0 && type != 1) {
+                return R.fail("type必须为0或1,0是ai客服,1是运营客服");
+            }
+
+            JSONObject result = aiUserSessionUtil.getUserSessions(userId, type);
+            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());
+        }
+    }
+}
+

+ 10 - 40
alien-store/src/main/java/shop/alien/store/controller/AliController.java

@@ -10,12 +10,8 @@ import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeUser;
 import shop.alien.entity.store.StoreAliPayLog;
-import shop.alien.entity.store.StoreInfo;
-import shop.alien.entity.store.StoreUser;
 import shop.alien.store.service.AliService;
 import shop.alien.store.service.LifeUserService;
-import shop.alien.store.service.StoreInfoService;
-import shop.alien.store.service.StoreUserService;
 import shop.alien.store.util.ali.AliApi;
 import shop.alien.store.util.ali.AliSms;
 import shop.alien.util.ali.AliOSSUtil;
@@ -26,10 +22,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.text.SimpleDateFormat;
 import java.util.Date;
-import java.util.List;
 import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
 
 /**
  * @author ssk
@@ -57,12 +50,8 @@ public class AliController {
 
     private final AliApi aliApi;
 
-    private final StoreUserService storeUserService;
-
     private final LifeUserService lifeUserService;
 
-    private final StoreInfoService storeInfoService;
-
     @ApiOperation("阿里回调")
     @ApiOperationSupport(order = 1)
     @GetMapping("/notify")
@@ -80,38 +69,19 @@ 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);
-        int size = 0;
+        String normalizedName = name == null ? null : name.trim();
+        String normalizedIdCard = idCard == null ? null : idCard.trim().toUpperCase();
+        log.info("AliController.getIdInfo?name={}&idCard={}", normalizedName, normalizedIdCard);
         if (appType == 0) {
-            //根据身份查询未注销的用户
-            size = lifeUserService
-                    .list(new LambdaQueryWrapper<LifeUser>()
-                            .eq(LifeUser::getIdCard, idCard)
-                            .eq(LifeUser::getRealName, name)
-                            .eq(LifeUser::getLogoutFlag, 0))
-                    .size();
-        } else {
-            //根据身份查询已入驻或审核中的商家
-            List<StoreUser> storeUserList = storeUserService
-                    .list(new LambdaQueryWrapper<StoreUser>()
-                            .eq(StoreUser::getIdCard, idCard)
-                            .eq(StoreUser::getName, name));
-            List<Integer> storeIds = storeUserList.stream()
-                    .map(StoreUser::getStoreId)
-                    .filter(Objects::nonNull)
-                    .collect(Collectors.toList());
-            if (!storeIds.isEmpty()) {
-                size = storeInfoService
-                        .list(new LambdaQueryWrapper<StoreInfo>()
-                                .in(StoreInfo::getId, storeIds)
-                                .notIn(StoreInfo::getStoreApplicationStatus, 2))
-                        .size();
+            // 根据身份证查询未注销用户:同一身份证只能实名一个账号
+            int size = lifeUserService.count(new LambdaQueryWrapper<LifeUser>()
+                    .eq(LifeUser::getIdCard, normalizedIdCard)
+                    .eq(LifeUser::getLogoutFlag, 0));
+            if (size > 0) {
+                return R.fail("该身份证已实名认证过");
             }
         }
-//        if (size > 0) {
-//            return R.fail("该身份证已实名认证过");
-//        }
-        if (aliPayConfig.getIdInfo(name, idCard)) {
+        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);

+ 23 - 2
alien-store/src/main/java/shop/alien/store/controller/CommonRatingController.java

@@ -83,14 +83,35 @@ public class CommonRatingController {
             storeId = "#{#commonRating.businessId}",
             targetType = "STORE"
     )
-    @ApiOperation(value = "新增评价", notes = "0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常")
+    @ApiOperation(value = "新增评价", notes = "0:成功(包括审核通过和审核不通过但允许创建的情况), 1:失败, 2:内容审核不通过(直接拒绝)。注意:小票审核和打卡审核不通过时,允许创建评论但审核状态设为不通过并记录审核原因")
     @PostMapping("/addRating")
     public R<Integer> add(@RequestBody CommonRating commonRating) {
         log.info("CommonRatingController.add?commonRating={}", commonRating);
         try {
-            return R.data(commonRatingService.saveCommonRating(commonRating));
+            // 参数校验
+            if (commonRating.getBusinessId() == null) {
+                return R.fail("参数错误:businessId不能为空");
+            }
+            if (commonRating.getBusinessType() == null) {
+                return R.fail("参数错误:businessType不能为空");
+            }
+            if (commonRating.getUserId() == null) {
+                return R.fail("参数错误:userId不能为空");
+            }
+            
+            Integer result = commonRatingService.saveCommonRating(commonRating);
+            if (result == 0) {
+                return R.data(result);
+            } else if (result == 2) {
+                return R.fail("评价内容包含敏感字符,审核不通过");
+            } else {
+                return R.fail("保存评价失败");
+            }
         } catch (IllegalArgumentException e) {
             return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("新增评价异常", e);
+            return R.fail("保存评价失败:" + e.getMessage());
         }
     }
 

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio