Parcourir la source

Merge branch 'refs/heads/sit' into uat-20260202

dujian il y a 1 mois
Parent
commit
e4c13d5558
62 fichiers modifiés avec 2601 ajouts et 932 suppressions
  1. 2 2
      alien-entity/src/main/java/shop/alien/entity/store/CommonComment.java
  2. 8 0
      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. 3 3
      alien-entity/src/main/java/shop/alien/entity/store/LifeDiscountCouponStoreFriend.java
  5. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeUserDynamics.java
  6. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreBanner.java
  7. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeDiscountCouponDto.java
  8. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreBannerDto.java
  9. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponFriendRuleDetailVo.java
  10. 10 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponFriendRuleVo.java
  11. 6 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeDiscountCouponVo.java
  12. 1 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/LifeCouponPlatformVo.java
  13. 4 4
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDTO.java
  14. 6 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityVO.java
  15. 23 0
      alien-entity/src/main/java/shop/alien/mapper/CommonCommentMapper.java
  16. 5 5
      alien-entity/src/main/java/shop/alien/mapper/CommonRatingMapper.java
  17. 5 3
      alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponFriendRuleDetailMapper.java
  18. 2 1
      alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponMapper.java
  19. 23 18
      alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponStoreFriendMapper.java
  20. 1 1
      alien-entity/src/main/java/shop/alien/mapper/LifeUserDynamicsMapper.java
  21. 6 5
      alien-entity/src/main/java/shop/alien/mapper/second/SecondGoodsMapper.java
  22. 2 1
      alien-entity/src/main/resources/mapper/LifeDiscountCouponFriendRuleMapper.xml
  23. 3 0
      alien-entity/src/main/resources/mapper/LifeDiscountCouponMapper.xml
  24. 125 54
      alien-job/src/main/java/shop/alien/job/store/StoreMembershipCardJob.java
  25. 2 2
      alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsServiceImpl.java
  26. 4 3
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/LifeCouponPlatformController.java
  27. 2 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/LifeDiscountCouponPlatformService.java
  28. 9 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/LifeDiscountCouponPlatformServiceImpl.java
  29. 8 5
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/MerchantAuthServiceImpl.java
  30. 7 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java
  31. 77 44
      alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java
  32. 1 1
      alien-store/src/main/java/shop/alien/store/controller/CommonRatingController.java
  33. 100 35
      alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponController.java
  34. 40 25
      alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponStoreFriendController.java
  35. 8 0
      alien-store/src/main/java/shop/alien/store/controller/LifeUserController.java
  36. 60 3
      alien-store/src/main/java/shop/alien/store/controller/StoreCuisineController.java
  37. 111 26
      alien-store/src/main/java/shop/alien/store/controller/StoreCustomerServiceController.java
  38. 10 10
      alien-store/src/main/java/shop/alien/store/controller/StoreOperationalActivityController.java
  39. 4 4
      alien-store/src/main/java/shop/alien/store/controller/StoreOperationalStatisticsController.java
  40. 2 2
      alien-store/src/main/java/shop/alien/store/service/LifeCommentService.java
  41. 9 6
      alien-store/src/main/java/shop/alien/store/service/LifeDiscountCouponService.java
  42. 15 3
      alien-store/src/main/java/shop/alien/store/service/LifeDiscountCouponStoreFriendService.java
  43. 201 107
      alien-store/src/main/java/shop/alien/store/service/LifeUserDynamicsService.java
  44. 36 2
      alien-store/src/main/java/shop/alien/store/service/LifeUserOrderService.java
  45. 1 0
      alien-store/src/main/java/shop/alien/store/service/LifeUserService.java
  46. 4 4
      alien-store/src/main/java/shop/alien/store/service/StoreOperationalStatisticsService.java
  47. 2 0
      alien-store/src/main/java/shop/alien/store/service/impl/CommonCommentServiceImpl.java
  48. 210 32
      alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java
  49. 568 247
      alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponServiceImpl.java
  50. 315 49
      alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponStoreFriendServiceImpl.java
  51. 4 4
      alien-store/src/main/java/shop/alien/store/service/impl/StoreBannerServiceImpl.java
  52. 110 84
      alien-store/src/main/java/shop/alien/store/service/impl/StoreClockInServiceImpl.java
  53. 99 15
      alien-store/src/main/java/shop/alien/store/service/impl/StoreCustomerServiceServiceImpl.java
  54. 53 69
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  55. 2 4
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityAchievementServiceImpl.java
  56. 16 17
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityServiceImpl.java
  57. 6 25
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalStatisticsServiceImpl.java
  58. 1 0
      alien-store/src/main/java/shop/alien/store/util/CommonConstant.java
  59. 2 2
      alien-store/src/main/java/shop/alien/store/util/ai/AiReportReviewUtil.java
  60. 227 0
      alien-store/src/main/java/shop/alien/store/util/ai/ReceiptAuditUtil.java
  61. 11 1
      alien-util/src/main/java/shop/alien/util/common/VideoUtils.java
  62. 2 1
      alien-util/src/main/java/shop/alien/util/common/constant/CommentSourceTypeEnum.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;
 

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

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

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

@@ -32,7 +32,7 @@ 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;
 
@@ -44,7 +44,7 @@ public class LifeDiscountCouponStoreFriend extends Model<LifeDiscountCouponStore
     @TableField("voucher_id")
     private String voucherId;
 
-    @ApiModelProperty(value = "好友店铺id")
+    @ApiModelProperty(value = "店铺用户ID(store_user.id),送券的用户")
     @TableField("friend_store_user_id")
     private Integer friendStoreUserId;
 
@@ -85,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;
 

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

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

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

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

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

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

@@ -54,5 +54,15 @@ public class LifeDiscountCouponFriendRuleVo extends LifeDiscountCouponFriendRule
     @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;
 }

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

@@ -168,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")

+ 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优惠券

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

@@ -33,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表示不限制")
@@ -92,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 = "活动限制人数")

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

@@ -72,5 +72,11 @@ public class StoreOperationalActivityVO extends StoreOperationalActivity {
 
     @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;
 }
 

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

@@ -59,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 来源类型

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

@@ -91,10 +91,10 @@ public interface CommonRatingMapper extends BaseMapper<CommonRating> {
     /**
      * 统计该用户在该店铺下已通过审核的好评次数(用于好评送券参与次数限制)
      * 注意:统计包括已删除的评论,防止用户删除评论后再次获得券
-     * 只统计活动开始时间之后创建的评论,活动开始前的评论不计入参与次数
+     * 审核成功之前的评论不计算在参与次数中(只统计审核成功时间在活动审核时间之后的评论)
      * @param userId 用户ID
      * @param storeId 店铺ID(business_id)
-     * @param activityStartTime 活动开始时间,为null时统计所有历史好评
+     * @param activityAuditTime 活动审核时间,为null时统计所有历史好评
      * @return 已通过的好评条数(business_type=1, audit_status=1, score>=4.5),包括已删除的评论
      */
     @Select("<script>" +
@@ -104,10 +104,10 @@ public interface CommonRatingMapper extends BaseMapper<CommonRating> {
             "AND user_id = #{userId} " +
             "AND audit_status = 1 " +
             "AND score >= 4.5 " +
-            "<if test='activityStartTime != null'>" +
-            "AND created_time >= #{activityStartTime} " +
+            "<if test='activityAuditTime != null'>" +
+            "AND updated_time >= #{activityAuditTime} " +
             "</if>" +
             "</script>")
-    int countPassedGoodRatingsByUserAndStore(@Param("userId") Long userId, @Param("storeId") Integer storeId, @Param("activityStartTime") Date activityStartTime);
+    int countPassedGoodRatingsByUserAndStore(@Param("userId") Long userId, @Param("storeId") Integer storeId, @Param("activityAuditTime") Date activityAuditTime);
 }
 

+ 5 - 3
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,7 +21,7 @@ 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);
 
     /** 查询收到的代金券列表(life_coupon,voucher_id 不为空) */
@@ -35,7 +34,9 @@ public interface LifeDiscountCouponFriendRuleDetailMapper extends BaseMapper<Lif
             "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 " +
+            "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 " +
@@ -45,6 +46,7 @@ public interface LifeDiscountCouponFriendRuleDetailMapper extends BaseMapper<Lif
     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 " +

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

@@ -72,7 +72,8 @@ public interface LifeDiscountCouponMapper extends BaseMapper<LifeDiscountCoupon>
      * @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);
+    List<LifeDiscountCoupon> selectListSingleTable(@Param("storeId") String storeId, @Param("couponStatus") Integer couponStatus, @Param("type") Integer type, @Param("couponType") Integer couponType);
 }

+ 23 - 18
alien-entity/src/main/java/shop/alien/mapper/LifeDiscountCouponStoreFriendMapper.java

@@ -36,26 +36,28 @@ public interface LifeDiscountCouponStoreFriendMapper extends BaseMapper<LifeDisc
     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" +
             "1 as type,\n" +
-            "null as voucherId\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);
 
     /** 好友赠我 - 代金券(type=4) */
-    @Select("select ldcsf.created_time endDate,CAST(lc.price AS DECIMAL(10,2)) nominalValue,null minimumSpendingAmount,img.img_url imgUrl,\n" +
+    @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" +
@@ -63,33 +65,35 @@ public interface LifeDiscountCouponStoreFriendMapper extends BaseMapper<LifeDisc
             "lc.id voucherId\n" +
             "from life_discount_coupon_store_friend ldcsf\n" +
             "left join life_coupon lc on lc.id = ldcsf.voucher_id and lc.delete_flag = 0\n" +
-            "left join store_info si on si.id = lc.store_id and si.delete_flag = 0\n" +
-            "left join store_user su on si.id = su.store_id\n" +
-            "left join store_img img on si.id = img.store_id and img.img_type = 1 and img.delete_flag = 0\n" +
+            "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 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" +
             "1 as type,\n" +
-            "null as voucherId\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 ldcsf.created_time endDate,CAST(lc.price AS DECIMAL(10,2)) nominalValue,null minimumSpendingAmount,img.img_url imgUrl,\n" +
+    @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" +
@@ -97,9 +101,10 @@ public interface LifeDiscountCouponStoreFriendMapper extends BaseMapper<LifeDisc
             "lc.id voucherId\n" +
             "from life_discount_coupon_store_friend ldcsf\n" +
             "left join life_coupon lc on lc.id = ldcsf.voucher_id and lc.delete_flag = 0\n" +
-            "left join store_info si on si.id = lc.store_id and si.delete_flag = 0\n" +
-            "left join store_user su on ldcsf.store_user_id = su.store_id\n" +
-            "left join store_img img on si.id = img.store_id and img.img_type = 1 and img.delete_flag = 0\n" +
+            "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);
 }

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

@@ -19,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 " +

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

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

+ 3 - 0
alien-entity/src/main/resources/mapper/LifeDiscountCouponMapper.xml

@@ -50,6 +50,9 @@
         <if test="type != null">
           AND type = #{type}
         </if>
+        <if test="couponType != null">
+          AND coupon_type = #{couponType}
+        </if>
     </select>
 
 </mapper>

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

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

+ 4 - 3
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) {
+        // 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);

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

+ 9 - 1
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();
         //如果根据类型查询

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

+ 7 - 1
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -494,11 +494,17 @@ 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());
+                }
             }
         }
 

+ 77 - 44
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;
 
@@ -135,25 +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 接口返回------{}", body);
+            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);
@@ -203,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);
             }
         }
@@ -282,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);
-                    // 如果获取失败,继续处理下一个,不影响其他数据
                 }
             }
         }

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

@@ -83,7 +83,7 @@ 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);

+ 100 - 35
alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponController.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeDiscountCoupon;
@@ -139,17 +140,19 @@ public class LifeDiscountCouponController {
         return lifeDiscountCouponUserService.receiveCoupon(lifeDiscountCouponUserDto);
     }
 
-    @ApiOperation("获取该用户该店铺优惠券列表")
+    @ApiOperation("获取该用户该店铺优惠券列表。couponType=1仅满减券,couponType=2仅折扣券,不传返回全部优惠券")
     @ApiOperationSupport(order = 7)
     @GetMapping("/getStoreUserCouponList")
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "storeId", value = "商户id", dataType = "String", paramType = "query", required = true)
+            @ApiImplicitParam(name = "storeId", value = "商户id", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "couponType", value = "优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选)", dataType = "Integer", paramType = "query", required = false)
     })
     public R<List<LifeDiscountCouponVo>> getStoreUserCouponList(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
-                                                                @RequestParam(value = "storeId") String storeId) {
-        log.info("LifeDiscountCouponController.getStoreUserCouponList?storeId={}", storeId);
+                                                                @RequestParam(value = "storeId") String storeId,
+                                                                @RequestParam(value = "couponType", required = false) Integer couponType) {
+        log.info("LifeDiscountCouponController.getStoreUserCouponList?storeId={}, couponType={}", storeId, couponType);
         try {
-            List<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getStoreUserCouponList(storeId, userLoginInfo);
+            List<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getStoreUserCouponList(storeId, userLoginInfo, couponType);
             return R.data(storeCouponList);
         } catch (Exception e) {
             log.error("LifeDiscountCouponController.getStoreUserCouponList ERROR Msg={}", e.getMessage());
@@ -157,20 +160,22 @@ public class LifeDiscountCouponController {
         }
     }
 
-    @ApiOperation("获取该用户该店铺优惠券已领可用不可用列表")
+    @ApiOperation("获取该用户该店铺优惠券已领可用不可用列表。couponType=1仅满减券,couponType=2仅折扣券,不传返回全部优惠券")
     @ApiOperationSupport(order = 8)
     @GetMapping("/getStoreUserUsableCouponList")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "storeId", value = "商户id", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "amount", value = "消费金额", dataType = "String", paramType = "query", required = true)
+            @ApiImplicitParam(name = "amount", value = "消费金额", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "couponType", value = "优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选)", dataType = "Integer", paramType = "query", required = false)
     })
     public R<Map> getStoreUserUsableCouponList(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
                                                @RequestParam(value = "storeId") String storeId,
-                                               @RequestParam(value = "amount") BigDecimal amount
+                                               @RequestParam(value = "amount") BigDecimal amount,
+                                               @RequestParam(value = "couponType", required = false) Integer couponType
     ) {
-        log.info("LifeDiscountCouponController.getStoreUserUsableCouponList?storeId={}", storeId);
+        log.info("LifeDiscountCouponController.getStoreUserUsableCouponList?storeId={}, couponType={}", storeId, couponType);
         try {
-            return R.data(lifeDiscountCouponService.getStoreUserUsableCouponList(storeId, userLoginInfo, amount));
+            return R.data(lifeDiscountCouponService.getStoreUserUsableCouponList(storeId, userLoginInfo, amount, couponType));
         } catch (Exception e) {
             log.error("LifeDiscountCouponController.getStoreUserUsableCouponList ERROR Msg={}", e.getMessage());
             return R.fail("查询失败");
@@ -178,39 +183,67 @@ public class LifeDiscountCouponController {
     }
 
 
-    @ApiOperation("获取该用户所有的优惠券列表")
+    @ApiOperation("获取该用户所有的优惠券列表。couponType=1仅满减券,couponType=2仅折扣券,不传返回全部优惠券")
     @ApiOperationSupport(order = 9)
     @GetMapping("/getUserCouponList")
-    @ApiImplicitParams({@ApiImplicitParam(name = "page", value = "分页页数", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "size", value = "分页条数", dataType = "String", paramType = "query", required = true),
+    @ApiImplicitParams({@ApiImplicitParam(name = "page", value = "分页页数", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "tabType", value = "分页类型(0:全部(未使用),1:即将过期,2:已使用,3:已过期)", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "type", value = "券类型(不传:优惠券+代金券都返回,1:仅优惠券查 life_discount_coupon,4:仅代金券查 life_coupon)", dataType = "Integer", paramType = "query", required = false)
+            @ApiImplicitParam(name = "type", value = "券类型(不传:优惠券+代金券都返回,1:仅优惠券查 life_discount_coupon,4:仅代金券查 life_coupon)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "couponType", value = "优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选,仅当type不为4时有效)", dataType = "Integer", paramType = "query", required = false)
     })
     public R<List<LifeDiscountCouponVo>> getUserCouponList(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
                                                            @RequestParam(value = "tabType") String tabType,
                                                            @RequestParam(defaultValue = "1") int page,
                                                            @RequestParam(defaultValue = "10") int size,
-                                                           @RequestParam(required = false) Integer type) {
-        log.info("LifeDiscountCouponController.getUserCouponList");
+                                                           @RequestParam(required = false) Integer type,
+                                                           @RequestParam(value = "couponType", required = false) Integer couponType) {
         try {
-            List<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getUserCouponList(userLoginInfo, page, size, tabType, type);
+            // 参数校验
+            if (StringUtils.isEmpty(tabType)) {
+                return R.fail("分页类型不能为空");
+            }
+            if (page < 1) {
+                page = 1;
+            }
+            if (size < 1 || size > 100) {
+                size = 10;
+            }
+            if (type != null && type != 1 && type != 4) {
+                return R.fail("券类型参数错误,必须为1(优惠券)或4(代金券)");
+            }
+            if (couponType != null && couponType != 1 && couponType != 2) {
+                return R.fail("优惠券类型参数错误,必须为1(满减券)或2(折扣券)");
+            }
+            
+            log.info("LifeDiscountCouponController.getUserCouponList?userId={}, tabType={}, page={}, size={}, type={}, couponType={}", 
+                    userLoginInfo.getUserId(), tabType, page, size, type, couponType);
+            
+            List<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getUserCouponList(
+                    userLoginInfo, page, size, tabType, type, couponType);
             return R.data(storeCouponList);
+        } catch (IllegalArgumentException e) {
+            log.error("LifeDiscountCouponController.getUserCouponList 参数错误, userId={}, error={}", 
+                    userLoginInfo != null ? userLoginInfo.getUserId() : "unknown", e.getMessage(), e);
+            return R.fail(e.getMessage());
         } catch (Exception e) {
-            log.error("LifeDiscountCouponController.getUserCouponList ERROR Msg={}", e.getMessage());
+            log.error("LifeDiscountCouponController.getUserCouponList ERROR, userId={}, error={}", 
+                    userLoginInfo != null ? userLoginInfo.getUserId() : "unknown", e.getMessage(), e);
             return R.fail("查询失败");
         }
     }
 
-    @ApiOperation("获取该店铺所有优惠券(分页), 好友优惠券")
+    @ApiOperation("获取该店铺所有优惠券(分页), 好友优惠券。couponType=1仅满减券,couponType=2仅折扣券,不传返回全部优惠券")
     @ApiOperationSupport(order = 10)
     @GetMapping("/getStoreAllCouponList")
-    @ApiImplicitParams({@ApiImplicitParam(name = "page", value = "分页页数", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "size", value = "分页条数", dataType = "String", paramType = "query", required = true),
+    @ApiImplicitParams({@ApiImplicitParam(name = "page", value = "分页页数", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "size", value = "分页条数", dataType = "Integer", paramType = "query", required = false),
             @ApiImplicitParam(name = "storeId", value = "商户id", dataType = "String", paramType = "query", required = true),
             @ApiImplicitParam(name = "couponName", value = "优惠券名称", dataType = "String", paramType = "query", required = false),
-            @ApiImplicitParam(name = "tab", value = "分页类型(0:全部(传其他也查全部),1:进行中,2:已结束)", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "couponsFromType", required = false, value = "查询类型(1:我的优惠券,2:好友的优惠券)"),
-            @ApiImplicitParam(name = "couponStatus", required = false, value = "优惠券状态(0:草稿,1:正式)"),
+            @ApiImplicitParam(name = "tab", value = "分页类型(0:全部,1:进行中,2:已结束,3:草稿,4:未开始,5:已下架,6:已清库)", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "couponsFromType", required = false, value = "查询类型(1:我的优惠券,2:好友的优惠券)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "couponStatus", required = false, value = "优惠券状态(0:草稿,1:正式)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "couponType", value = "优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选)", dataType = "Integer", paramType = "query", required = false),
     })
     public R<IPage<LifeDiscountCouponVo>> getStoreAllCouponList(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
                                                                 @RequestParam(defaultValue = "1") int page,
@@ -219,32 +252,64 @@ public class LifeDiscountCouponController {
                                                                 @RequestParam(value = "couponName", required = false) String couponName,
                                                                 @RequestParam(value = "tab") String tab,
                                                                 @RequestParam(value = "couponsFromType", defaultValue = "1") int couponsFromType,
-                                                                @RequestParam(value = "couponStatus", defaultValue = "1", required = false) int couponStatus
+                                                                @RequestParam(value = "couponStatus", defaultValue = "1", required = false) int couponStatus,
+                                                                @RequestParam(value = "couponType", required = false) Integer couponType
     ) {
-        log.info("LifeDiscountCouponController.getStoreAllCouponList?storeId={}, couponName={}, tab={}, page={}, size={}, couponStatus={}", storeId, couponName, tab, page, size, couponStatus);
         try {
-            IPage<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getStoreAllCouponList(storeId, userLoginInfo, page, size, couponName, tab, couponsFromType, couponStatus);
+            // 参数校验
+            if (StringUtils.isEmpty(storeId)) {
+                return R.fail("商户id不能为空");
+            }
+            if (StringUtils.isEmpty(tab)) {
+                return R.fail("分页类型不能为空");
+            }
+            if (page < 1) {
+                page = 1;
+            }
+            if (size < 1 || size > 100) {
+                size = 10;
+            }
+            if (couponsFromType != 1 && couponsFromType != 2) {
+                couponsFromType = 1;
+            }
+            if (couponStatus != 0 && couponStatus != 1) {
+                couponStatus = 1;
+            }
+            if (couponType != null && couponType != 1 && couponType != 2) {
+                return R.fail("优惠券类型参数错误,必须为1(满减券)或2(折扣券)");
+            }
+            
+            log.info("LifeDiscountCouponController.getStoreAllCouponList?storeId={}, couponName={}, tab={}, page={}, size={}, couponsFromType={}, couponStatus={}, couponType={}", 
+                    storeId, couponName, tab, page, size, couponsFromType, couponStatus, couponType);
+            
+            IPage<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getStoreAllCouponList(
+                    storeId, userLoginInfo, page, size, couponName, tab, couponsFromType, couponStatus, couponType);
             return R.data(storeCouponList);
+        } catch (IllegalArgumentException e) {
+            log.error("LifeDiscountCouponController.getStoreAllCouponList 参数错误, storeId={}, error={}", storeId, e.getMessage(), e);
+            return R.fail(e.getMessage());
         } catch (Exception e) {
-            log.error("LifeDiscountCouponController.getStoreCouponList ERROR Msg={}", e.getMessage());
+            log.error("LifeDiscountCouponController.getStoreAllCouponList ERROR, storeId={}, error={}", storeId, e.getMessage(), e);
             return R.fail("查询失败");
         }
     }
 
-    @ApiOperation("获取该店铺所有优惠券 代金券(不分页)")
+    @ApiOperation("获取该店铺所有优惠券 代金券(不分页)。couponType=1仅满减券,couponType=2仅折扣券,不传返回全部优惠券")
     @ApiOperationSupport(order = 11)
     @GetMapping("/getStoreAllCouponListPaginateNot")
     @ApiImplicitParams({
             @ApiImplicitParam(name = "storeId", value = "商户id", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "type", value = "类型:1-优惠券(默认),4-代金券", dataType = "Integer", paramType = "query")
+            @ApiImplicitParam(name = "type", value = "类型:1-优惠券(默认),4-代金券", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "couponType", value = "优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选,仅当type不为4时有效)", dataType = "Integer", paramType = "query", required = false)
     })
     public R<List<LifeDiscountCouponVo>> getStoreAllCouponListPaginateNot(@ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
                                                                           @RequestParam(value = "storeId") String storeId,@RequestParam(value = "status", required = false) String status,
                                                                           @RequestParam(value = "couponStatus", defaultValue = "1", required = false) int couponStatus,
-                                                                          @RequestParam(value = "type", defaultValue = "1", required = false) Integer type) {
-        log.info("LifeDiscountCouponController.getStoreAllCouponListPaginateNot?storeId={}, status={}, couponStatus={}, type={}", storeId, status, couponStatus, type);
+                                                                          @RequestParam(value = "type", defaultValue = "1", required = false) Integer type,
+                                                                          @RequestParam(value = "couponType", required = false) Integer couponType) {
+        log.info("LifeDiscountCouponController.getStoreAllCouponListPaginateNot?storeId={}, status={}, couponStatus={}, type={}, couponType={}", storeId, status, couponStatus, type, couponType);
         try {
-            List<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getStoreAllCouponListPaginateNot(status, storeId, userLoginInfo, couponStatus, type);
+            List<LifeDiscountCouponVo> storeCouponList = lifeDiscountCouponService.getStoreAllCouponListPaginateNot(status, storeId, userLoginInfo, couponStatus, type, couponType);
             return R.data(storeCouponList);
         } catch (Exception e) {
             log.error("LifeDiscountCouponController.getStoreAllCouponListPaginateNot ERROR Msg={}", e.getMessage());
@@ -289,7 +354,7 @@ public class LifeDiscountCouponController {
                                                                          @RequestParam(value = "couponStatus", defaultValue = "1", required = false) int couponStatus) {
         log.info("LifeDiscountCouponController.getMyCreatedDiscountCouponList?storeId={}", storeId);
         try {
-            List<LifeDiscountCouponVo> list = lifeDiscountCouponService.getStoreAllCouponListPaginateNot(status, storeId, userLoginInfo, couponStatus, 1);
+            List<LifeDiscountCouponVo> list = lifeDiscountCouponService.getStoreAllCouponListPaginateNot(status, storeId, userLoginInfo, couponStatus, 1, null);
             return R.data(list);
         } catch (Exception e) {
             log.error("LifeDiscountCouponController.getMyCreatedDiscountCouponList ERROR Msg={}", e.getMessage());
@@ -311,7 +376,7 @@ public class LifeDiscountCouponController {
                                                                  @RequestParam(value = "couponStatus", defaultValue = "1", required = false) int couponStatus) {
         log.info("LifeDiscountCouponController.getMyCreatedVoucherList?storeId={}", storeId);
         try {
-            List<LifeDiscountCouponVo> list = lifeDiscountCouponService.getStoreAllCouponListPaginateNot(status, storeId, userLoginInfo, couponStatus, 4);
+            List<LifeDiscountCouponVo> list = lifeDiscountCouponService.getStoreAllCouponListPaginateNot(status, storeId, userLoginInfo, couponStatus, 4, null);
             return R.data(list);
         } catch (Exception e) {
             log.error("LifeDiscountCouponController.getMyCreatedVoucherList ERROR Msg={}", e.getMessage());

+ 40 - 25
alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponStoreFriendController.java

@@ -7,18 +7,16 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeDiscountCouponFriendRule;
-import shop.alien.entity.store.LifeGroupBuyThali;
 import shop.alien.entity.store.UserLoginInfo;
 import shop.alien.entity.store.dto.LifeDiscountCouponStoreFriendDto;
-import shop.alien.entity.store.dto.LifeGroupBuyDto;
 import shop.alien.entity.store.vo.LifeDiscountCouponFriendRuleDetailVo;
 import shop.alien.entity.store.vo.LifeDiscountCouponFriendRuleVo;
 import shop.alien.entity.store.vo.LifeDiscountCouponStoreFriendVo;
-import shop.alien.entity.store.vo.LifeGroupBuyCountDateVo;
 import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.LifeDiscountCouponStoreFriendService;
 import shop.alien.util.common.TokenInfo;
 import springfox.documentation.annotations.ApiIgnore;
+import org.apache.commons.lang3.StringUtils;
 
 import java.util.List;
 import java.util.Map;
@@ -163,43 +161,60 @@ public class LifeDiscountCouponStoreFriendController {
         return R.data(lifeDiscountCouponStoreFriendService.getRuleById(id));
     }
 
-    @ApiOperation("查询好友赠券。type=4 返回代金券,否则返回优惠券")
+    @ApiOperation("获取收到的好友优惠券列表。couponType=1仅满减券,couponType=2仅折扣券,不传返回全部优惠券。type=4 返回代金券,否则返回优惠券")
+    @GetMapping("/getReceivedFriendCouponList")
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "storeId", value = "当前登录店铺id", dataType = "String", paramType = "query", required = true),
-            @ApiImplicitParam(name = "friendStoreUserId", value = "选中好友店铺用户id", dataType = "String", paramType = "query", required = false),
-            @ApiImplicitParam(name = "type", value = "4=代金券,其他=优惠券", dataType = "Integer", paramType = "query", required = false)
+            @ApiImplicitParam(name = "storeId", value = "店铺id", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "friendStoreUserId", value = "好友店铺用户id", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "type", value = "类型:4=仅代金券,不传=全部(优惠券+代金券)(可选)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "couponType", value = "优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选,仅当type不为4时有效)", dataType = "Integer", paramType = "query", required = false)
     })
-    @GetMapping("/getReceivedFriendCouponList")
     public R<List<LifeDiscountCouponFriendRuleDetailVo>> getReceivedFriendCouponList(@RequestParam(value = "storeId") String storeId,
                                                                                      @RequestParam(value = "friendStoreUserId", required = false) String friendStoreUserId,
-                                                                                     @RequestParam(value = "type", required = false) Integer type) {
-        log.info("LifeDiscountCouponStoreFriendController.getReceivedFriendCouponList?storeId={},friendStoreUserId={},type={}", storeId, friendStoreUserId, type);
-        return R.data(lifeDiscountCouponStoreFriendService.getReceivedFriendCouponList(storeId, friendStoreUserId, type));
+                                                                                     @RequestParam(value = "type", required = false) Integer type,
+                                                                                     @RequestParam(value = "couponType", required = false) Integer couponType) {
+        log.info("LifeDiscountCouponStoreFriendController.getReceivedFriendCouponList?storeId={},friendStoreUserId={},type={},couponType={}", storeId, friendStoreUserId, type, couponType);
+        return R.data(lifeDiscountCouponStoreFriendService.getReceivedFriendCouponList(storeId, friendStoreUserId, type, couponType));
     }
 
     @ApiOperation("查询赠券规则")
-    @ApiImplicitParams({@ApiImplicitParam(name = "storeId", value = "当前登录店铺id", dataType = "String", paramType = "query", required = true)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "当前登录店铺id", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "acName", value = "活动名称(模糊查询,可选)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "status", value = "状态:0-启用,1-禁用(可选)", dataType = "String", paramType = "query", required = false)
     })
     @GetMapping("/getRuleList")
-    public R<List<LifeDiscountCouponFriendRuleVo>> getRuleList(@RequestParam(value = "storeId") String storeId, String acName, String status) {
-        log.info("LifeDiscountCouponStoreFriendController.getRuleList?storeId={},name={},status={}", storeId, acName, status);
-        return R.data(lifeDiscountCouponStoreFriendService.getRuleList(storeId, acName, status));
+    public R<List<LifeDiscountCouponFriendRuleVo>> getRuleList(@RequestParam(value = "storeId") String storeId,
+                                                                @RequestParam(value = "acName", required = false) String acName,
+                                                                @RequestParam(value = "status", required = false) String status) {
+        log.info("LifeDiscountCouponStoreFriendController.getRuleList?storeId={},acName={},status={}", storeId, acName, status);
+        try {
+            if (StringUtils.isEmpty(storeId)) {
+                return R.fail("店铺ID不能为空");
+            }
+            return R.data(lifeDiscountCouponStoreFriendService.getRuleList(storeId, acName, status));
+        } catch (Exception e) {
+            log.error("LifeDiscountCouponStoreFriendController.getRuleList ERROR Msg={}", e.getMessage(), e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
     }
 
-    @ApiOperation("查询赠券记录。type=4 仅代金券,不传返回全部(优惠券+代金券)")
+    @ApiOperation("查询赠券记录(商户送给商户券)。queryType=1查询我收到的,queryType=2查询我送出的。type=4仅代金券,不传返回全部(优惠券+代金券)。couponType=1仅满减券,couponType=2仅折扣券,不传返回全部优惠券")
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "storeUserId", value = "好友赠我-当前登录店铺id", dataType = "String", paramType = "query", required = false),
-            @ApiImplicitParam(name = "friendStoreUserId", value = "我赠好友-选中好友店铺用户id", dataType = "String", paramType = "query", required = false),
-            @ApiImplicitParam(name = "storeName", value = "店铺名称模糊", dataType = "String", paramType = "query", required = false),
-            @ApiImplicitParam(name = "type", value = "4=仅代金券,不传=全部", dataType = "Integer", paramType = "query", required = false)
+            @ApiImplicitParam(name = "storeUserId", value = "当前登录店铺用户id(必填)", dataType = "String", paramType = "query", required = true),
+            @ApiImplicitParam(name = "queryType", value = "查询类型:1=我收到的(所有好友赠送给我的),2=我送出的(我送给所有好友的)(必填)", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "storeName", value = "店铺名称模糊查询(可选)", dataType = "String", paramType = "query", required = false),
+            @ApiImplicitParam(name = "type", value = "4=仅代金券,不传=全部(优惠券+代金券)(可选)", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "couponType", value = "优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选,仅当type不为4时有效)", dataType = "Integer", paramType = "query", required = false)
     })
     @GetMapping("/getReceivedSendFriendCouponList")
-    public R<List<LifeDiscountCouponFriendRuleVo>> getReceivedSendFriendCouponList(@RequestParam(value = "storeUserId", required = false) String storeUserId,
-                                                                                   @RequestParam(value = "friendStoreUserId", required = false) String friendStoreUserId,
+    public R<List<LifeDiscountCouponFriendRuleVo>> getReceivedSendFriendCouponList(@RequestParam(value = "storeUserId") String storeUserId,
+                                                                                   @RequestParam(value = "queryType") Integer queryType,
                                                                                    @RequestParam(value = "storeName", required = false) String storeName,
-                                                                                   @RequestParam(value = "type", required = false) Integer type) {
-        log.info("LifeDiscountCouponStoreFriendController.getReceivedSendFriendCouponList?storeUserId={},friendStoreUserId={},storeName={},type={}", storeUserId, friendStoreUserId, storeName, type);
-        return R.data(lifeDiscountCouponStoreFriendService.getReceivedSendFriendCouponList(storeUserId, friendStoreUserId, storeName, type));
+                                                                                   @RequestParam(value = "type", required = false) Integer type,
+                                                                                   @RequestParam(value = "couponType", required = false) Integer couponType) {
+        log.info("LifeDiscountCouponStoreFriendController.getReceivedSendFriendCouponList?storeUserId={},queryType={},storeName={},type={},couponType={}", storeUserId, queryType, storeName, type, couponType);
+        return R.data(lifeDiscountCouponStoreFriendService.getReceivedSendFriendCouponList(storeUserId, queryType, storeName, type, couponType));
 
 
 

+ 8 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeUserController.java

@@ -1,5 +1,6 @@
 package shop.alien.store.controller;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
@@ -73,6 +74,13 @@ public class LifeUserController {
     @PostMapping("/modifyUser")
     public R<Boolean> modifyUser(@RequestBody LifeUser user) {
         log.info("LifeUserController.modifyUser?user={}", user.toString());
+
+        int size = lifeUserService.count(new LambdaQueryWrapper<LifeUser>()
+                .eq(LifeUser::getIdCard, user.getIdCard())
+                .eq(LifeUser::getDeleteFlag, 0));
+        if (size > 0) {
+            return  R.fail("该证件已被实名");
+        }
         boolean isChangeRealInfo = lifeUserService.checkRealInfo(user);
         int num = service.modifyUser(user);
         if (num == 0) {

+ 60 - 3
alien-store/src/main/java/shop/alien/store/controller/StoreCuisineController.java

@@ -13,6 +13,7 @@ import org.springframework.beans.BeanUtils;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreCuisine;
+import shop.alien.entity.store.StoreCuisineCombo;
 import shop.alien.entity.store.StoreInfo;
 import shop.alien.entity.store.StorePrice;
 import shop.alien.entity.store.dto.CuisineComboDto;
@@ -21,6 +22,7 @@ import shop.alien.entity.store.dto.TablewareFeeDto;
 import shop.alien.entity.store.vo.PriceListVo;
 import shop.alien.mapper.StoreCuisineMapper;
 import shop.alien.store.annotation.TrackEvent;
+import shop.alien.store.service.StoreCuisineComboService;
 import shop.alien.store.service.StoreCuisineService;
 import shop.alien.store.service.StoreInfoService;
 import shop.alien.store.service.StorePriceService;
@@ -59,6 +61,8 @@ public class StoreCuisineController {
 
     private final StoreCuisineMapper storeCuisineMapper;
 
+    private final StoreCuisineComboService storeCuisineComboService;
+
     @ApiOperation("新增美食套餐或单品")
     @ApiOperationSupport(order = 1)
     @PostMapping("/addCuisineCombo")
@@ -376,13 +380,66 @@ public class StoreCuisineController {
     @PostMapping("/changeShelfStatus")
     public R<String> changeShelfStatus(@RequestParam("id") Integer id, @RequestParam("shelfStatus") Integer shelfStatus) {
         log.info("StoreCuisineController.changeShelfStatus?id={},shelfStatus={}", id, shelfStatus);
+        
+        // 参数验证
+        if (id == null) {
+            return R.fail("美食价目ID不能为空");
+        }
         if (shelfStatus == null || (shelfStatus != 1 && shelfStatus != 2)) {
             return R.fail("上下架状态不合法(只能为1或2)");
         }
-        if (storeCuisineService.changeShelfStatus(id, shelfStatus)) {
-            return R.success("操作成功");
+        
+        try {
+            // 查询当前美食信息
+            StoreCuisine cuisine = storeCuisineService.getById(id);
+            if (cuisine == null) {
+                return R.fail("美食价目不存在");
+            }
+            
+            // 如果是下架操作(shelfStatus == 2),且是单品(cuisineType == 1),需要检查是否被套餐引用
+            if (shelfStatus == 2 && cuisine.getCuisineType() != null && cuisine.getCuisineType() == 1) {
+                // 查询是否有套餐引用了该单品
+                LambdaQueryWrapper<StoreCuisineCombo> comboQueryWrapper = new LambdaQueryWrapper<>();
+                comboQueryWrapper.eq(StoreCuisineCombo::getSid, id);
+                List<StoreCuisineCombo> comboList = storeCuisineComboService.list(comboQueryWrapper);
+                
+                if (comboList != null && !comboList.isEmpty()) {
+                    // 收集所有引用该单品的套餐ID
+                    List<Integer> comboIds = comboList.stream()
+                            .map(StoreCuisineCombo::getCid)
+                            .distinct()
+                            .collect(java.util.stream.Collectors.toList());
+                    
+                    // 查询这些套餐的上架状态
+                    List<StoreCuisine> combos = new ArrayList<>(storeCuisineService.listByIds(comboIds));
+                    List<String> onShelfComboNames = new ArrayList<>();
+                    
+                    for (StoreCuisine combo : combos) {
+                        // 检查套餐是否还在上架状态(shelfStatus == 1)
+                        if (combo.getShelfStatus() != null && combo.getShelfStatus() == 1) {
+                            onShelfComboNames.add(combo.getName());
+                        }
+                    }
+                    
+                    // 如果有套餐还在上架,不能下架单品
+                    if (!onShelfComboNames.isEmpty()) {
+                        String comboNames = String.join("、", onShelfComboNames);
+                        log.warn("下架单品失败:单品ID={}被以下套餐引用且套餐还在上架:{}", id, comboNames);
+                        return R.fail("该单品被套餐引用,无法下架。请先下架以下套餐:" + comboNames);
+                    }
+                }
+            }
+            
+            // 执行上下架操作
+            if (storeCuisineService.changeShelfStatus(id, shelfStatus)) {
+                return R.success("操作成功");
+            }
+            return R.fail("操作失败");
+            
+        } catch (Exception e) {
+            log.error("上下架操作异常,ID: {}, shelfStatus: {}", id, shelfStatus, e);
+            return R.fail("操作失败:" + e.getMessage());
         }
-        return R.fail("操作失败");
     }
 
     @ApiOperation("分页查询美食价目/通用价目")

+ 111 - 26
alien-store/src/main/java/shop/alien/store/controller/StoreCustomerServiceController.java

@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.AiIntelligentAssistant;
@@ -36,11 +38,19 @@ public class StoreCustomerServiceController {
     @ApiImplicitParams({@ApiImplicitParam(name = "question", value = "问题关键字", dataType = "String", paramType = "query", required = true)})
     @GetMapping("/getByQuestion")
     public R<StoreCustomerService> getByQuestion(@RequestParam String question) {
-        StoreCustomerService result = storeCustomerServiceService.getByQuestion(question);
-        if (null == result) {
-            return R.fail("暂未搜索到相应答案");
+        try {
+            if (!StringUtils.hasText(question)) {
+                return R.fail("问题关键字不能为空");
+            }
+            StoreCustomerService result = storeCustomerServiceService.getByQuestion(question);
+            if (result == null) {
+                return R.fail("暂未搜索到相应答案");
+            }
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("StoreCustomerServiceController.getByQuestion ERROR, question={}, error={}", question, e.getMessage(), e);
+            return R.fail("查询失败");
         }
-        return R.data(result);
     }
 
     @ApiOperation("获取随机问题")
@@ -49,8 +59,19 @@ public class StoreCustomerServiceController {
     , @ApiImplicitParam(name = "limit", value = "数量", dataType = "Integer", paramType = "query", required = true)})
     @GetMapping("/getRandList")
     public R<List<StoreCustomerService>> getRandList(@RequestParam String type, @RequestParam Integer limit) {
-        List<StoreCustomerService> result = storeCustomerServiceService.getRandList(type,limit);
-        return R.data(result);
+        try {
+            if (!StringUtils.hasText(type)) {
+                return R.fail("类型不能为空");
+            }
+            if (limit == null || limit <= 0) {
+                return R.fail("数量必须大于0");
+            }
+            List<StoreCustomerService> result = storeCustomerServiceService.getRandList(type, limit);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("StoreCustomerServiceController.getRandList ERROR, type={}, limit={}, error={}", type, limit, e.getMessage(), e);
+            return R.fail("查询失败");
+        }
     }
 
     @ApiOperation("删除问题")
@@ -58,9 +79,17 @@ public class StoreCustomerServiceController {
     @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "id", dataType = "String", paramType = "query", required = true)
             })
     @GetMapping("/delStoreCustomerService")
-    public R delStoreCustomerService(@RequestParam String id) {
-        storeCustomerServiceService.delStoreCustomerService(id);
-        return R.data("删除成功");
+    public R<String> delStoreCustomerService(@RequestParam String id) {
+        try {
+            if (!StringUtils.hasText(id)) {
+                return R.fail("id不能为空");
+            }
+            storeCustomerServiceService.delStoreCustomerService(id);
+            return R.data("删除成功");
+        } catch (Exception e) {
+            log.error("StoreCustomerServiceController.delStoreCustomerService ERROR, id={}, error={}", id, e.getMessage(), e);
+            return R.fail("删除失败");
+        }
     }
 
     @ApiOperation("中台-问题列表")
@@ -71,21 +100,40 @@ public class StoreCustomerServiceController {
             @ApiImplicitParam(name = "type", value = "类型(1/商家/2用户)", dataType = "String", paramType = "query", required = false),
     })
     @GetMapping("/getStoreCustomerServicePage")
-    private R<IPage<StoreCustomerService>> getStoreCustomerServicePage(@RequestParam(value = "page", defaultValue = "1") int page,
+    public R<IPage<StoreCustomerService>> getStoreCustomerServicePage(@RequestParam(value = "page", defaultValue = "1") int page,
                                                        @RequestParam(value = "size", defaultValue = "10") int size,
                                                        @RequestParam(value = "question", required = false) String question,
                                                        @RequestParam(value = "type", required = false) String type) {
-        log.info("StoreCustomerServiceController.getStoreCustomerServicePage?page={},size={},question={},type={}", page, size, question, type);
-        return R.data(storeCustomerServiceService.getStoreCustomerServicePage(page, size, question, type));
+        try {
+            if (page < 1) {
+                page = 1;
+            }
+            if (size < 1 || size > 100) {
+                size = 10;
+            }
+            log.info("StoreCustomerServiceController.getStoreCustomerServicePage?page={},size={},question={},type={}", page, size, question, type);
+            return R.data(storeCustomerServiceService.getStoreCustomerServicePage(page, size, question, type));
+        } catch (Exception e) {
+            log.error("StoreCustomerServiceController.getStoreCustomerServicePage ERROR, page={}, size={}, error={}", page, size, e.getMessage(), e);
+            return R.fail("查询失败");
+        }
     }
 
     @ApiOperation("中台-保存问题")
     @ApiOperationSupport(order = 5)
     @PostMapping("/saveStoreCustomerService")
     public R<StoreCustomerService> saveStoreCustomerService(@RequestBody StoreCustomerService storeCustomerService) {
-        log.info("StoreCustomerServiceController.saveStoreCustomerService?storeCustomerService={}", storeCustomerService.toString());
-        StoreCustomerService saved = storeCustomerServiceService.saveStoreCustomerService(storeCustomerService);
-        return R.data(saved);
+        try {
+            if (storeCustomerService == null) {
+                return R.fail("参数不能为空");
+            }
+            log.info("StoreCustomerServiceController.saveStoreCustomerService?storeCustomerService={}", storeCustomerService);
+            StoreCustomerService saved = storeCustomerServiceService.saveStoreCustomerService(storeCustomerService);
+            return R.data(saved);
+        } catch (Exception e) {
+            log.error("StoreCustomerServiceController.saveStoreCustomerService ERROR, storeCustomerService={}, error={}", storeCustomerService, e.getMessage(), e);
+            return R.fail("保存失败");
+        }
     }
 
     @ApiOperation("id查询问题")
@@ -94,28 +142,65 @@ public class StoreCustomerServiceController {
     })
     @GetMapping("/getById")
     public R<StoreCustomerService> getById(@RequestParam String id) {
-        log.info("StoreCustomerServiceController.getById?id={}", id);
-        StoreCustomerService saved = storeCustomerServiceService.getById(id);
-        return R.data(saved);
+        try {
+            if (!StringUtils.hasText(id)) {
+                return R.fail("id不能为空");
+            }
+            log.info("StoreCustomerServiceController.getById?id={}", id);
+            StoreCustomerService saved = storeCustomerServiceService.getById(id);
+            if (saved == null) {
+                return R.fail("未找到对应的问题");
+            }
+            return R.data(saved);
+        } catch (Exception e) {
+            log.error("StoreCustomerServiceController.getById ERROR, id={}, error={}", id, e.getMessage(), e);
+            return R.fail("查询失败");
+        }
     }
 
     @ApiOperation("保存聊天")
-    @ApiOperationSupport(order = 5)
+    @ApiOperationSupport(order = 7)
     @PostMapping("/saveAiIntelligentAssistant")
     public R<List<AiIntelligentAssistant>> saveAiIntelligentAssistant(@RequestBody List<AiIntelligentAssistant> aiIntelligentAssistants) {
-        log.info("StoreCustomerServiceController.saveAiIntelligentAssistant?aiIntelligentAssistants={}", aiIntelligentAssistants.toString());
-        List<AiIntelligentAssistant> saved = storeCustomerServiceService.saveAiIntelligentAssistant(aiIntelligentAssistants);
-        return R.data(saved);
+        try {
+            if (CollectionUtils.isEmpty(aiIntelligentAssistants)) {
+                return R.fail("聊天记录不能为空");
+            }
+            log.info("StoreCustomerServiceController.saveAiIntelligentAssistant?size={}", aiIntelligentAssistants.size());
+            List<AiIntelligentAssistant> saved = storeCustomerServiceService.saveAiIntelligentAssistant(aiIntelligentAssistants);
+            return R.data(saved);
+        } catch (Exception e) {
+            log.error("StoreCustomerServiceController.saveAiIntelligentAssistant ERROR, size={}, error={}", 
+                    aiIntelligentAssistants != null ? aiIntelligentAssistants.size() : 0, e.getMessage(), e);
+            return R.fail("保存失败");
+        }
     }
 
     @ApiOperation("查询聊天记录")
-    @ApiOperationSupport(order = 2)
+    @ApiOperationSupport(order = 8)
     @ApiImplicitParams({@ApiImplicitParam(name = "userId", value = "用户id)", dataType = "String", paramType = "query", required = true)
             , @ApiImplicitParam(name = "time", value = "时间", dataType = "String", paramType = "query", required = true)
             , @ApiImplicitParam(name = "talkSource", value = "1平台使用/2商户运营", dataType = "Integer", paramType = "query", required = true)})
     @GetMapping("/selectAiIntelligentAssistant")
-    public R<List<AiIntelligentAssistant>> selectAiIntelligentAssistant(@RequestParam String userId, @RequestParam String time,@RequestParam Integer talkSource) {
-        List<AiIntelligentAssistant> result = storeCustomerServiceService.selectAiIntelligentAssistant(userId,time,talkSource);
-        return R.data(result);
+    public R<List<AiIntelligentAssistant>> selectAiIntelligentAssistant(@RequestParam String userId, 
+                                                                         @RequestParam String time,
+                                                                         @RequestParam Integer talkSource) {
+        try {
+            if (!StringUtils.hasText(userId)) {
+                return R.fail("用户id不能为空");
+            }
+            if (!StringUtils.hasText(time)) {
+                return R.fail("时间参数不能为空");
+            }
+            if (talkSource == null || (talkSource != 1 && talkSource != 2)) {
+                return R.fail("talkSource参数错误,必须为1或2");
+            }
+            List<AiIntelligentAssistant> result = storeCustomerServiceService.selectAiIntelligentAssistant(userId, time, talkSource);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("StoreCustomerServiceController.selectAiIntelligentAssistant ERROR, userId={}, time={}, talkSource={}, error={}", 
+                    userId, time, talkSource, e.getMessage(), e);
+            return R.fail("查询失败");
+        }
     }
 }

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

@@ -86,7 +86,7 @@ public class StoreOperationalActivityController {
     }
 
 
-    @ApiOperation("报名校验")
+    @ApiOperation("报名校验(用户端)")
     @ApiOperationSupport(order = 2)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true),
@@ -107,7 +107,7 @@ public class StoreOperationalActivityController {
         }
     }
 
-    @ApiOperation("活动报名")
+    @ApiOperation("活动报名(用户端)")
     @ApiOperationSupport(order = 3)
     @PostMapping("/signup")
     public R<String> signup(@RequestBody StoreOperationalActivitySignupDto dto) {
@@ -123,7 +123,7 @@ public class StoreOperationalActivityController {
         }
     }
 
-    @ApiOperation("新增成果")
+    @ApiOperation("新增成果(用户端)")
     @ApiOperationSupport(order = 4)
     @PostMapping("/achievement/add")
     public R<String> addAchievement(@RequestBody StoreOperationalActivityAchievementDto dto) {
@@ -139,7 +139,7 @@ public class StoreOperationalActivityController {
         }
     }
 
-    @ApiOperation("成果详情")
+    @ApiOperation("成果详情(用户端)")
     @ApiOperationSupport(order = 5)
     @ApiImplicitParam(name = "id", value = "成果ID", dataTypeClass = Integer.class, paramType = "query", required = true)
     @GetMapping("/achievement/detail")
@@ -156,7 +156,7 @@ public class StoreOperationalActivityController {
         }
     }
 
-    @ApiOperation("成果列表")
+    @ApiOperation("成果列表(用户端)")
     @ApiOperationSupport(order = 6)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query"),
@@ -184,7 +184,7 @@ public class StoreOperationalActivityController {
         }
     }
 
-    @ApiOperation("案例列表")
+    @ApiOperation("案例列表(用户端)")
     @ApiOperationSupport(order = 7)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "storeId", value = "店铺ID", dataTypeClass = Integer.class, paramType = "query", required = true),
@@ -212,7 +212,7 @@ public class StoreOperationalActivityController {
         }
     }
 
-    @ApiOperation("案例卡片概览(列表默认一条,含总数)")
+    @ApiOperation("案例卡片概览(用户端,列表默认一条,含总数)")
     @ApiOperationSupport(order = 8)
     @ApiImplicitParam(name = "storeId", value = "店铺ID", dataTypeClass = Integer.class, paramType = "query", required = true)
     @GetMapping("/achievement/case/preview")
@@ -229,7 +229,7 @@ public class StoreOperationalActivityController {
         }
     }
 
-    @ApiOperation("案例详情")
+    @ApiOperation("案例详情(用户端)")
     @ApiOperationSupport(order = 9)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true),
@@ -251,7 +251,7 @@ public class StoreOperationalActivityController {
         }
     }
 
-    @ApiOperation("我的报名列表")
+    @ApiOperation("我的报名列表(用户端)")
     @ApiOperationSupport(order = 10)
     @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
     @GetMapping("/signup/my")
@@ -273,7 +273,7 @@ public class StoreOperationalActivityController {
         }
     }
 
-    @ApiOperation("删除报名及成果")
+    @ApiOperation("删除报名及成果(用户端)")
     @ApiOperationSupport(order = 11)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true),

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

@@ -165,7 +165,7 @@ public class StoreOperationalStatisticsController {
             )
     })
     @GetMapping("/getStatisticsComparisonNew")
-    public R<StoreOperationalStatisticsComparisonVo> getStatisticsComparisonNew(
+    public R<Void> getStatisticsComparisonNew(
             @RequestParam("storeId") Integer storeId,
             @RequestParam("currentStartTime") String currentStartTime,
             @RequestParam("currentEndTime") String currentEndTime,
@@ -173,12 +173,12 @@ public class StoreOperationalStatisticsController {
             @RequestParam("previousEndTime") String previousEndTime) {
         log.info("StoreOperationalStatisticsController.getStatisticsComparisonNew - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}",
                 storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
-        StoreOperationalStatisticsComparisonVo comparison = storeOperationalStatisticsService.getStatisticsComparisonNew(
+        boolean success = storeOperationalStatisticsService.getStatisticsComparisonNew(
                 storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
-        if (comparison == null) {
+        if (!success) {
             return R.fail("暂无对比数据");
         }
-        return R.data(comparison);
+        return R.success("操作成功");
     }
 
     @ApiOperation("查询历史统计记录列表(分页)")

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

@@ -196,7 +196,7 @@ public class LifeCommentService {
                 return commonRatingMapper.update(null, new UpdateWrapper<CommonRating>()
                         .setSql("like_count = like_count + 1")
                         .eq("id", huifuId));
-            } else if (CommonConstant.COMMENT_LIKE.equals(type) || CommonConstant.DYNAMIC_LIKE.equals(type) || CommonConstant.CLOCK_IN_LIKE.equals(type)) {
+            } else if (CommonConstant.COMMENT_LIKE.equals(type) || CommonConstant.DYNAMIC_LIKE.equals(type) || CommonConstant.CLOCK_IN_LIKE.equals(type) || CommonConstant.SECOND_HAND_LIKE.equals(type)) {
                 // 12-评论点赞:更新评论表点赞数
                 return commonCommentMapper.update(null, new UpdateWrapper<CommonComment>()
                         .setSql("like_count = like_count + 1")
@@ -364,7 +364,7 @@ public class LifeCommentService {
                 return commonRatingMapper.update(null, new UpdateWrapper<CommonRating>()
                         .setSql("like_count = like_count - 1")
                         .eq("id", huifuId));
-            } else if (CommonConstant.COMMENT_LIKE.equals(type) || CommonConstant.DYNAMIC_LIKE.equals(type) || CommonConstant.CLOCK_IN_LIKE.equals(type)) {
+            } else if (CommonConstant.COMMENT_LIKE.equals(type) || CommonConstant.DYNAMIC_LIKE.equals(type) || CommonConstant.CLOCK_IN_LIKE.equals(type) || CommonConstant.SECOND_HAND_LIKE.equals(type)) {
                 // 12-评论点赞:更新评论表点赞数
                 return commonCommentMapper.update(null, new UpdateWrapper<CommonComment>()
                         .setSql("like_count = like_count - 1")

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

@@ -53,30 +53,33 @@ public interface LifeDiscountCouponService extends IService<LifeDiscountCoupon>
 
     /**
      * 获取该用户该店铺优惠券列表
+     * @param couponType 优惠券类型:1=满减券,2=折扣券,null=全部优惠券
      */
-    List<LifeDiscountCouponVo> getStoreUserCouponList(String storeId, UserLoginInfo userLoginInfo);
+    List<LifeDiscountCouponVo> getStoreUserCouponList(String storeId, UserLoginInfo userLoginInfo, Integer couponType);
 
     /**
-     * 获取该用户该店铺优惠券列表
+     * 获取该用户该店铺优惠券已领可用不可用列表
+     * @param couponType 优惠券类型:1=满减券,2=折扣券,null=全部优惠券
      */
-    Map getStoreUserUsableCouponList(String storeId, UserLoginInfo userLoginInfo, BigDecimal amount);
+    Map getStoreUserUsableCouponList(String storeId, UserLoginInfo userLoginInfo, BigDecimal amount, Integer couponType);
 
     /**
      * 获取该用户优惠券列表
      * @param type 不传:优惠券+代金券都返回;1:仅优惠券(查 life_discount_coupon);4:仅代金券(查 life_coupon)
+     * @param couponType 优惠券类型:1=满减券,2=折扣券,null=全部优惠券(仅当type不为4时有效)
      */
-    List<LifeDiscountCouponVo> getUserCouponList(UserLoginInfo userLoginInfo, int page, int size, String tabType, Integer type);
+    List<LifeDiscountCouponVo> getUserCouponList(UserLoginInfo userLoginInfo, int page, int size, String tabType, Integer type, Integer couponType);
 
     /**
      * 获取所有优惠券列表(分页)
      */
-    IPage<LifeDiscountCouponVo> getStoreAllCouponList(String storeId, UserLoginInfo userLoginInfo, int page, int size, String couponName, String tab, int couponsFromType, int couponStatus);
+    IPage<LifeDiscountCouponVo> getStoreAllCouponList(String storeId, UserLoginInfo userLoginInfo, int page, int size, String couponName, String tab, int couponsFromType, int couponStatus, Integer couponType);
 
     /**
      * 获取所有优惠券列表(不分页)
      * @param type 类型:1-优惠券(默认),4-代金券
      */
-    List<LifeDiscountCouponVo> getStoreAllCouponListPaginateNot(String status, String storeId, UserLoginInfo userLoginInfo, int couponStatus, Integer type);
+    List<LifeDiscountCouponVo> getStoreAllCouponListPaginateNot(String status, String storeId, UserLoginInfo userLoginInfo, int couponStatus, Integer type, Integer couponType);
 
     /**
      * 获取优惠券规则

+ 15 - 3
alien-store/src/main/java/shop/alien/store/service/LifeDiscountCouponStoreFriendService.java

@@ -56,14 +56,26 @@ public interface LifeDiscountCouponStoreFriendService extends IService<LifeDisco
     /**
      * 查询收到的赠券列表。type=4 返回代金券(life_coupon),否则返回优惠券(life_discount_coupon)
      */
-    List<LifeDiscountCouponFriendRuleDetailVo> getReceivedFriendCouponList(String storeId, String friendStoreUserId, Integer type);
+    /**
+     * 获取收到的好友优惠券列表
+     * @param couponType 优惠券类型:1=满减券,2=折扣券,null=全部优惠券(仅当type不为4时有效)
+     */
+    List<LifeDiscountCouponFriendRuleDetailVo> getReceivedFriendCouponList(String storeId, String friendStoreUserId, Integer type, Integer couponType);
 
     List<LifeDiscountCouponFriendRuleVo> getRuleList(String storeId, String acName, String status);
 
     LifeDiscountCouponFriendRuleVo getRuleById(String id);
 
-    /** type=4 仅代金券,不传则返回全部(优惠券+代金券) */
-    List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(String storeUserId, String friendStoreUserId, String storeName, Integer type);
+    /**
+     * 查询赠券记录(商户送给商户券)
+     * @param storeUserId 当前登录店铺用户id(必填)
+     * @param queryType 查询类型:1=我收到的(所有好友赠送给我的),2=我送出的(我送给所有好友的)(必填)
+     * @param storeName 店铺名称模糊查询(可选)
+     * @param type 4=仅代金券,不传=全部(优惠券+代金券)(可选)
+     * @param couponType 优惠券类型:1=仅满减券,2=仅折扣券,不传=全部优惠券(可选,仅当type不为4时有效)
+     * @return 赠券列表
+     */
+    List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(String storeUserId, Integer queryType, String storeName, Integer type, Integer couponType);
 
     /**
      * 好评送券:用户好评且AI审核通过后,按运营活动配置的优惠券/代金券ID发放到用户券包,发放成功后扣减库存;按券类型发送对应通知。

+ 201 - 107
alien-store/src/main/java/shop/alien/store/service/LifeUserDynamicsService.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
@@ -15,15 +16,18 @@ import org.springframework.util.StringUtils;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.vo.*;
 import shop.alien.mapper.*;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import shop.alien.util.common.constant.CommentSourceTypeEnum;
 
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
  * 用户动态
  */
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper, LifeUserDynamics> {
@@ -55,6 +59,8 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
     private final CommonCommentMapper commonCommentMapper;
 
     private final CommonRatingService commonRatingService;
+    
+    private final CommonRatingMapper commonRatingMapper;
 
     public int addLiulanCount(String id) {
         LambdaUpdateWrapper<LifeUserDynamics> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
@@ -159,60 +165,63 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
         // 查询动态数据并按类型过滤
         List<LifeUserDynamicsVo> lifeUserDynamicsVoList = lifeUserDynamicsMapper.getLifeUserDynamicsList();
 
-        // 店铺id
-        // TODO 可以优化小驴:优化方案直接sql分组查询评价数量,避免循环查询
+        // 店铺id - 优化:批量查询评价数量,避免N+1查询
         List<String> storeUserIdList = new ArrayList<>();
-        Map<String, Integer> commitCountMap = new HashMap<>();
         //对lifeUserDynamicsVoList数据进行处理,当type为2的时候,把userName的数值赋值到storeName
         lifeUserDynamicsVoList.forEach(item -> {
             if (item.getType().equals("2")) {
                 item.setStoreName(item.getUserName());
-                storeUserIdList.add(item.getStoreUserId());
+                if (item.getStoreUserId() != null) {
+                    storeUserIdList.add(item.getStoreUserId());
+                }
             }
         });
 
-        for (String storeId : storeUserIdList) {
-            Integer totalCount = 0;
-            double storeScore;
-            Object ratingObj =  commonRatingService.getRatingCount(Integer.parseInt(storeId), 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;
-                }
-            }
-            commitCountMap.put(storeId, totalCount);
-        }
+        // 批量查询评价数量(优化:从N+1查询改为批量查询)
+        Map<String, Integer> commitCountMap = batchGetRatingCount(storeUserIdList);
         if (!StringUtils.isEmpty(type)) {
             lifeUserDynamicsVoList = lifeUserDynamicsVoList.stream().filter(item -> type.equals(item.getType())).collect(Collectors.toList());
         }
 
-        // 查询我的关注信息,构建关注者ID列表
-        LambdaQueryWrapper<LifeFans> lifeFansWrapper = new LambdaQueryWrapper<>();
-        lifeFansWrapper.eq(LifeFans::getFansId, phoneId);
-        List<LifeFans> lifeFansList = lifeFansMapper.selectList(lifeFansWrapper);
-        List<String> followList = lifeFansList.stream().map(LifeFans::getFollowedId).collect(Collectors.toList());
+        // 并发查询关注、粉丝、点赞信息(优化:并行执行多个独立查询)
+        CompletableFuture<List<String>> followListFuture = CompletableFuture.supplyAsync(() -> {
+            if (StringUtils.isEmpty(phoneId)) {
+                return Collections.emptyList();
+            }
+            LambdaQueryWrapper<LifeFans> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LifeFans::getFansId, phoneId);
+            return lifeFansMapper.selectList(wrapper).stream()
+                    .map(LifeFans::getFollowedId)
+                    .collect(Collectors.toList());
+        });
 
-        // 查询我的粉丝信息,构建粉丝ID列表
-        lifeFansWrapper = new LambdaQueryWrapper<>();
-        lifeFansWrapper.eq(LifeFans::getFollowedId, phoneId);
-        lifeFansList = lifeFansMapper.selectList(lifeFansWrapper);
-        List<String> fansList = lifeFansList.stream().map(LifeFans::getFansId).collect(Collectors.toList());
+        CompletableFuture<List<String>> fansListFuture = CompletableFuture.supplyAsync(() -> {
+            if (StringUtils.isEmpty(phoneId)) {
+                return Collections.emptyList();
+            }
+            LambdaQueryWrapper<LifeFans> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LifeFans::getFollowedId, phoneId);
+            return lifeFansMapper.selectList(wrapper).stream()
+                    .map(LifeFans::getFansId)
+                    .collect(Collectors.toList());
+        });
 
-        // 查询我的点赞记录,构建点赞ID列表
-        LambdaQueryWrapper<LifeLikeRecord> likeWrapper = new LambdaQueryWrapper<>();
-        likeWrapper.eq(LifeLikeRecord::getDianzanId, phoneId);
-        likeWrapper.eq(LifeLikeRecord::getType, "2");
-        List<LifeLikeRecord> lifeLikeList = lifeLikeRecordMapper.selectList(likeWrapper);
-        List<String> likeList = lifeLikeList.stream().map(LifeLikeRecord::getHuifuId).collect(Collectors.toList());
+        CompletableFuture<List<String>> likeListFuture = CompletableFuture.supplyAsync(() -> {
+            if (StringUtils.isEmpty(phoneId)) {
+                return Collections.emptyList();
+            }
+            LambdaQueryWrapper<LifeLikeRecord> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LifeLikeRecord::getDianzanId, phoneId)
+                    .eq(LifeLikeRecord::getType, "2");
+            return lifeLikeRecordMapper.selectList(wrapper).stream()
+                    .map(LifeLikeRecord::getHuifuId)
+                    .collect(Collectors.toList());
+        });
+
+        // 等待所有查询完成
+        List<String> followList = followListFuture.join();
+        List<String> fansList = fansListFuture.join();
+        List<String> likeList = likeListFuture.join();
 
         // 根据myself参数过滤动态:自己或非本人动态
         if ("1".equals(myself)) {
@@ -226,10 +235,15 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
             lifeUserDynamicsVoList = lifeUserDynamicsVoList.stream().filter(item -> followList.contains(item.getPhoneId())).collect(Collectors.toList());
         }
 
-//        List<StoreCommentVo> rootCommitCount = storeCommentMapper.getRootCommitCount(2, null);
-//        List<StoreCommentVo> sonCommitCount = storeCommentMapper.getSonCommitCount(2, null);
-
-        List<CommonCommentVo> commonCommentVo= commonCommentMapper.getCommentCount(CommentSourceTypeEnum.DYNAMIC_COMMENT.getType());
+        // 批量查询评论数量(优化:一次性查询所有动态的评论数)
+        List<CommonCommentVo> commonCommentVo = commonCommentMapper.getCommentCount(CommentSourceTypeEnum.DYNAMIC_COMMENT.getType());
+        // 转换为Map,提升查找性能
+        Map<Long, Integer> commentCountMap = commonCommentVo.stream()
+                .collect(Collectors.toMap(
+                        vo -> Long.parseLong(vo.getSourceId().toString()),
+                        CommonCommentVo::getCommentCount,
+                        (v1, v2) -> v1 + v2 // 如果有重复key,累加
+                ));
 
         // 设置动态对象的状态信息:是否关注对方、是否被关注、是否点赞及评论数量
         // 设置.imagePath。视频为mp4+jpg格式,图片为jpg/png格式。
@@ -255,23 +269,9 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
             } else {
                 vo.setIsLike("0");
             }
-            List<CommonCommentVo> collect = commonCommentVo.stream().filter(x -> x.getSourceId().equals(Long.parseLong(vo.getId().toString()))).collect(Collectors.toList());
-//            List<StoreCommentVo> rootList = rootCommitCount.stream().filter(item -> Objects.equals(item.getBusinessId(), vo.getId())).collect(Collectors.toList());
-            if (collect.isEmpty()) {
-                vo.setCommentCount(0);
-            } else {
-                Integer count = 0;
-                for (CommonCommentVo commentVo : collect) {
-                    count += commentVo.getCommentCount();
-                }
-//                for (CommonCommentVo storeCommentVo : collect) {
-//                    List<StoreCommentVo> sonList = sonCommitCount.stream().filter(item -> Objects.equals(item.getReplyId(), storeCommentVo.getId())).collect(Collectors.toList());
-//                    if (!sonList.isEmpty()) {
-//                        count += sonList.get(0).getCommitCount();
-//                    }
-//                }
-                vo.setCommentCount(count);
-            }
+            // 优化:直接从Map中获取评论数量,避免stream过滤
+            Integer commentCount = commentCountMap.getOrDefault(Long.parseLong(vo.getId().toString()), 0);
+            vo.setCommentCount(commentCount);
         }
 
         // 如果phoneId不为空,处理黑名单逻辑,过滤掉被拉黑用户的动态
@@ -326,69 +326,163 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
         return lifeUserDynamicsVoList;
     }
 
+    /**
+     * 批量查询评价数量(优化:从N+1查询改为批量查询)
+     * 
+     * @param storeUserIdList 店铺ID列表
+     * @return 店铺ID -> 评价数量的映射
+     */
+    private Map<String, Integer> batchGetRatingCount(List<String> storeUserIdList) {
+        Map<String, Integer> commitCountMap = new HashMap<>();
+        if (storeUserIdList == null || storeUserIdList.isEmpty()) {
+            return commitCountMap;
+        }
+
+        try {
+            // 转换为Integer列表
+            List<Integer> storeIdList = storeUserIdList.stream()
+                    .filter(id -> id != null && !id.isEmpty())
+                    .map(id -> {
+                        try {
+                            return Integer.parseInt(id);
+                        } catch (NumberFormatException e) {
+                            return null;
+                        }
+                    })
+                    .filter(Objects::nonNull)
+                    .distinct()
+                    .collect(Collectors.toList());
+
+            if (storeIdList.isEmpty()) {
+                return commitCountMap;
+            }
+
+            // 批量查询评价记录(只查询审核通过且展示的)
+            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()
+                    ));
+
+            // 转换为String -> Integer映射
+            for (String storeIdStr : storeUserIdList) {
+                try {
+                    Integer storeId = Integer.parseInt(storeIdStr);
+                    Long count = ratingCountMap.get(storeId);
+                    commitCountMap.put(storeIdStr, count != null ? count.intValue() : 0);
+                } catch (NumberFormatException e) {
+                    commitCountMap.put(storeIdStr, 0);
+                }
+            }
+        } catch (Exception e) {
+            log.error("批量获取评价数量失败", e);
+            // 如果批量查询失败,设置默认值
+            storeUserIdList.forEach(storeId -> commitCountMap.put(storeId, 0));
+        }
+
+        return commitCountMap;
+    }
+
     public List<LifeUserDynamicsVo> getStoreUserDynamics(String storePhone, String userType, String loginPhone) {
-            LambdaQueryWrapper<LifeUserDynamics> lambdaQueryWrapper = new LambdaQueryWrapper<>();
-            lambdaQueryWrapper.eq(LifeUserDynamics :: getPhoneId, storePhone);
-            List<LifeUserDynamics> lifeUserDynamics = lifeUserDynamicsMapper.selectList(lambdaQueryWrapper);
+            // 动态列表与点赞记录并行查询
+            CompletableFuture<List<LifeUserDynamics>> dynamicsFuture = CompletableFuture.supplyAsync(() -> {
+                LambdaQueryWrapper<LifeUserDynamics> lambdaQueryWrapper = new LambdaQueryWrapper<>();
+                lambdaQueryWrapper.eq(LifeUserDynamics::getPhoneId, storePhone);
+                return lifeUserDynamicsMapper.selectList(lambdaQueryWrapper);
+            });
+            CompletableFuture<Set<String>> likeSetFuture = CompletableFuture.supplyAsync(() -> {
+                LambdaQueryWrapper<LifeLikeRecord> likeWrapper = new LambdaQueryWrapper<>();
+                likeWrapper.eq(LifeLikeRecord::getType, "2");
+                if (userType.equals("1")) {
+                    likeWrapper.eq(LifeLikeRecord::getDianzanId, "user_" + loginPhone);
+                } else {
+                    likeWrapper.eq(LifeLikeRecord::getDianzanId, "store_" + loginPhone);
+                }
+                List<LifeLikeRecord> lifeLikeList = lifeLikeRecordMapper.selectList(likeWrapper);
+                return lifeLikeList.stream().map(LifeLikeRecord::getHuifuId).collect(Collectors.toSet());
+            });
+
+            List<LifeUserDynamics> lifeUserDynamics = dynamicsFuture.join();
+            Set<String> likeSet = likeSetFuture.join();
+
             List<LifeUserDynamicsVo> lifeUserDynamicsVos = lifeUserDynamics.stream()
                 .map(dynamics -> {
                     LifeUserDynamicsVo vo = new LifeUserDynamicsVo();
                     BeanUtils.copyProperties(dynamics, vo);
-                    // 特殊字段处理
                     return vo;
                 })
                 .collect(Collectors.toList());
 
-            // 查询我的点赞记录,构建点赞ID列表
-            LambdaQueryWrapper<LifeLikeRecord> likeWrapper = new LambdaQueryWrapper<>();
-            likeWrapper.eq(LifeLikeRecord::getType, "2");
-            if(userType.equals("1")){
-                likeWrapper.eq(LifeLikeRecord::getDianzanId, "user_"+loginPhone);
-            }else{
-                likeWrapper.eq(LifeLikeRecord::getDianzanId, "store_"+loginPhone);
-            }
-            List<LifeLikeRecord> lifeLikeList = lifeLikeRecordMapper.selectList(likeWrapper);
-            List<String> likeList = lifeLikeList.stream().map(LifeLikeRecord::getHuifuId).collect(Collectors.toList());
-         for (LifeUserDynamicsVo vo : lifeUserDynamicsVos) {
-            if (likeList.contains(String.valueOf(vo.getId()))) {
-                vo.setIsLike("1");
-            } else {
-                vo.setIsLike("0");
-            }
-            
-            // 查询用户名称和头像
-            String phoneId = vo.getPhoneId();
-            if (StringUtils.hasText(phoneId)) {
-                if (phoneId.startsWith("user_")) {
-                    // 用户类型
-                    String userPhone = phoneId.substring(5);
-                    LifeUser lifeUser = lifeUserService.getUserByPhone(userPhone);
-                    if (lifeUser != null) {
-                        vo.setUserName(lifeUser.getUserName());
-                        vo.setUserImage(lifeUser.getUserImage());
+            // 收集所有 phoneId,批量查询用户/商户信息,消除 N+1
+            Set<String> userPhones = new HashSet<>();
+            Set<String> storePhones = new HashSet<>();
+            for (LifeUserDynamicsVo vo : lifeUserDynamicsVos) {
+                String phoneId = vo.getPhoneId();
+                if (StringUtils.hasText(phoneId)) {
+                    if (phoneId.startsWith("user_")) {
+                        userPhones.add(phoneId.substring(5));
+                    } else if (phoneId.startsWith("store_")) {
+                        storePhones.add(phoneId.substring(6));
                     }
-                } else if (phoneId.startsWith("store_")) {
-                    // 商户类型
-                    String storePhoneNum = phoneId.substring(6);
-                    StoreUserVo storeUser = storeUserService.getUserByPhone(storePhoneNum);
-                    if (storeUser != null) {
-                        // 查询店铺信息获取店铺名称
-                        if (storeUser.getStoreId() != null) {
-                            StoreInfo storeInfo = storeInfoMapper.selectById(storeUser.getStoreId());
-                            if (storeInfo != null && StringUtils.hasText(storeInfo.getStoreName())) {
-                                vo.setUserName(storeInfo.getStoreName());
+                }
+            }
+
+            // 批量查询 LifeUser、StoreUser
+            Map<String, LifeUser> userByPhoneMap = CollectionUtils.isEmpty(userPhones) ? Collections.emptyMap()
+                    : lifeUserMapper.selectList(new LambdaQueryWrapper<LifeUser>().in(LifeUser::getUserPhone, userPhones))
+                            .stream().collect(Collectors.toMap(LifeUser::getUserPhone, u -> u, (a, b) -> a));
+            List<StoreUser> storeUserList = CollectionUtils.isEmpty(storePhones) ? Collections.emptyList()
+                    : storeUserService.list(new LambdaQueryWrapper<StoreUser>().in(StoreUser::getPhone, storePhones));
+            Map<String, StoreUser> storeUserByPhoneMap = storeUserList.stream().collect(Collectors.toMap(StoreUser::getPhone, u -> u, (a, b) -> a));
+
+            // 批量查询店铺信息
+            List<Integer> storeIds = storeUserList.stream().map(StoreUser::getStoreId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+            Map<Integer, StoreInfo> storeInfoMap = CollectionUtils.isEmpty(storeIds) ? Collections.emptyMap()
+                    : storeInfoMapper.selectBatchIds(storeIds).stream().collect(Collectors.toMap(StoreInfo::getId, s -> s, (a, b) -> a));
+
+            for (LifeUserDynamicsVo vo : lifeUserDynamicsVos) {
+                if (likeSet.contains(String.valueOf(vo.getId()))) {
+                    vo.setIsLike("1");
+                } else {
+                    vo.setIsLike("0");
+                }
+
+                String phoneId = vo.getPhoneId();
+                if (StringUtils.hasText(phoneId)) {
+                    if (phoneId.startsWith("user_")) {
+                        String userPhone = phoneId.substring(5);
+                        LifeUser lifeUser = userByPhoneMap.get(userPhone);
+                        if (lifeUser != null) {
+                            vo.setUserName(lifeUser.getUserName());
+                            vo.setUserImage(lifeUser.getUserImage());
+                        }
+                    } else if (phoneId.startsWith("store_")) {
+                        String storePhoneNum = phoneId.substring(6);
+                        StoreUser storeUser = storeUserByPhoneMap.get(storePhoneNum);
+                        if (storeUser != null) {
+                            if (storeUser.getStoreId() != null) {
+                                StoreInfo storeInfo = storeInfoMap.get(storeUser.getStoreId());
+                                if (storeInfo != null && StringUtils.hasText(storeInfo.getStoreName())) {
+                                    vo.setUserName(storeInfo.getStoreName());
+                                } else {
+                                    vo.setUserName(storeUser.getNickName());
+                                }
                             } else {
                                 vo.setUserName(storeUser.getNickName());
                             }
-                        } else {
-                            vo.setUserName(storeUser.getNickName());
+                            vo.setUserImage(storeUser.getHeadImg());
                         }
-                        // 设置商户头像
-                        vo.setUserImage(storeUser.getHeadImg());
                     }
                 }
             }
-        }
             return lifeUserDynamicsVos;
     }
 

+ 36 - 2
alien-store/src/main/java/shop/alien/store/service/LifeUserOrderService.java

@@ -803,8 +803,42 @@ public class LifeUserOrderService extends ServiceImpl<LifeUserOrderMapper, LifeU
             lifeDiscountCouponUser.setUseTime(date);
             lifeDiscountCouponUserMapper.updateById(lifeDiscountCouponUser);
             lifeDiscountCoupon = lifeDiscountCouponMapper.selectById(lifeDiscountCouponUser.getCouponId());
-            avgDiscountCouponPrice = lifeDiscountCoupon.getNominalValue().divide(countDecimal, 2, RoundingMode.DOWN);
-            avgDiscountCouponLastPrice = lifeDiscountCoupon.getNominalValue().subtract(avgDiscountCouponPrice.multiply(countDecimal.subtract(BigDecimal.ONE)));
+            
+            // 根据优惠券类型计算优惠金额
+            Integer couponType = lifeDiscountCoupon.getCouponType();
+            if (couponType == null || couponType == 1) {
+                // 满减券(默认类型,兼容旧数据):直接使用面值作为减免金额
+                BigDecimal nominalValue = lifeDiscountCoupon.getNominalValue();
+                if (nominalValue == null) {
+                    nominalValue = BigDecimal.ZERO;
+                }
+                avgDiscountCouponPrice = nominalValue.divide(countDecimal, 2, RoundingMode.DOWN);
+                avgDiscountCouponLastPrice = nominalValue.subtract(avgDiscountCouponPrice.multiply(countDecimal.subtract(BigDecimal.ONE)));
+            } else if (couponType == 2) {
+                // 折扣券:根据订单金额和折扣率计算优惠金额
+                // 注意:totalPrice 是使用优惠券后的价格,需要反推原始价格
+                // 如果折扣率为 discountRate(例如80表示8折),则:
+                // 原始价格 = 优惠后价格 / (discountRate / 100)
+                // 优惠金额 = 原始价格 - 优惠后价格
+                BigDecimal discountRate = lifeDiscountCoupon.getDiscountRate();
+                if (discountRate == null || discountRate.compareTo(BigDecimal.ZERO) <= 0 || discountRate.compareTo(new BigDecimal(100)) > 0) {
+                    // 折扣率无效,使用0作为优惠金额
+                    avgDiscountCouponPrice = BigDecimal.ZERO;
+                    avgDiscountCouponLastPrice = BigDecimal.ZERO;
+                } else {
+                    // 计算原始订单总金额(使用优惠券前)
+                    BigDecimal originalTotalPrice = totalPrice.divide(discountRate.divide(new BigDecimal(100), 4, RoundingMode.HALF_UP), 2, RoundingMode.HALF_UP);
+                    // 计算总优惠金额
+                    BigDecimal totalDiscountAmount = originalTotalPrice.subtract(totalPrice);
+                    // 平均分配到每个券上
+                    avgDiscountCouponPrice = totalDiscountAmount.divide(countDecimal, 2, RoundingMode.DOWN);
+                    avgDiscountCouponLastPrice = totalDiscountAmount.subtract(avgDiscountCouponPrice.multiply(countDecimal.subtract(BigDecimal.ONE)));
+                }
+            } else {
+                // 未知类型,使用0作为优惠金额
+                avgDiscountCouponPrice = BigDecimal.ZERO;
+                avgDiscountCouponLastPrice = BigDecimal.ZERO;
+            }
         }
 
         for (int i = 0; i < buyCount; i++) {

+ 1 - 0
alien-store/src/main/java/shop/alien/store/service/LifeUserService.java

@@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import shop.alien.config.properties.RiskControlProperties;
+import shop.alien.entity.result.R;
 import shop.alien.entity.second.LifeUserLog;
 import shop.alien.entity.second.SecondRiskControlRecord;
 import shop.alien.entity.store.LifeFans;

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

@@ -40,17 +40,17 @@ public interface StoreOperationalStatisticsService {
                                                                     String previousStartTime, String previousEndTime);
 
     /**
-     * 获取商家经营统计数据对比(新接口)
+     * 获取商家经营统计数据对比(新接口),仅返回是否成功,不返回对比数据
      *
      * @param storeId           店铺ID
      * @param currentStartTime  当期开始时间(格式:yyyy-MM-dd)
      * @param currentEndTime    当期结束时间(格式:yyyy-MM-dd)
      * @param previousStartTime 上期开始时间(格式:yyyy-MM-dd)
      * @param previousEndTime   上期结束时间(格式:yyyy-MM-dd)
-     * @return 经营统计数据对比
+     * @return true-成功,false-暂无对比数据(失败)
      */
-    StoreOperationalStatisticsComparisonVo getStatisticsComparisonNew(Integer storeId, String currentStartTime, String currentEndTime,
-                                                                      String previousStartTime, String previousEndTime);
+    boolean getStatisticsComparisonNew(Integer storeId, String currentStartTime, String currentEndTime,
+                                      String previousStartTime, String previousEndTime);
 
     /**
      * 查询历史统计记录列表(分页)

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

@@ -88,6 +88,8 @@ public class CommonCommentServiceImpl extends ServiceImpl<CommonCommentMapper, C
             likeType = CommonConstant.DYNAMIC_LIKE;
         } else if (sourceType == CommentSourceTypeEnum.CLOCK_IN_COMMENT.getType()){
             likeType = CommonConstant.CLOCK_IN_LIKE;
+        } else if (sourceType == CommentSourceTypeEnum.SECOND_HAND_COMMENT.getType()){
+            likeType =  CommonConstant.SECOND_HAND_LIKE;
         }
         List<CommonCommentVo> firstLevelComment = getFirstLevelComment(sourceType, sourceId, pageNum, pageSize, userId,likeType);
         CommonRatingVo commonRatingVo = new CommonRatingVo();

+ 210 - 32
alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java

@@ -27,6 +27,7 @@ import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.entity.storePlatform.StoreOperationalActivity;
 import shop.alien.mapper.*;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+import shop.alien.util.common.Constants;
 import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.service.CommonCommentService;
 import shop.alien.store.service.CommonRatingService;
@@ -34,6 +35,7 @@ import shop.alien.store.service.LifeDiscountCouponStoreFriendService;
 import shop.alien.store.util.CommonConstant;
 import shop.alien.store.util.ai.AiContentModerationUtil;
 import shop.alien.store.util.ai.AiVideoModerationUtil;
+import shop.alien.store.util.ai.ReceiptAuditUtil;
 import shop.alien.util.common.DateUtils;
 import shop.alien.util.common.constant.CommentSourceTypeEnum;
 import shop.alien.util.common.constant.RatingBusinessTypeEnum;
@@ -84,6 +86,8 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
     private final AiVideoModerationUtil aiVideoModerationUtil;
     private final LifeDiscountCouponStoreFriendService lifeDiscountCouponStoreFriendService;
     private final StoreOperationalActivityMapper storeOperationalActivityMapper;
+    private final ReceiptAuditUtil receiptAuditUtil;
+    private final StoreClockInMapper storeClockInMapper;
 
     public static final List<String> SERVICES_LIST = ImmutableList.of(
             TextReviewServiceEnum.COMMENT_DETECTION_PRO.getService(),
@@ -125,8 +129,7 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                 commonRating.setScoreThree(parse.getDouble("scoreThree"));
             }
             
-            // 2. 先进行文本和图片审核(同步审核,必须通过才能保存)
-            // 处理imageUrls可能为null的情况
+            // 2. 处理图片URL列表
             String imageUrlsStr = commonRating.getImageUrls();
             List<String> imageUrlList = new ArrayList<>();
             if (StringUtils.isNotEmpty(imageUrlsStr)) {
@@ -135,18 +138,119 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
             
             // 一次遍历完成分类,避免多次流式处理
             Map<String, List<String>> urlCategoryMap = StoreRenovationRequirementServiceImpl.classifyUrls(imageUrlList);
+            List<String> imageUrls = urlCategoryMap.get("image");
+            List<String> videoUrls = urlCategoryMap.get("video");
+            
+            // 3. 内容审核(基础审核,必须通过)
+            // 文本、图片、视频必须符合法律法规要求
+            AiContentModerationUtil.AuditResult contentAuditResult = new AiContentModerationUtil.AuditResult(true, "");
+            // 只要有内容或图片,就必须进行内容审核
+            if (StringUtils.isNotEmpty(commonRating.getContent()) || !imageUrls.isEmpty()) {
+                contentAuditResult = aiContentModerationUtil.auditContent(commonRating.getContent(), imageUrls);
+            }
             
-            // 3. 文本和图片审核(同步,必须通过)
-            AiContentModerationUtil.AuditResult auditResult = new AiContentModerationUtil.AuditResult(true, "");
-            // 只要有内容或图片,就必须审核
-            if (StringUtils.isNotEmpty(commonRating.getContent()) || !urlCategoryMap.get("image").isEmpty()) {
-                auditResult = aiContentModerationUtil.auditContent(commonRating.getContent(), urlCategoryMap.get("image"));
+            // 内容审核不通过,直接判定为审核不通过
+            if (!contentAuditResult.isPassed()) {
+                log.warn("评论审核失败:内容审核不通过,不保存评论。原因:{}", contentAuditResult.getFailureReason());
+                return 2; // 返回2表示内容审核不通过
             }
             
-            // 4. 文本/图片审核不通过,直接返回,不保存
-            if (!auditResult.isPassed()) {
-                log.warn("评价审核不通过,不保存评论。原因:{}", auditResult.getFailureReason());
-                return 2; // 返回2表示文本内容异常
+            log.info("评论审核:内容审核通过,继续检查其他条件");
+            
+            // 4. 内容审核通过后,检查是否满足以下条件之一(满足任意一个即可):
+            // 条件1:当前用户必须在这个店铺打过卡
+            // 条件2:当前用户上传的图片中包含该店铺的小票(调用AI审核)
+            // 注意:小票审核和打卡审核不通过时,允许创建评论,但审核状态设为不通过并记录审核原因
+            
+            boolean condition1Passed = false; // 条件1:打过卡
+            boolean condition2Passed = false; // 条件2:有小票
+            String auditReason = null; // 审核原因
+            
+            // 检查条件1:用户是否在该店铺打过卡
+            try {
+                LambdaQueryWrapper<StoreClockIn> clockInWrapper = new LambdaQueryWrapper<>();
+                clockInWrapper.eq(StoreClockIn::getUserId, commonRating.getUserId())
+                        .eq(StoreClockIn::getStoreId, commonRating.getBusinessId())
+                        .eq(StoreClockIn::getDeleteFlag, 0)
+                        .last("LIMIT 1");
+                Integer clockInCount = storeClockInMapper.selectCount(clockInWrapper);
+                condition1Passed = clockInCount != null && clockInCount > 0;
+                if (condition1Passed) {
+                    log.info("评论审核条件1通过:用户在该店铺打过卡,userId={}, storeId={}", 
+                            commonRating.getUserId(), commonRating.getBusinessId());
+                } else {
+                    // 打卡审核不通过,记录审核原因
+                    auditReason = "用户未在该店铺打过卡";
+                    log.info("评论审核条件1未通过:用户未在该店铺打过卡,userId={}, storeId={}", 
+                            commonRating.getUserId(), commonRating.getBusinessId());
+                }
+            } catch (Exception e) {
+                log.warn("检查用户打卡记录失败:userId={}, storeId={}", 
+                        commonRating.getUserId(), commonRating.getBusinessId(), e);
+                // 检查异常时,也记录为未通过
+                if (auditReason == null) {
+                    auditReason = "检查用户打卡记录失败";
+                }
+            }
+            
+            // 检查条件2:图片中包含该店铺的小票(如果有图片)
+            // 注意:无论打卡是否通过,只要有图片就进行小票审核
+            if (!imageUrls.isEmpty()) {
+                try {
+                    // 获取店铺名称用于小票审核
+                    String storeName = null;
+                    StoreInfo storeInfo = storeInfoMapper.selectById(commonRating.getBusinessId());
+                    if (storeInfo != null && StringUtils.isNotEmpty(storeInfo.getStoreName())) {
+                        storeName = storeInfo.getStoreName();
+                    }
+                    
+                    // 调用小票审核接口
+                    ReceiptAuditUtil.ReceiptAuditResult receiptAuditResult = receiptAuditUtil.auditReceipt(imageUrls, storeName);
+                    condition2Passed = receiptAuditResult.isPassed();
+                    
+                    if (condition2Passed) {
+                        log.info("评论审核条件2通过:图片中包含该店铺的小票,isValidProof={}, proofType={}", 
+                                receiptAuditResult.isValidProof(), receiptAuditResult.getProofType());
+                    } else {
+                        // 小票审核不通过,记录审核原因
+                        String receiptReason = receiptAuditResult.getFailureReason();
+                        if (StringUtils.isNotEmpty(receiptReason)) {
+                            auditReason = "小票审核不通过:" + receiptReason;
+                        } else {
+                            auditReason = "小票审核不通过:上传的图片不是有效的消费凭证(小票、支付记录等)";
+                        }
+                        log.info("评论审核条件2未通过:小票审核失败,reason={}", receiptReason);
+                    }
+                } catch (Exception e) {
+                    log.warn("小票审核异常:userId={}, storeId={}", 
+                            commonRating.getUserId(), commonRating.getBusinessId(), e);
+                    // 小票审核异常时,也记录为未通过
+                    if (auditReason == null) {
+                        auditReason = "小票审核异常";
+                    }
+                    condition2Passed = false; // 异常时视为不通过
+                }
+            }
+            
+            // 判断审核结果:如果两个条件都不满足,判定为审核不通过;至少满足一个条件,算审核通过
+            if (!condition1Passed && !condition2Passed) {
+                // 两个条件都不满足,判定为审核不通过
+                if (auditReason == null) {
+                    if (imageUrls.isEmpty()) {
+                        auditReason = "不满足审核条件:用户未在该店铺打过卡";
+                    } else {
+                        auditReason = "不满足审核条件:1) 用户未在该店铺打过卡;2) 上传的图片不包含该店铺的小票";
+                    }
+                }
+                // 设置审核状态为不通过(2-驳回)
+                commonRating.setAuditStatus(2);
+                commonRating.setAuditReason(auditReason);
+                log.warn("评论审核不通过:内容审核通过,但两个条件都不满足。条件1(打过卡)={}, 条件2(有小票)={}, 审核原因={}", 
+                        condition1Passed, condition2Passed, auditReason);
+            } else {
+                // 至少满足一个条件,审核通过,审核状态保持为待审核(0),等待视频审核
+                log.info("评论审核通过:内容审核通过,且满足条件1(打过卡)={} 或 条件2(有小票)={}", 
+                        condition1Passed, condition2Passed);
             }
             
             // 5. 文本/图片审核通过,保存评论(状态为待审核,等待视频审核)
@@ -156,8 +260,54 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                 return 1;
             }
             
-            // 6. 如果有视频,进行异步视频审核
-            List<String> videoUrls = urlCategoryMap.get("video");
+            // 6. 如果审核不通过(小票审核或打卡审核不通过),发送websocket通知给用户
+            if (commonRating.getAuditStatus() != null && commonRating.getAuditStatus() == 2 && StringUtils.isNotEmpty(auditReason)) {
+                try {
+                    // 获取用户信息
+                    LifeUser lifeUser = lifeUserMapper.selectById(commonRating.getUserId());
+                    if (lifeUser != null && StringUtils.isNotEmpty(lifeUser.getUserPhone())) {
+                        String receiverId = "user_" + lifeUser.getUserPhone();
+                        
+                        // 构建通知内容
+                        JSONObject contextJson = new JSONObject();
+                        contextJson.put("ratingId", commonRating.getId());
+                        contextJson.put("storeId", commonRating.getBusinessId());
+                        contextJson.put("message", "您的评价审核未通过:" + auditReason);
+                        contextJson.put("auditReason", auditReason);
+                        
+                        // 保存通知到数据库
+                        LifeNotice lifeNotice = new LifeNotice();
+                        lifeNotice.setSenderId("system");
+                        lifeNotice.setReceiverId(receiverId);
+                        lifeNotice.setBusinessId(commonRating.getBusinessId());
+                        lifeNotice.setTitle("评价审核通知");
+                        lifeNotice.setContext(contextJson.toJSONString());
+                        lifeNotice.setNoticeType(Constants.Notice.SYSTEM_NOTICE); // 系统通知
+                        lifeNotice.setIsRead(0);
+                        lifeNoticeMapper.insert(lifeNotice);
+                        
+                        // 通过WebSocket发送实时通知
+                        WebSocketVo webSocketVo = new WebSocketVo();
+                        webSocketVo.setSenderId("system");
+                        webSocketVo.setReceiverId(receiverId);
+                        webSocketVo.setCategory("notice");
+                        webSocketVo.setNoticeType("1");
+                        webSocketVo.setIsRead(0);
+                        webSocketVo.setText(JSONObject.from(lifeNotice).toJSONString());
+                        
+                        webSocketProcess.sendMessage(receiverId, JSONObject.from(webSocketVo).toJSONString());
+                        log.info("评价审核不通过通知发送成功,ratingId={}, receiverId={}, auditReason={}", 
+                                commonRating.getId(), receiverId, auditReason);
+                    } else {
+                        log.warn("无法发送评价审核通知:用户信息不存在或手机号为空,userId={}", commonRating.getUserId());
+                    }
+                } catch (Exception e) {
+                    log.error("发送评价审核不通过通知失败,ratingId={}, error={}", 
+                            commonRating.getId(), e.getMessage(), e);
+                }
+            }
+            
+            // 7. 如果有视频,进行异步视频审核
             if (!videoUrls.isEmpty()) {
                 CompletableFuture.runAsync(() -> {
                     AiVideoModerationUtil.VideoAuditResult videoAuditResult = null;
@@ -183,24 +333,41 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                             log.info("视频审核不通过,已更新状态,ratingID:{},原因:{}",
                                     rating.getId(), videoAuditResult.getFailureReason());
                         } else if (Objects.nonNull(videoAuditResult) && videoAuditResult.isPassed()) {
-                            // 审核通过也更新状态
+                            // 视频审核通过,但需要检查之前的审核状态
+                            // 如果之前因为小票/打卡不通过而设置为2(不通过),则保持为2,不改为1
                             CommonRating rating = this.getById(commonRating.getId());
                             if (Objects.nonNull(rating)) {
-                                rating.setAuditStatus(1);
-                                this.saveOrUpdate(rating);
-                                // 对不同的businessType进行不同的处理,
-                                doBusinessWithType(commonRating);
-                                log.info("视频审核通过,已更新状态,ratingID:{}", rating.getId());
+                                // 只有当前状态为待审核(0)时,才改为通过(1)
+                                // 如果已经是2(不通过),说明之前因为小票/打卡不通过,保持为2
+                                if (rating.getAuditStatus() == null || rating.getAuditStatus() == 0) {
+                                    rating.setAuditStatus(1);
+                                    this.saveOrUpdate(rating);
+                                    // 对不同的businessType进行不同的处理,
+                                    // 使用更新后的rating对象,确保auditStatus正确
+                                    doBusinessWithType(rating);
+                                    log.info("视频审核通过,已更新状态为通过,ratingID:{}", rating.getId());
+                                } else if (rating.getAuditStatus() == 2) {
+                                    // 之前因为小票/打卡不通过,即使视频通过,也保持为不通过
+                                    log.info("视频审核通过,但之前因小票/打卡不通过,保持审核状态为不通过,ratingID:{}", rating.getId());
+                                }
                             }
                         } else {
                             // 视频审核结果为空,可能是没有视频或审核服务异常
-                            // 如果文本/图片已通过,且没有视频,则设置为审核通过
+                            // 需要检查之前的审核状态,如果之前因为小票/打卡不通过而设置为2,则保持为2
                             CommonRating rating = this.getById(commonRating.getId());
                             if (Objects.nonNull(rating)) {
-                                rating.setAuditStatus(1);
-                                this.saveOrUpdate(rating);
-                                doBusinessWithType(commonRating);
-                                log.info("无视频或视频审核结果为空,文本/图片已通过,设置为审核通过,ratingID:{}", rating.getId());
+                                // 只有当前状态为待审核(0)时,才改为通过(1)
+                                // 如果已经是2(不通过),说明之前因为小票/打卡不通过,保持为2
+                                if (rating.getAuditStatus() == null || rating.getAuditStatus() == 0) {
+                                    rating.setAuditStatus(1);
+                                    this.saveOrUpdate(rating);
+                                    // 使用更新后的rating对象,确保auditStatus正确
+                                    doBusinessWithType(rating);
+                                    log.info("无视频或视频审核结果为空,文本/图片已通过,设置为审核通过,ratingID:{}", rating.getId());
+                                } else if (rating.getAuditStatus() == 2) {
+                                    // 之前因为小票/打卡不通过,保持为不通过
+                                    log.info("无视频或视频审核结果为空,但之前因小票/打卡不通过,保持审核状态为不通过,ratingID:{}", rating.getId());
+                                }
                             }
                         }
                     } catch (Exception e) {
@@ -214,13 +381,22 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                     }
                 }, commonVideoTaskExecutor);
             } else {
-                // 7. 没有视频,文本/图片审核已通过,直接设置为审核通过
+                // 7. 没有视频,文本/图片审核已通过,但需要检查之前的审核状态
+                // 如果之前因为小票/打卡不通过而设置为2(不通过),则保持为2,不改为1
                 CommonRating rating = this.getById(commonRating.getId());
                 if (Objects.nonNull(rating)) {
-                    rating.setAuditStatus(1);
-                    this.saveOrUpdate(rating);
-                    doBusinessWithType(commonRating);
-                    log.info("无视频,文本/图片审核已通过,设置为审核通过,ratingID:{}", rating.getId());
+                    // 只有当前状态为待审核(0)时,才改为通过(1)
+                    // 如果已经是2(不通过),说明之前因为小票/打卡不通过,保持为2
+                    if (rating.getAuditStatus() == null || rating.getAuditStatus() == 0) {
+                        rating.setAuditStatus(1);
+                        this.saveOrUpdate(rating);
+                        // 使用更新后的rating对象,确保auditStatus正确
+                        doBusinessWithType(rating);
+                        log.info("无视频,文本/图片审核已通过,设置为审核通过,ratingID:{}", rating.getId());
+                    } else if (rating.getAuditStatus() == 2) {
+                        // 之前因为小票/打卡不通过,保持为不通过
+                        log.info("无视频,文本/图片审核已通过,但之前因小票/打卡不通过,保持审核状态为不通过,ratingID:{}", rating.getId());
+                    }
                 }
             }
             
@@ -282,7 +458,9 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
             }
             // 用户好评且 AI 审核通过后:按运营活动表(评论有礼)配置的 coupon_id/voucher_id 发放,用 participation_limit 限制参与次数,发放成功后扣减库存,按优惠券/代金券发对应通知
             // 支持多个活动同时生效:如果一个商家有多个正在开启的活动(如一个送优惠券,一个送代金券),都会执行
-            if (score != null && score >= 4.5 && commonRating.getUserId() != null && commonRating.getBusinessId() != null) {
+            // 重要:只有在审核通过(auditStatus == 1)的情况下才送券
+            if (score != null && score >= 4.5 && commonRating.getUserId() != null && commonRating.getBusinessId() != null 
+                    && commonRating.getAuditStatus() != null && commonRating.getAuditStatus() == 1) {
                 try {
                     Date now = new Date();
                     // 查询所有符合条件的活动(不再限制为1个)
@@ -314,9 +492,9 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                         // 检查参与次数限制(每个活动单独计算)
                         Integer limit = activity.getParticipationLimit();
                         if (limit != null && limit > 0) {
-                            // 只统计活动开始时间之后的好评,活动开始前的评论不计入参与次数
+                            // 审核成功之前的评论不计算在参与次数中(只统计审核成功时间在活动审核时间之后的评论)
                             int passedCount = commonRatingMapper.countPassedGoodRatingsByUserAndStore(
-                                    commonRating.getUserId(), businessId, activity.getStartTime());
+                                    commonRating.getUserId(), businessId, activity.getAuditTime());
                             if (passedCount > limit) {
                                 log.info("CommonRatingService 好评送券跳过:超过运营活动参与次数 activityId={}, participation_limit={}, count={}, userId={}, storeId={}, activityStartTime={}",
                                         activity.getId(), limit, passedCount, commonRating.getUserId(), businessId, activity.getStartTime());

Fichier diff supprimé car celui-ci est trop grand
+ 568 - 247
alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponServiceImpl.java


+ 315 - 49
alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponStoreFriendServiceImpl.java

@@ -29,7 +29,10 @@ import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -534,7 +537,7 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
      * @return 领取的优惠券列表
      */
     @Override
-    public List<LifeDiscountCouponFriendRuleDetailVo> getReceivedFriendCouponList(String storeId, String friendStoreUserId, Integer type) {
+    public List<LifeDiscountCouponFriendRuleDetailVo> getReceivedFriendCouponList(String storeId, String friendStoreUserId, Integer type, Integer couponType) {
         QueryWrapper<LifeDiscountCouponFriendRuleDetailVo> queryWrapper = new QueryWrapper<>();
         queryWrapper.eq("ldcsf.store_user_id", storeId);
         queryWrapper.eq("ldcsf.delete_flag", 0);
@@ -552,6 +555,10 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
         // 优惠券:保持原逻辑,只查 release_type=1
         queryWrapper.eq("ldcsf.release_type", 1);
         queryWrapper.isNotNull("ldcsf.coupon_id");
+        //如果指定了优惠券类型(满减券或折扣券),添加筛选条件
+        if (couponType != null) {
+            queryWrapper.eq("ldc.coupon_type", couponType);
+        }
         if (StringUtils.isEmpty(friendStoreUserId)) {
             queryWrapper.groupBy("ldcsf.friend_store_user_id").orderByDesc("couponNum");
         } else {
@@ -562,35 +569,59 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
 
     @Override
     public List<LifeDiscountCouponFriendRuleVo> getRuleList(String storeId, String acName, String status) {
+        // 参数校验
         if (StringUtils.isEmpty(storeId)) {
+            log.warn("getRuleList 参数错误:storeId 不能为空");
             return new ArrayList<>();
         }
+        
+        // 查询规则列表(一个规则可能关联多个优惠券,会返回多条记录)
         List<LifeDiscountCouponFriendRuleVo> ruleList = lifeDiscountCouponFriendRuleDetailMapper.getRuleList(storeId);
         if (ruleList == null) {
             ruleList = new ArrayList<>();
         }
+        
+        // 计算状态:根据结束日期判断活动是否已结束
+        // 状态:0-启用(活动进行中),1-禁用(活动已结束)
         if (ObjectUtils.isNotEmpty(ruleList)) {
             Date now = new Date();
             ruleList.forEach(i -> {
                 if (i.getEndDate() != null) {
+                    // 如果结束日期在当前时间之后,活动进行中(启用)
+                    // 如果结束日期在当前时间之前或等于,活动已结束(禁用)
                     i.setStatus(i.getEndDate().after(now) ? "0" : "1");
                 } else {
-                    i.setStatus(null);
+                    // 如果结束日期为null,视为永久有效,状态为启用
+                    i.setStatus("0");
                 }
             });
         }
+        
+        // 按规则ID分组去重:同一个规则ID只保留第一条记录(避免一个规则关联多个优惠券时重复展示)
+        Map<Integer, LifeDiscountCouponFriendRuleVo> ruleMap = new LinkedHashMap<>();
+        for (LifeDiscountCouponFriendRuleVo rule : ruleList) {
+            if (rule.getId() != null && !ruleMap.containsKey(rule.getId())) {
+                ruleMap.put(rule.getId(), rule);
+            }
+        }
+        ruleList = new ArrayList<>(ruleMap.values());
+        
+        // 根据活动名称过滤(模糊查询)
         if (StringUtils.isNotEmpty(acName)) {
-            String name = acName;
+            String name = acName.trim();
             ruleList = ruleList.stream()
                     .filter(i -> i.getAcName() != null && i.getAcName().contains(name))
                     .collect(Collectors.toList());
         }
+        
+        // 根据状态过滤
         if (StringUtils.isNotEmpty(status)) {
-            String s = status;
+            String s = status.trim();
             ruleList = ruleList.stream()
                     .filter(i -> i.getStatus() != null && i.getStatus().equals(s))
                     .collect(Collectors.toList());
         }
+        
         return ruleList;
     }
 
@@ -602,58 +633,256 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
         if (ObjectUtils.isNotEmpty(lifeDiscountCouponFriendRule)) {
             List<LifeDiscountCouponFriendRuleDetailVo> lifeDiscountCouponFriendRuleDetails = lifeDiscountCouponFriendRuleDetailMapper.getDetailList(lifeDiscountCouponFriendRule.getId().toString());
             ruleVo.setLifeDiscountCouponFriendRuleDetailVos(lifeDiscountCouponFriendRuleDetails);
+            
+            // 如果规则表中没有 couponType,尝试从关联的优惠券中获取
+            if (ruleVo.getCouponType() == null && ObjectUtils.isNotEmpty(lifeDiscountCouponFriendRuleDetails)) {
+                // 查找第一个优惠券(非代金券)的 couponType
+                for (LifeDiscountCouponFriendRuleDetailVo detail : lifeDiscountCouponFriendRuleDetails) {
+                    if (detail.getCouponId() != null && detail.getCouponType() != null) {
+                        ruleVo.setCouponType(detail.getCouponType());
+                        break;
+                    }
+                }
+            }
         }
         return ruleVo;
     }
 
+    /**
+     * 代金券类型常量
+     */
+    private static final Integer TYPE_VOUCHER = 4;
+
     @Override
-    public List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(String storeUserId, String friendStoreUserId, String storeName, Integer type) {
-        boolean voucherOnly = Integer.valueOf(4).equals(type);
-        if (StringUtils.isNotEmpty(storeUserId)) {
-            // 好友赠我
-            if (voucherOnly) {
-                QueryWrapper<LifeDiscountCouponFriendRuleVo> q = new QueryWrapper<>();
-                q.eq("ldcsf.store_user_id", storeUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.voucher_id");
-                if (StringUtils.isNotEmpty(storeName)) q.like("si.store_name", storeName);
-                return lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListVoucher(q);
+    public List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(String storeUserId, Integer queryType, String storeName, Integer type, Integer couponType) {
+        // 参数校验
+        if (StringUtils.isEmpty(storeUserId)) {
+            log.warn("getReceivedSendFriendCouponList 参数错误:storeUserId 不能为空");
+            return new ArrayList<>();
+        }
+        if (queryType == null || (queryType != 1 && queryType != 2)) {
+            log.warn("getReceivedSendFriendCouponList 参数错误:queryType 必须为 1 或 2,当前值={}", queryType);
+            return new ArrayList<>();
+        }
+        // 校验couponType参数(1=满减券,2=折扣券)
+        if (couponType != null && couponType != 1 && couponType != 2) {
+            log.warn("getReceivedSendFriendCouponList 参数错误:couponType 必须为 1(满减券)或 2(折扣券),当前值={}", couponType);
+            return new ArrayList<>();
+        }
+
+        // 判断是否仅查询代金券
+        boolean voucherOnly = TYPE_VOUCHER.equals(type);
+
+        // 确定查询类型
+        // 根据表结构(实体类注释):
+        // store_user_id: store_info的ID(店铺ID),收到券的店铺
+        // friend_store_user_id: store_user的ID(店铺用户ID),送券的用户
+        // 前端传入的 storeUserId 是当前登录账号的 store_user.id(店铺用户ID)
+        // queryType = 1: 我收到的(所有好友赠送给我的)-> store_user_id = 我的店铺ID(我是收到券的店铺,需要从店铺用户ID转换为店铺ID)
+        // queryType = 2: 我送出的(我送给所有好友的)-> friend_store_user_id = 我的店铺用户ID(我是送券的用户)
+        boolean isReceivedByMe = (queryType == 1);
+
+        return queryCouponList(isReceivedByMe, storeUserId, storeName, type, voucherOnly, couponType);
+    }
+
+    /**
+     * 查询赠券列表(抽取公共方法,减少代码重复)
+     *
+     * @param isReceivedByMe true=我收到的(所有好友赠送给我的),false=我送出的(我送给所有好友的)
+     * @param storeUserId     当前登录店铺用户ID
+     * @param storeName       店铺名称(模糊查询)
+     * @param type            类型参数(4=代金券,其他=优惠券,null=全部)
+     * @param voucherOnly     是否仅查询代金券
+     * @param couponType      优惠券类型(1=满减券,2=折扣券,null=全部优惠券)
+     * @return 赠券列表
+     */
+    private List<LifeDiscountCouponFriendRuleVo> queryCouponList(boolean isReceivedByMe, String storeUserId, String storeName, Integer type, boolean voucherOnly, Integer couponType) {
+        List<LifeDiscountCouponFriendRuleVo> result;
+        
+        // 仅查询代金券
+        if (voucherOnly) {
+            QueryWrapper<LifeDiscountCouponFriendRuleVo> queryWrapper = buildBaseQueryWrapper(isReceivedByMe, storeUserId, storeName);
+            queryWrapper.isNotNull("ldcsf.voucher_id");
+            result = isReceivedByMe
+                    ? lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListVoucher(queryWrapper)
+                    : lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListwzhyVoucher(queryWrapper);
+        } else {
+            // 查询优惠券
+            QueryWrapper<LifeDiscountCouponFriendRuleVo> couponQuery = buildBaseQueryWrapper(isReceivedByMe, storeUserId, storeName);
+            couponQuery.isNotNull("ldcsf.coupon_id");
+            // 如果指定了优惠券类型(满减券或折扣券),添加筛选条件
+            if (couponType != null) {
+                couponQuery.eq("ldc.coupon_type", couponType);
+            }
+            List<LifeDiscountCouponFriendRuleVo> couponList = isReceivedByMe
+                    ? lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponList(couponQuery)
+                    : lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListwzhy(couponQuery);
+
+            // 如果 type 不为 null,说明只查询优惠券,直接返回
+            if (type != null) {
+                result = couponList;
+            } else {
+                // type 为 null,需要合并优惠券和代金券
+                QueryWrapper<LifeDiscountCouponFriendRuleVo> voucherQuery = buildBaseQueryWrapper(isReceivedByMe, storeUserId, storeName);
+                voucherQuery.isNotNull("ldcsf.voucher_id");
+                List<LifeDiscountCouponFriendRuleVo> voucherList = isReceivedByMe
+                        ? lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListVoucher(voucherQuery)
+                        : lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListwzhyVoucher(voucherQuery);
+
+                // 合并列表
+                result = new ArrayList<>(couponList);
+                result.addAll(voucherList);
             }
-            QueryWrapper<LifeDiscountCouponFriendRuleVo> qCoupon = new QueryWrapper<>();
-            qCoupon.eq("ldcsf.store_user_id", storeUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.coupon_id");
-            if (StringUtils.isNotEmpty(storeName)) qCoupon.like("si.store_name", storeName);
-            List<LifeDiscountCouponFriendRuleVo> list = lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponList(qCoupon);
-            if (type != null) return list;
-            QueryWrapper<LifeDiscountCouponFriendRuleVo> qVoucher = new QueryWrapper<>();
-            qVoucher.eq("ldcsf.store_user_id", storeUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.voucher_id");
-            if (StringUtils.isNotEmpty(storeName)) qVoucher.like("si.store_name", storeName);
-            List<LifeDiscountCouponFriendRuleVo> voucherList = lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListVoucher(qVoucher);
-            list = new ArrayList<>(list);
-            list.addAll(voucherList);
-            list.sort((a, b) -> (b.getEndDate() != null && a.getEndDate() != null) ? b.getEndDate().compareTo(a.getEndDate()) : 0);
-            return list;
         }
-        if (StringUtils.isNotEmpty(friendStoreUserId)) {
-            // 我赠好友
-            if (voucherOnly) {
-                QueryWrapper<LifeDiscountCouponFriendRuleVo> q = new QueryWrapper<>();
-                q.eq("ldcsf.friend_store_user_id", friendStoreUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.voucher_id");
-                if (StringUtils.isNotEmpty(storeName)) q.like("si.store_name", storeName);
-                return lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListwzhyVoucher(q);
+
+        // 批量查询商户头像并填充
+        fillStoreAvatar(result);
+        
+        // 排序
+        return sortByEndDate(result);
+    }
+
+    /**
+     * 批量查询商户头像并填充到结果中
+     * 只从 store_user.head_img 获取商户头像,不涉及店铺信息表(store_info)
+     *
+     * @param couponList 券列表
+     */
+    private void fillStoreAvatar(List<LifeDiscountCouponFriendRuleVo> couponList) {
+        if (CollectionUtils.isEmpty(couponList)) {
+            return;
+        }
+
+        // 收集所有的店铺ID(storeId是Integer类型)
+        List<Integer> storeIds = couponList.stream()
+                .map(LifeDiscountCouponFriendRuleVo::getStoreId)
+                .filter(storeId -> storeId != null)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (storeIds.isEmpty()) {
+            log.warn("没有有效的店铺ID,无法查询商户头像");
+            return;
+        }
+
+        // 批量查询商户头像(只从 store_user.head_img 获取,优先取主账号头像)
+        LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
+        userWrapper.in(StoreUser::getStoreId, storeIds)
+                .eq(StoreUser::getDeleteFlag, 0)
+                .isNotNull(StoreUser::getHeadImg)
+                .ne(StoreUser::getHeadImg, "")
+                .orderByAsc(StoreUser::getAccountType) // 主账号(1)排在前面,null值排在最后
+                .orderByAsc(StoreUser::getId);
+        List<StoreUser> storeUsers = storeUserMapper.selectList(userWrapper);
+
+        // 构建店铺ID -> 头像的映射(优先主账号)
+        Map<Integer, String> storeAvatarMap = new HashMap<>();
+        for (StoreUser user : storeUsers) {
+            if (user.getStoreId() != null && user.getHeadImg() != null && !user.getHeadImg().trim().isEmpty()) {
+                Integer storeId = user.getStoreId();
+                // 如果该店铺还没有头像,或者当前用户是主账号(account_type = 1),则设置头像
+                // 主账号优先:如果已存在头像但当前是主账号,则覆盖
+                if (!storeAvatarMap.containsKey(storeId)) {
+                    // 该店铺还没有头像,直接设置
+                    storeAvatarMap.put(storeId, user.getHeadImg());
+                } else if (user.getAccountType() != null && user.getAccountType() == 1) {
+                    // 该店铺已有头像,但当前是主账号,优先使用主账号头像
+                    storeAvatarMap.put(storeId, user.getHeadImg());
+                }
+            }
+        }
+
+        // 填充头像到结果中
+        for (LifeDiscountCouponFriendRuleVo vo : couponList) {
+            if (vo.getStoreId() != null) {
+                String avatar = storeAvatarMap.get(vo.getStoreId());
+                if (avatar != null) {
+                    vo.setImgUrl(avatar);
+                }
             }
-            QueryWrapper<LifeDiscountCouponFriendRuleVo> qCoupon = new QueryWrapper<>();
-            qCoupon.eq("ldcsf.friend_store_user_id", friendStoreUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.coupon_id");
-            if (StringUtils.isNotEmpty(storeName)) qCoupon.like("si.store_name", storeName);
-            List<LifeDiscountCouponFriendRuleVo> list = lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListwzhy(qCoupon);
-            if (type != null) return list;
-            QueryWrapper<LifeDiscountCouponFriendRuleVo> qVoucher = new QueryWrapper<>();
-            qVoucher.eq("ldcsf.friend_store_user_id", friendStoreUserId).eq("ldcsf.delete_flag", 0).isNotNull("ldcsf.voucher_id");
-            if (StringUtils.isNotEmpty(storeName)) qVoucher.like("si.store_name", storeName);
-            List<LifeDiscountCouponFriendRuleVo> voucherList = lifeDiscountCouponStoreFriendMapper.getReceivedSendFriendCouponListwzhyVoucher(qVoucher);
-            list = new ArrayList<>(list);
-            list.addAll(voucherList);
-            list.sort((a, b) -> (b.getEndDate() != null && a.getEndDate() != null) ? b.getEndDate().compareTo(a.getEndDate()) : 0);
+        }
+    }
+
+    /**
+     * 构建基础查询条件
+     *
+     * @param isReceivedByMe true=我收到的(所有好友赠送给我的),false=我送出的(我送给所有好友的)
+     * @param storeUserId     当前登录店铺用户ID
+     * @param storeName       店铺名称(模糊查询)
+     * @return QueryWrapper
+     */
+    private QueryWrapper<LifeDiscountCouponFriendRuleVo> buildBaseQueryWrapper(boolean isReceivedByMe, String storeUserId, String storeName) {
+        QueryWrapper<LifeDiscountCouponFriendRuleVo> queryWrapper = new QueryWrapper<>();
+        // 根据表结构(实体类注释):
+        // store_user_id: store_info的ID(店铺ID),收到券的店铺
+        // friend_store_user_id: store_user的ID(店铺用户ID),送券的用户
+        // 前端传入的 storeUserId 是当前登录账号的 store_user.id(店铺用户ID)
+        if (isReceivedByMe) {
+            // 我收到的:查询所有好友赠送给我的券(我是收到券的店铺)
+            // store_user_id = 我的店铺ID(store_info.id),需要从店铺用户ID转换为店铺ID
+            StoreUser storeUser = storeUserMapper.selectById(storeUserId);
+            if (storeUser != null && storeUser.getStoreId() != null) {
+                queryWrapper.eq("ldcsf.store_user_id", storeUser.getStoreId());
+            } else {
+                // 如果查询不到店铺用户,返回空结果
+                queryWrapper.eq("1", "0"); // 永远不匹配的条件
+                log.warn("buildBaseQueryWrapper 查询不到店铺用户,storeUserId={}", storeUserId);
+            }
+        } else {
+            // 我送出的:查询我送给所有好友的券(我是送券的用户)
+            // friend_store_user_id = 我的店铺用户ID(store_user.id)
+            queryWrapper.eq("ldcsf.friend_store_user_id", storeUserId);
+        }
+        queryWrapper.eq("ldcsf.delete_flag", 0);
+        // 店铺名称模糊查询
+        if (StringUtils.isNotEmpty(storeName)) {
+            queryWrapper.like("si.store_name", storeName);
+        }
+        return queryWrapper;
+    }
+
+    /**
+     * 按有效期降序排序(null 值排在最后),如果有效期为 null 则按创建时间降序排序
+     *
+     * @param list 待排序列表
+     * @return 排序后的列表
+     */
+    private List<LifeDiscountCouponFriendRuleVo> sortByEndDate(List<LifeDiscountCouponFriendRuleVo> list) {
+        if (CollectionUtils.isEmpty(list)) {
             return list;
         }
-        return new ArrayList<>();
+        list.sort((a, b) -> {
+            // 优先按有效期(validDate)排序,如果为 null 则按创建时间(endDate)排序
+            Date validDateA = a.getValidDate();
+            Date validDateB = b.getValidDate();
+            Date endDateA = a.getEndDate();
+            Date endDateB = b.getEndDate();
+            
+            // 如果两个都有有效期,按有效期排序
+            if (validDateA != null && validDateB != null) {
+                return validDateB.compareTo(validDateA); // 降序:有效期晚的在前
+            }
+            // 如果只有一个有有效期,有有效期的排在前面
+            if (validDateA != null) {
+                return -1;
+            }
+            if (validDateB != null) {
+                return 1;
+            }
+            // 如果都没有有效期,按创建时间排序
+            if (endDateA == null && endDateB == null) {
+                return 0;
+            }
+            if (endDateA == null) {
+                return 1;
+            }
+            if (endDateB == null) {
+                return -1;
+            }
+            // 降序排序(最新的在前)
+            return endDateB.compareTo(endDateA);
+        });
+        return list;
     }
 
     /**
@@ -717,6 +946,7 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
         int grantedVoucher = 0;
         String couponName = null;  // 优惠券名称
         String voucherName = null;  // 代金券名称
+        LifeDiscountCoupon lifeDiscountCoupon = null;  // 保存优惠券对象,用于通知消息
 
         // 按优惠券ID发放:发放1张,扣减库存,发优惠券通知
         if (couponId != null && couponId > 0) {
@@ -728,7 +958,12 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
                     lifeDiscountCouponUser.setCouponId(coupon.getId());
                     lifeDiscountCouponUser.setUserId(commenterUserId);
                     lifeDiscountCouponUser.setReceiveTime(new Date());
-                    lifeDiscountCouponUser.setExpirationTime(coupon.getValidDate());
+                    // 设置过期时间:优先使用validDate,如果为null则使用endDate
+                    LocalDate expirationTime = coupon.getValidDate();
+                    if (expirationTime == null && coupon.getEndDate() != null) {
+                        expirationTime = coupon.getEndDate();
+                    }
+                    lifeDiscountCouponUser.setExpirationTime(expirationTime);
                     lifeDiscountCouponUser.setStatus(Integer.parseInt(DiscountCouponEnum.WAITING_USED.getValue()));
                     lifeDiscountCouponUser.setDeleteFlag(0);
                     lifeDiscountCouponUserMapper.insert(lifeDiscountCouponUser);
@@ -736,6 +971,7 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
                     lifeDiscountCouponMapper.updateById(coupon);
                     grantedCoupon = 1;
                     couponName = coupon.getName();  // 保存优惠券名称
+                    lifeDiscountCoupon = coupon;  // 保存优惠券对象,用于通知消息
                 } catch (Exception e) {
                     log.error("发放优惠券失败,userId={}, storeId={}, couponId={}, error={}", userId, storeId, couponId, e.getMessage(), e);
                 }
@@ -774,12 +1010,42 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
                 LifeNotice couponNotice = new LifeNotice();
                 couponNotice.setSenderId("system");
                 couponNotice.setReceiverId("user_" + lifeUser.getUserPhone());
-                // 明确标注是优惠券,并包含券的具体名称
-                String couponText = "您对店铺「" + storeInfo.getStoreName() + "」的好评已通过审核,已为您发放优惠券「" + couponName + "」,快去我的券包查看吧~";
+                
+                // 构建优惠券详情信息
+                String couponDetailText = "";
+                if (lifeDiscountCoupon != null) {
+                    Integer couponType = lifeDiscountCoupon.getCouponType();
+                    if (couponType != null && couponType == 2) {
+                        // 折扣券
+                        BigDecimal discountRate = lifeDiscountCoupon.getDiscountRate();
+                        if (discountRate != null) {
+                            couponDetailText = "(" + discountRate + "折";
+                            if (lifeDiscountCoupon.getMinimumSpendingAmount() != null) {
+                                couponDetailText += ",满" + lifeDiscountCoupon.getMinimumSpendingAmount() + "可用";
+                            }
+                            couponDetailText += ")";
+                        }
+                    } else {
+                        // 满减券(默认)
+                        if (lifeDiscountCoupon.getNominalValue() != null) {
+                            couponDetailText = "(满减" + lifeDiscountCoupon.getNominalValue() + "元";
+                            if (lifeDiscountCoupon.getMinimumSpendingAmount() != null) {
+                                couponDetailText += ",满" + lifeDiscountCoupon.getMinimumSpendingAmount() + "可用";
+                            }
+                            couponDetailText += ")";
+                        }
+                    }
+                }
+                
+                // 明确标注是优惠券,并包含券的具体名称和详情
+                String couponText = "您对店铺「" + storeInfo.getStoreName() + "」的好评已通过审核,已为您发放优惠券「" + couponName + "」" + couponDetailText + ",快去我的券包查看吧~";
                 JSONObject couponJson = new JSONObject();
                 couponJson.put("message", couponText);
                 couponJson.put("couponType", "优惠券");  // 明确标识券类型
                 couponJson.put("couponName", couponName);  // 券名称
+                if (lifeDiscountCoupon != null) {
+                    couponJson.put("couponTypeValue", lifeDiscountCoupon.getCouponType());  // 优惠券类型值(1-满减券,2-折扣券)
+                }
                 couponNotice.setContext(couponJson.toJSONString());
                 couponNotice.setNoticeType(1);
                 couponNotice.setTitle("好评送优惠券");  // 标题明确标注是优惠券

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

@@ -50,10 +50,10 @@ public class StoreBannerServiceImpl extends ServiceImpl<StoreBannerMapper, Store
             queryWrapper.ne(StoreBanner::getId, storeBannerDto.getId());
         }
 
-        long count = this.count(queryWrapper);
-        if (count > 0) {
-            return R.fail("该模块名称已存在,不能重复添加或修改");
-        }
+//        long count = this.count(queryWrapper);
+//        if (count > 0) {
+//            return R.fail("该模块名称已存在,不能重复添加或修改");
+//        }
 
         StoreBanner storeBanner = new StoreBanner();
         BeanUtils.copyProperties(storeBannerDto, storeBanner);

+ 110 - 84
alien-store/src/main/java/shop/alien/store/service/impl/StoreClockInServiceImpl.java

@@ -69,6 +69,8 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
 
     private final CommonCommentService commonCommentService;
 
+    private final CommonCommentMapper commonCommentMapper;
+
     private final WebSocketProcess webSocketProcess;
 
     // 初始化线程池
@@ -132,15 +134,19 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
     @Override
     public IPage<StoreClockInVo> getStoreClockInList(Integer userId, int page, int size, String phoneId, int mySelf,Integer storeId) {
         IPage<StoreClockIn> iPage = new Page<>(page, size);
-        // 查询我的点赞
+        
+        // 优化:提前查询所有需要的数据,避免在循环中查询
+        // 查询我的点赞 - 使用Set提高查找效率
         LambdaQueryWrapper<LifeLikeRecord> likeWrapper = new LambdaQueryWrapper<>();
         likeWrapper.eq(LifeLikeRecord::getDianzanId, phoneId);
         likeWrapper.eq(LifeLikeRecord::getType, "5");
         List<LifeLikeRecord> lifeLikeList = lifeLikeRecordMapper.selectList(likeWrapper);
-        List<String> likeList = lifeLikeList.stream().map(LifeLikeRecord::getHuifuId).collect(Collectors.toList());
-
+        Set<String> likeSet = lifeLikeList.stream()
+                .map(LifeLikeRecord::getHuifuId)
+                .collect(Collectors.toSet());
 
-        if(StringUtils.isNotBlank(String.valueOf(storeId)) && !String.valueOf(storeId).equals("null")){
+        // 优化:使用Objects.nonNull判断storeId
+        if (Objects.nonNull(storeId)) {
             IPage<StoreClockInVo> storeClockInIPage1 = storeClockInMapper.getStoreClockInList(iPage, new QueryWrapper<StoreClockIn>()
                     .eq("clock.store_id", storeId)
                     .eq("clock.delete_flag", 0)
@@ -155,15 +161,24 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
                                                     .eq("clock.user_id", userId)
                                     ))
                     .orderByDesc("created_time"));
-            storeClockInIPage1.getRecords().forEach(vo -> {
-                if (likeList.contains(String.valueOf(vo.getId()))) {
-                    vo.setIsLike("1");
-                } else {
-                    vo.setIsLike("0");
-                }
-            });
+            
+            List<StoreClockInVo> records = storeClockInIPage1.getRecords();
+            if (!records.isEmpty()) {
+                // 批量查询评论数量
+                List<Integer> clockInIds = records.stream()
+                        .map(StoreClockInVo::getId)
+                        .collect(Collectors.toList());
+                Map<Integer, Integer> commentCountMap = batchGetCommentCountMap(clockInIds);
+                
+                records.forEach(vo -> {
+                    vo.setIsLike(likeSet.contains(String.valueOf(vo.getId())) ? "1" : "0");
+                    vo.setCommentCount(commentCountMap.getOrDefault(vo.getId(), 0));
+                    setStoreTypeNew(vo);
+                });
+            }
             return storeClockInIPage1;
         }
+        
         QueryWrapper<StoreClockIn> wrapper = new QueryWrapper<>();
         wrapper.eq(1 == mySelf, "clock.user_id", userId);
         wrapper.isNotNull(0 == mySelf, "clock.img_url");
@@ -181,96 +196,107 @@ public class StoreClockInServiceImpl extends ServiceImpl<StoreClockInMapper, Sto
         wrapper.orderByDesc("clock.created_time");
 
         IPage<StoreClockInVo> storeClockInIPage = storeClockInMapper.getStoreClockInList(iPage, wrapper);
+        List<StoreClockInVo> records = storeClockInIPage.getRecords();
+        
+        if (records.isEmpty()) {
+            return storeClockInIPage;
+        }
 
-        // 查询我的关注
+        // 优化:批量查询所有需要的数据
+        // 查询我的关注 - 使用Set提高查找效率
         LambdaQueryWrapper<LifeFans> lifeFansWrapper = new LambdaQueryWrapper<>();
         lifeFansWrapper.eq(LifeFans::getFansId, phoneId);
         List<LifeFans> lifeFansList = lifeFansMapper.selectList(lifeFansWrapper);
-        List<String> followList = lifeFansList.stream().map(LifeFans::getFollowedId).collect(Collectors.toList());
+        Set<String> followSet = lifeFansList.stream()
+                .map(LifeFans::getFollowedId)
+                .collect(Collectors.toSet());
 
-        // 查询我的粉丝
+        // 查询我的粉丝 - 使用Set提高查找效率
         lifeFansWrapper = new LambdaQueryWrapper<>();
         lifeFansWrapper.eq(LifeFans::getFollowedId, phoneId);
         lifeFansList = lifeFansMapper.selectList(lifeFansWrapper);
-        List<String> fansList = lifeFansList.stream().map(LifeFans::getFansId).collect(Collectors.toList());
+        Set<String> fansSet = lifeFansList.stream()
+                .map(LifeFans::getFansId)
+                .collect(Collectors.toSet());
 
-
-
-        // 查询我的收藏
+        // 查询我的收藏 - 使用Set提高查找效率
         LambdaQueryWrapper<LifeCollect> collectWrapper = new LambdaQueryWrapper<>();
         collectWrapper.eq(LifeCollect::getUserId, String.valueOf(userId));
         List<LifeCollect> lifeCollectList = lifeCollectMapper.selectList(collectWrapper);
-        List<String> collectList = lifeCollectList.stream().map(LifeCollect::getStoreId).collect(Collectors.toList());
-
-
-        storeClockInIPage.getRecords().forEach(vo -> {
-            if (followList.contains(vo.getPhoneId())) {
-                vo.setIsFollowThis("1");
-            } else {
-                vo.setIsFollowThis("0");
-            }
-            if (fansList.contains(vo.getPhoneId())) {
-                vo.setIsFollowMe("1");
-            } else {
-                vo.setIsFollowMe("0");
-            }
-            if (likeList.contains(String.valueOf(vo.getId()))) {
-                vo.setIsLike("1");
-            } else {
-                vo.setIsLike("0");
-            }
-            if (collectList.contains(String.valueOf(vo.getStoreId()))) {
-                vo.setIsCollect("1");
-            } else {
-                vo.setIsCollect("0");
-            }
-
-//            // 该用户的打卡记录
-//            LambdaQueryWrapper<StoreClockIn> clockInWrapper = new LambdaQueryWrapper<>();
-//            clockInWrapper.eq(StoreClockIn::getUserId, vo.getUserId());
-//            List<StoreClockIn> clockInList = storeClockInMapper.selectList(clockInWrapper);
-//
-//            Set<Integer> clockInSet = clockInList.stream().map(StoreClockIn::getStoreId).collect(Collectors.toSet());
-//            // 该用户今天打卡记录
-//            Set<Integer> clockInTodaySet = clockInList.stream().filter(item -> item.getCreatedTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().equals(LocalDate.now())).map(StoreClockIn::getStoreId).collect(Collectors.toSet());
-//
-//            // 在该店铺的打卡次数
-//            Map<Integer, Long> clockInStoreNumMap = clockInList.stream().collect(Collectors.groupingBy(StoreClockIn::getStoreId, Collectors.counting()));
-//
-//            // 该用户打卡的所有店铺次数(一个店铺只算一次)
-//            int clockInNum = (int) clockInList.stream().map(StoreClockIn::getStoreId).distinct().count();
-//
-//            // 是否在该店铺打过卡
-//            if (clockInSet.contains(vo.getStoreId())){
-//                vo.setClockInStore("1");
-//            } else {
-//                vo.setClockInStore("0");
-//            }
-//            // 今天是否在该店铺打过卡
-//            if (clockInTodaySet.contains(vo.getStoreId())){
-//                vo.setClockInStoreToday("1");
-//            } else {
-//                vo.setClockInStoreToday("0");
-//            }
-//            // 在该店铺打卡次数clockInStoreNum
-//            vo.setClockInStoreNum(clockInStoreNumMap.get(vo.getStoreId()));
-//            // 打卡次数
-//            vo.setClockInNum(clockInNum);
-            Map<String, Object> commitCount = commonCommentService.getCommitCount(vo.getId(), CommentSourceTypeEnum.CLOCK_IN_COMMENT.getType(), userId.toString(), null);
-            vo.setCommentCount(Integer.parseInt(commitCount.get("commentCount").toString()));
-            if(Arrays.asList("酒吧", "KTV", "洗浴汗蒸", "按摩足疗").contains(vo.getBusinessSectionName())){
-                vo.setStoreTypeNew(1);
-            } else if (Arrays.asList("丽人美发", "运动健身").contains(vo.getBusinessSectionName())){
-                vo.setStoreTypeNew(2);
-            } else if (Arrays.asList("特色美食").contains(vo.getBusinessSectionName())){
-                vo.setStoreTypeNew(3);
-            }
+        Set<String> collectSet = lifeCollectList.stream()
+                .map(LifeCollect::getStoreId)
+                .collect(Collectors.toSet());
+
+        // 优化:批量查询评论数量,避免N+1问题
+        List<Integer> clockInIds = records.stream()
+                .map(StoreClockInVo::getId)
+                .collect(Collectors.toList());
+        Map<Integer, Integer> commentCountMap = batchGetCommentCountMap(clockInIds);
+
+        // 优化:使用Set进行O(1)查找,而不是List的O(n)查找
+        records.forEach(vo -> {
+            vo.setIsFollowThis(followSet.contains(vo.getPhoneId()) ? "1" : "0");
+            vo.setIsFollowMe(fansSet.contains(vo.getPhoneId()) ? "1" : "0");
+            vo.setIsLike(likeSet.contains(String.valueOf(vo.getId())) ? "1" : "0");
+            vo.setIsCollect(collectSet.contains(String.valueOf(vo.getStoreId())) ? "1" : "0");
+            vo.setCommentCount(commentCountMap.getOrDefault(vo.getId(), 0));
+            setStoreTypeNew(vo);
         });
 
-
         return storeClockInIPage;
     }
 
+    /**
+     * 批量查询评论数量
+     * 
+     * @param clockInIds 打卡记录ID列表
+     * @return 打卡记录ID -> 评论数量的映射
+     */
+    private Map<Integer, Integer> batchGetCommentCountMap(List<Integer> clockInIds) {
+        if (clockInIds == null || clockInIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        
+        try {
+            List<shop.alien.entity.store.vo.CommonCommentVo> commentCountList = 
+                    commonCommentMapper.batchGetCommentCount(
+                            CommentSourceTypeEnum.CLOCK_IN_COMMENT.getType(), 
+                            clockInIds
+                    );
+            
+            return commentCountList.stream()
+                    .filter(item -> item.getSourceId() != null && item.getCommentCount() != null)
+                    .collect(Collectors.toMap(
+                            item -> item.getSourceId().intValue(),
+                            item -> item.getCommentCount(),
+                            (existing, replacement) -> existing
+                    ));
+        } catch (Exception e) {
+            log.error("批量查询评论数量失败", e);
+            return Collections.emptyMap();
+        }
+    }
+
+    /**
+     * 设置店铺类型
+     * 
+     * @param vo 打卡记录VO
+     */
+    private void setStoreTypeNew(StoreClockInVo vo) {
+        String businessSectionName = vo.getBusinessSectionName();
+        if (businessSectionName == null) {
+            return;
+        }
+        
+        if (Arrays.asList("酒吧", "KTV", "洗浴汗蒸", "按摩足疗").contains(businessSectionName)) {
+            vo.setStoreTypeNew(1);
+        } else if (Arrays.asList("丽人美发", "运动健身").contains(businessSectionName)) {
+            vo.setStoreTypeNew(2);
+        } else if (Arrays.asList("特色美食").contains(businessSectionName)) {
+            vo.setStoreTypeNew(3);
+        }
+    }
+
     @Override
     public int deleteClockIn(Integer id) {
         return storeClockInMapper.deleteById(id);

+ 99 - 15
alien-store/src/main/java/shop/alien/store/service/impl/StoreCustomerServiceServiceImpl.java

@@ -5,18 +5,21 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
 import shop.alien.entity.store.AiIntelligentAssistant;
 import shop.alien.entity.store.StoreCustomerService;
 import shop.alien.mapper.AiIntelligentAssistantMapper;
 import shop.alien.mapper.StoreCustomerServiceMapper;
 import shop.alien.store.service.StoreCustomerServiceService;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import org.springframework.stereotype.Service;
 
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -36,9 +39,15 @@ public class StoreCustomerServiceServiceImpl extends ServiceImpl<StoreCustomerSe
 
     @Override
     public StoreCustomerService getByQuestion(String question) {
+        if (StringUtils.isEmpty(question)) {
+            return null;
+        }
         LambdaQueryWrapper<StoreCustomerService> lambdaQueryWrapper = new LambdaQueryWrapper<>();
-        List<StoreCustomerService> list = storeCustomerServiceMapper.selectList(lambdaQueryWrapper);
-        return list.stream().filter(item -> item.getQuestion().contains(question)).findFirst().orElse(null);
+        lambdaQueryWrapper.like(StoreCustomerService::getQuestion, question)
+                .eq(StoreCustomerService::getDeleteFlag, 0)
+                .orderByDesc(StoreCustomerService::getCreatedTime)
+                .last("limit 1");
+        return storeCustomerServiceMapper.selectOne(lambdaQueryWrapper);
     }
 
     @Override
@@ -77,6 +86,13 @@ public class StoreCustomerServiceServiceImpl extends ServiceImpl<StoreCustomerSe
 
     @Override
     public List<StoreCustomerService> getRandList(String type, Integer limit) {
+        if (StringUtils.isEmpty(type) || limit == null || limit <= 0) {
+            return new ArrayList<>();
+        }
+        // 限制最大查询数量,防止SQL注入和性能问题
+        if (limit > 100) {
+            limit = 100;
+        }
         LambdaQueryWrapper<StoreCustomerService> lambdaQueryWrapper = new LambdaQueryWrapper<>();
         lambdaQueryWrapper.eq(StoreCustomerService::getType, type)
                 .eq(StoreCustomerService::getDeleteFlag, 0)
@@ -86,34 +102,102 @@ public class StoreCustomerServiceServiceImpl extends ServiceImpl<StoreCustomerSe
 
     @Override
     public List<AiIntelligentAssistant> saveAiIntelligentAssistant(List<AiIntelligentAssistant> aiIntelligentAssistants) {
+        if (aiIntelligentAssistants == null || aiIntelligentAssistants.isEmpty()) {
+            return new ArrayList<>();
+        }
+        Date now = new Date();
         for (AiIntelligentAssistant aiIntelligentAssistant : aiIntelligentAssistants) {
+            if (aiIntelligentAssistant == null) {
+                continue;
+            }
             aiIntelligentAssistant.setDeleteFlag(0);
-            if (aiIntelligentAssistant.getType() != 0) {
-                aiIntelligentAssistant.setCreatedTime(new Date());
-                aiIntelligentAssistant.setUpdatedTime(new Date());
+            if (aiIntelligentAssistant.getType() != null && aiIntelligentAssistant.getType() != 0) {
+                aiIntelligentAssistant.setCreatedTime(now);
+                aiIntelligentAssistant.setUpdatedTime(now);
             }
         }
-        aiIntelligentAssistantMapper.insertList(aiIntelligentAssistants);
-        return aiIntelligentAssistants;
+        // 过滤掉null值
+        List<AiIntelligentAssistant> validList = aiIntelligentAssistants.stream()
+                .filter(item -> item != null)
+                .collect(Collectors.toList());
+        if (validList.isEmpty()) {
+            return new ArrayList<>();
+        }
+        aiIntelligentAssistantMapper.insertList(validList);
+        return validList;
     }
 
+    /**
+     * 查询聊天记录
+     * 只返回最近7天内的记录
+     */
     @Override
-    public List<AiIntelligentAssistant> selectAiIntelligentAssistant(String userId,String time,Integer talkSource) {
+    public List<AiIntelligentAssistant> selectAiIntelligentAssistant(String userId, String time, Integer talkSource) {
+        // 参数校验
+        if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(time) || talkSource == null) {
+            return new ArrayList<>();
+        }
+        
+        // 解析time字符串为Date类型
+        Date queryTime;
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            queryTime = sdf.parse(time);
+        } catch (Exception e) {
+            // 如果解析失败,使用当前时间
+            queryTime = new Date();
+        }
+        
+        // 计算7天前的时间
+        Date now = new Date();
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(now);
+        calendar.add(Calendar.DAY_OF_YEAR, -7);
+        Date sevenDaysAgo = calendar.getTime();
+        
+        // 确保查询时间不早于7天前
+        if (queryTime.before(sevenDaysAgo)) {
+            queryTime = sevenDaysAgo;
+        }
+        
+        // 查找最后一个type=0的记录作为分页标记点(限制在7天内)
         LambdaQueryWrapper<AiIntelligentAssistant> last = new LambdaQueryWrapper<AiIntelligentAssistant>()
-                .lt(AiIntelligentAssistant::getCreatedTime, time)
+                .lt(AiIntelligentAssistant::getCreatedTime, queryTime)
+                .ge(AiIntelligentAssistant::getCreatedTime, sevenDaysAgo) // 只查询7天内的记录
                 .eq(AiIntelligentAssistant::getDeleteFlag, 0)
-                .eq(AiIntelligentAssistant::getType, 0).eq(AiIntelligentAssistant::getTalkSource, talkSource).eq(AiIntelligentAssistant::getUserId, userId).orderByDesc(AiIntelligentAssistant::getCreatedTime).last("limit 1");
+                .eq(AiIntelligentAssistant::getType, 0)
+                .eq(AiIntelligentAssistant::getTalkSource, talkSource)
+                .eq(AiIntelligentAssistant::getUserId, userId)
+                .orderByDesc(AiIntelligentAssistant::getCreatedTime)
+                .last("limit 1");
         AiIntelligentAssistant aiIntelligentAssistant = aiIntelligentAssistantMapper.selectOne(last);
+        
         if (ObjectUtils.isNotEmpty(aiIntelligentAssistant)) {
+            // 找到了标记点,从标记点开始查询到time之间的记录
+            Date markPointTime = aiIntelligentAssistant.getCreatedTime();
+            // 确保标记点时间不早于7天前
+            if (markPointTime.before(sevenDaysAgo)) {
+                markPointTime = sevenDaysAgo;
+            }
+            
+            LambdaQueryWrapper<AiIntelligentAssistant> queryWrapper = new LambdaQueryWrapper<AiIntelligentAssistant>()
+                    .ge(AiIntelligentAssistant::getCreatedTime, markPointTime)
+                    .lt(AiIntelligentAssistant::getCreatedTime, queryTime)
+                    .eq(AiIntelligentAssistant::getDeleteFlag, 0)
+                    .eq(AiIntelligentAssistant::getUserId, userId)
+                    .eq(AiIntelligentAssistant::getTalkSource, talkSource)
+                    .orderByAsc(AiIntelligentAssistant::getCreatedTime);
+            return aiIntelligentAssistantMapper.selectList(queryWrapper);
+        } else {
+            // 如果找不到type=0的标记点,直接查询7天内的所有记录(支持首次加载)
             LambdaQueryWrapper<AiIntelligentAssistant> queryWrapper = new LambdaQueryWrapper<AiIntelligentAssistant>()
-                    .ge(AiIntelligentAssistant::getCreatedTime, aiIntelligentAssistant.getCreatedTime())
-                    .lt(AiIntelligentAssistant::getCreatedTime, time)
+                    .ge(AiIntelligentAssistant::getCreatedTime, sevenDaysAgo)
+                    .lt(AiIntelligentAssistant::getCreatedTime, queryTime)
                     .eq(AiIntelligentAssistant::getDeleteFlag, 0)
                     .eq(AiIntelligentAssistant::getUserId, userId)
                     .eq(AiIntelligentAssistant::getTalkSource, talkSource)
                     .orderByAsc(AiIntelligentAssistant::getCreatedTime);
             return aiIntelligentAssistantMapper.selectList(queryWrapper);
         }
-        return new ArrayList<>();
     }
 }

+ 53 - 69
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -393,13 +393,20 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         if (storeMainInfoVo.getRenewContractStatus() == 1) {
             storeMainInfoVo.setRenewContractStatus(0);
         }
-        if (storeMainInfoVo.getStoreStatus() == -1) {
+        // 处理注销状态:如果已超过7天,店铺应该已被定时任务删除,这里只处理7天内的倒计时
+        if (storeMainInfoVo.getStoreStatus() == -1 && storeMainInfoVo.getLogoutTime() != null) {
             LocalDateTime localDateTime = storeMainInfoVo.getLogoutTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
             LocalDateTime future = localDateTime.plusDays(7);
             LocalDateTime now = LocalDateTime.now();
             Duration duration = Duration.between(now, future);
             long correct = duration.toMillis();
-            storeMainInfoVo.setCountdown(correct);
+            // 如果倒计时已过期(小于0),说明已超过7天,店铺应该已被删除,不设置倒计时
+            if (correct > 0) {
+                storeMainInfoVo.setCountdown(correct);
+            } else {
+                // 超过7天,倒计时设为0,前端应该提示店铺已注销
+                storeMainInfoVo.setCountdown(0);
+            }
         }
         //存入门店地址
         storeMainInfoVo.setStoreAddress(storeInfo.getStoreAddress());
@@ -2745,6 +2752,25 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         LambdaQueryWrapper<StoreInfo> storeUserLambdaQueryWrapper = new LambdaQueryWrapper<>();
         storeUserLambdaQueryWrapper.eq(StoreInfo::getId, storeInfo.getId());
         StoreInfo storeIn = storeInfoMapper.selectOne(storeUserLambdaQueryWrapper);
+        
+        // 检查店铺是否存在
+        if (storeIn == null) {
+            throw new IllegalArgumentException("店铺不存在或已被删除");
+        }
+        
+        // 检查是否超过7天冷静期
+        if (storeIn.getLogoutTime() != null) {
+            Date logoutTime = storeIn.getLogoutTime();
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime(logoutTime);
+            calendar.add(Calendar.DAY_OF_YEAR, 7);
+            Date sevenDaysLater = calendar.getTime();
+            Date now = new Date();
+            if (now.after(sevenDaysLater)) {
+                throw new IllegalArgumentException("已超过7天冷静期,无法取消注销");
+            }
+        }
+        
         // 修改注销标记为0
         storeIn.setLogoutFlag(0);
         // 注销状态变为可用
@@ -3501,7 +3527,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                     }
                     Integer status = activity.getStatus();
                     String activityType = activity.getActivityType();
-                    if (!"1".equals(activityType)) {
+                    if (!"MARKETING".equals(activityType)) {
                         // 非MARKETING且未开始,不展示
                         return status == null || status != 2;
                     }
@@ -5875,12 +5901,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             result.setClockInStoreToday(0);
         }
 
-
-        Map<String, Object> commitCountAndScore = storeCommentService.getCommitCountAndScore(null, 5, Integer.parseInt(storeId), null, null);
-//        result.setScore(Double.parseDouble(commitCountAndScore.get("score").toString()));
-//        result.setCommitCount(commitCountAndScore.get("commitCount").toString());
         Integer totalCount = 0;
-        double storeScore;
         Object ratingObj =  commonRatingService.getRatingCount(storeInfo.getId(), 1);
         if (ratingObj != null) {
             Map<String, Object> ratingMap = (Map<String, Object>) ratingObj;
@@ -7502,7 +7523,6 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         String name = holidayName.trim();
 
         try {
-            // 元旦:1月1日,通常3天假期(1月1日-1月3日)
             if (name.contains("元旦")) {
                 return new LocalDate[]{
                     LocalDate.of(year, 1, 1),
@@ -7510,21 +7530,16 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 };
             }
 
-            // 春节:农历正月初一,通常7天假期
             if (name.contains("春节")) {
-                // 春节日期每年不同,这里提供近几年的数据,建议使用农历工具精确计算
-                // 2024年春节:2月10日(农历正月初一)-2月16日
-                // 2025年春节:1月29日(农历正月初一)-2月4日
-                // 2026年春节:2月17日(农历正月初一)-2月23日
-                if (year == 2024) {
+                if (year == 2028) {
                     return new LocalDate[]{
-                        LocalDate.of(year, 2, 10),
-                        LocalDate.of(year, 2, 16)
+                        LocalDate.of(year, 1, 25),
+                        LocalDate.of(year, 2, 2)
                     };
-                } else if (year == 2025) {
+                } else if (year == 2027) {
                     return new LocalDate[]{
-                        LocalDate.of(year, 1, 29),
-                        LocalDate.of(year, 2, 4)
+                        LocalDate.of(year, 2, 5),
+                        LocalDate.of(year, 2, 13)
                     };
                 } else if (year == 2026) {
                     return new LocalDate[]{
@@ -7532,7 +7547,6 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                         LocalDate.of(year, 2, 23)
                     };
                 } else {
-                    // 默认:根据年份大致估算(建议使用农历工具精确计算)
                     return new LocalDate[]{
                         LocalDate.of(year, 2, 10),
                         LocalDate.of(year, 2, 16)
@@ -7540,42 +7554,34 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 }
             }
 
-            // 情人节:2月14日,1天
             if (name.contains("情人节")) {
                 LocalDate date = LocalDate.of(year, 2, 14);
                 return new LocalDate[]{date, date};
             }
 
-            // 元宵节:农历正月十五,1天(通常在2月或3月)
             if (name.contains("元宵")) {
-                // 元宵节是春节后的第15天,这里简化处理
-                // 2024年元宵节:2月24日,2025年元宵节:2月12日,2026年元宵节:3月4日
-                if (year == 2024) {
-                    LocalDate date = LocalDate.of(year, 2, 24);
+                if (year == 2028) {
+                    LocalDate date = LocalDate.of(year, 2, 9);
                     return new LocalDate[]{date, date};
-                } else if (year == 2025) {
-                    LocalDate date = LocalDate.of(year, 2, 12);
+                } else if (year == 2027) {
+                    LocalDate date = LocalDate.of(year, 2, 20);
                     return new LocalDate[]{date, date};
                 } else if (year == 2026) {
-                    LocalDate date = LocalDate.of(year, 3, 4);
+                    LocalDate date = LocalDate.of(year, 3, 3);
                     return new LocalDate[]{date, date};
                 } else {
-                    // 默认:大致在2月底3月初
                     LocalDate date = LocalDate.of(year, 2, 24);
                     return new LocalDate[]{date, date};
                 }
             }
 
-            // 清明节:通常4月4日或5日,通常3天假期
             if (name.contains("清明")) {
-                // 清明节通常在4月4日或5日,这里以4月4日为基准,包含前后各1天
                 return new LocalDate[]{
                     LocalDate.of(year, 4, 4),
                     LocalDate.of(year, 4, 6)
                 };
             }
 
-            // 劳动节:5月1日,通常5天假期(5月1日-5月5日)
             if (name.contains("劳动") || name.contains("五一")) {
                 return new LocalDate[]{
                     LocalDate.of(year, 5, 1),
@@ -7583,25 +7589,19 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 };
             }
 
-            // 儿童节:6月1日,1天
             if (name.contains("儿童")) {
                 LocalDate date = LocalDate.of(year, 6, 1);
                 return new LocalDate[]{date, date};
             }
 
-            // 端午节:农历五月初五,通常3天假期
             if (name.contains("端午")) {
-                // 端午节通常在6月,这里提供近几年的数据
-                // 2024年端午节:6月10日,2025年端午节:5月31日,2026年端午节:6月19日
-                if (year == 2024) {
+                if (year == 2028) {
                     return new LocalDate[]{
-                        LocalDate.of(year, 6, 10),
-                        LocalDate.of(year, 6, 12)
+                        LocalDate.of(year, 6, 27)
                     };
-                } else if (year == 2025) {
+                } else if (year == 2027) {
                     return new LocalDate[]{
-                        LocalDate.of(year, 5, 31),
-                        LocalDate.of(year, 6, 2)
+                        LocalDate.of(year, 6, 9)
                     };
                 } else if (year == 2026) {
                     return new LocalDate[]{
@@ -7609,7 +7609,6 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                         LocalDate.of(year, 6, 21)
                     };
                 } else {
-                    // 默认:大致在6月中旬
                     return new LocalDate[]{
                         LocalDate.of(year, 6, 10),
                         LocalDate.of(year, 6, 12)
@@ -7617,39 +7616,30 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 }
             }
 
-            // 七夕:农历七月初七,1天(通常在8月)
             if (name.contains("七夕")) {
-                // 七夕通常在8月,这里提供近几年的数据
-                // 2024年七夕:8月10日,2025年七夕:7月31日,2026年七夕:8月20日
-                if (year == 2024) {
-                    LocalDate date = LocalDate.of(year, 8, 10);
+                if (year == 2028) {
+                    LocalDate date = LocalDate.of(year, 8, 26);
                     return new LocalDate[]{date, date};
-                } else if (year == 2025) {
-                    LocalDate date = LocalDate.of(year, 7, 31);
+                } else if (year == 2027) {
+                    LocalDate date = LocalDate.of(year, 8, 8);
                     return new LocalDate[]{date, date};
                 } else if (year == 2026) {
-                    LocalDate date = LocalDate.of(year, 8, 20);
+                    LocalDate date = LocalDate.of(year, 8, 19);
                     return new LocalDate[]{date, date};
                 } else {
-                    // 默认:大致在8月上旬
                     LocalDate date = LocalDate.of(year, 8, 10);
                     return new LocalDate[]{date, date};
                 }
             }
 
-            // 中秋节:农历八月十五,通常3天假期
             if (name.contains("中秋")) {
-                // 中秋节通常在9月或10月,这里提供近几年的数据
-                // 2024年中秋节:9月17日,2025年中秋节:10月6日,2026年中秋节:9月25日
-                if (year == 2024) {
+                if (year == 2028) {
                     return new LocalDate[]{
-                        LocalDate.of(year, 9, 17),
-                        LocalDate.of(year, 9, 19)
+                        LocalDate.of(year, 10, 3)
                     };
-                } else if (year == 2025) {
+                } else if (year == 2027) {
                     return new LocalDate[]{
-                        LocalDate.of(year, 10, 6),
-                        LocalDate.of(year, 10, 8)
+                        LocalDate.of(year, 9, 15)
                     };
                 } else if (year == 2026) {
                     return new LocalDate[]{
@@ -7657,7 +7647,6 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                         LocalDate.of(year, 9, 27)
                     };
                 } else {
-                    // 默认:大致在9月下旬
                     return new LocalDate[]{
                         LocalDate.of(year, 9, 29),
                         LocalDate.of(year, 10, 1)
@@ -7665,7 +7654,6 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 }
             }
 
-            // 国庆节:10月1日,通常7天假期(10月1日-10月7日)
             if (name.contains("国庆")) {
                 return new LocalDate[]{
                     LocalDate.of(year, 10, 1),
@@ -7673,20 +7661,16 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 };
             }
 
-            // 冬至:通常12月21日或22日,1天
             if (name.contains("冬至")) {
-                // 冬至通常在12月21日或22日,这里统一为12月22日
                 LocalDate date = LocalDate.of(year, 12, 22);
                 return new LocalDate[]{date, date};
             }
 
-            // 平安夜:12月24日,1天
             if (name.contains("平安夜")) {
                 LocalDate date = LocalDate.of(year, 12, 24);
                 return new LocalDate[]{date, date};
             }
 
-            // 圣诞节:12月25日,1天
             if (name.contains("圣诞")) {
                 LocalDate date = LocalDate.of(year, 12, 25);
                 return new LocalDate[]{date, date};

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

@@ -23,8 +23,6 @@ import shop.alien.mapper.storePlantform.StoreOperationalActivityAchievementMappe
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
 import shop.alien.store.dto.StoreOperationalActivityAchievementDto;
 import shop.alien.store.service.StoreOperationalActivityAchievementService;
-import shop.alien.util.common.DateUtils;
-
 /**
  * 运营活动成果服务实现
  *
@@ -58,8 +56,8 @@ public class StoreOperationalActivityAchievementServiceImpl implements StoreOper
         if (activity == null) {
             throw new IllegalArgumentException("活动不存在");
         }
-        Date nowDay = DateUtils.toDateOnly(new Date());
-        if (activity.getStartTime() != null && nowDay.before(DateUtils.toDateOnly(activity.getStartTime()))) {
+        Date now = new Date();
+        if (activity.getStartTime() != null && now.before(activity.getStartTime())) {
             throw new IllegalArgumentException("活动未开始,不允许新增成果");
         }
 

+ 16 - 17
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityServiceImpl.java

@@ -32,7 +32,6 @@ import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.dto.StoreOperationalActivitySignupDto;
 import shop.alien.store.service.StoreOperationalActivityService;
-import shop.alien.util.common.DateUtils;
 import shop.alien.util.common.Constants;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 
@@ -323,11 +322,11 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
         if (status == null || (status != 2 && status != 5)) {
             throw new IllegalArgumentException("活动状态不允许报名");
         }
-        Date nowDay = DateUtils.toDateOnly(new Date());
-        if (activity.getSignupStartTime() != null && nowDay.before(DateUtils.toDateOnly(activity.getSignupStartTime()))) {
+        Date now = new Date();
+        if (activity.getSignupStartTime() != null && now.before(activity.getSignupStartTime())) {
             throw new IllegalArgumentException("未到报名时间");
         }
-        if (activity.getSignupEndTime() != null && nowDay.after(DateUtils.toDateOnly(activity.getSignupEndTime()))) {
+        if (activity.getSignupEndTime() != null && now.after(activity.getSignupEndTime())) {
             throw new IllegalArgumentException("报名已结束");
         }
 //        if (activity.getStartTime() != null && status == 5 && now.before(activity.getStartTime())) {
@@ -428,13 +427,13 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
         if (activity == null) {
             return false;
         }
-        Date nowDay = DateUtils.toDateOnly(new Date());
-        Date start = DateUtils.toDateOnly(activity.getSignupStartTime());
-        Date end = DateUtils.toDateOnly(activity.getSignupEndTime());
-        if (start != null && nowDay.before(start)) {
+        Date now = new Date();
+        Date start = activity.getSignupStartTime();
+        Date end = activity.getSignupEndTime();
+        if (start != null && now.before(start)) {
             return false;
         }
-        if (end != null && nowDay.after(end)) {
+        if (end != null && now.after(end)) {
             return false;
         }
         return true;
@@ -444,11 +443,11 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
         if (activity == null) {
             return null;
         }
-        Date nowDay = DateUtils.toDateOnly(new Date());
+        Date now = new Date();
         if (activity.getStatus() != null && activity.getStatus() == 7) {
             return 0;
         }
-        if (activity.getEndTime() != null && nowDay.after(DateUtils.toDateOnly(activity.getEndTime()))) {
+        if (activity.getEndTime() != null && now.after(activity.getEndTime())) {
             return 0;
         }
         Integer status = activity.getStatus();
@@ -456,10 +455,10 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
             return 1;
         }
         if (signupStatus != null && signupStatus == 2
-                && activity.getStartTime() != null && nowDay.before(DateUtils.toDateOnly(activity.getStartTime()))) {
+                && activity.getStartTime() != null && now.before(activity.getStartTime())) {
             return 2;
         }
-        if (activity.getSignupEndTime() != null && nowDay.after(DateUtils.toDateOnly(activity.getSignupEndTime()))) {
+        if (activity.getSignupEndTime() != null && now.after(activity.getSignupEndTime())) {
             if (status != null && status == 5 && signupStatus != null && signupStatus == 2) {
                 return 3;
             }
@@ -473,7 +472,7 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
                 return 4;
             }
             if (signupStatus == 1) {
-                if (activity.getSignupEndTime() != null && nowDay.before(DateUtils.toDateOnly(activity.getSignupEndTime()))) {
+                if (activity.getSignupEndTime() != null && now.before(activity.getSignupEndTime())) {
                     return 5;
                 }
                 return 1;
@@ -519,14 +518,14 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
         if (item == null) {
             return null;
         }
-        Date nowDay = DateUtils.toDateOnly(new Date());
+        Date now = new Date();
         Integer activityStatus = item.getActivityStatus();
         boolean hasAchievement = item.getHasAchievement() != null && item.getHasAchievement() == 1;
         Integer auditStatus = item.getSignupAuditStatus();
         Date startTime = item.getStartTime();
         Date endTime = item.getEndTime();
         boolean ended = (activityStatus != null && (activityStatus == 7 || activityStatus == 6))
-                || (endTime != null && nowDay.after(DateUtils.toDateOnly(endTime)));
+                || (endTime != null && now.after(endTime));
         if (ended) {
             return hasAchievement ? 2 : 1;
         }
@@ -548,7 +547,7 @@ public class StoreOperationalActivityServiceImpl implements StoreOperationalActi
             }
         }
         if (auditStatus != null && auditStatus == 0
-                && startTime != null && !nowDay.after(DateUtils.toDateOnly(startTime))) {
+                && startTime != null && !now.after(startTime)) {
             return 7;
         }
         return null;

+ 6 - 25
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalStatisticsServiceImpl.java

@@ -318,10 +318,9 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
     }
 
     @Override
-    public StoreOperationalStatisticsComparisonVo getStatisticsComparisonNew(Integer storeId, String currentStartTime, String currentEndTime,
-                                                                            String previousStartTime, String previousEndTime) {
-
-        log.info("StoreOperationalStatisticsServiceImpl.getStatisticsComparison - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}",
+    public boolean getStatisticsComparisonNew(Integer storeId, String currentStartTime, String currentEndTime,
+                                             String previousStartTime, String previousEndTime) {
+        log.info("StoreOperationalStatisticsServiceImpl.getStatisticsComparisonNew - storeId={}, currentStartTime={}, currentEndTime={}, previousStartTime={}, previousEndTime={}",
                 storeId, currentStartTime, currentEndTime, previousStartTime, previousEndTime);
 
         // 参数校验
@@ -359,29 +358,11 @@ public class StoreOperationalStatisticsServiceImpl implements StoreOperationalSt
         comparison.setServiceQualityData(buildServiceQualityDataComparison(currentStatistics.getServiceQualityData(), previousStatistics.getServiceQualityData()));
         comparison.setPriceListRanking(buildPriceListRankingComparison(currentStatistics.getPriceListRanking(), previousStatistics.getPriceListRanking(), storeId));
 
-        // 当comparison中所有对比数据的current和previous都为0或为空时,不抛异常,返回 null(由 Controller 返回失败提示
+        // 当comparison中所有对比数据的current和previous都为0或为空时,返回 false(失败
         if (!hasComparisonData(comparison)) {
-            return null;
-        }
-
-        // 保存历史记录(不包含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 false;
         }
-
-        return comparison;
+        return true;
     }
 
     /**

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

@@ -113,6 +113,7 @@ public class CommonConstant {
     public static final String COMMENT_LIKE = "12";
     public static final String DYNAMIC_LIKE = "13";
     public static final String CLOCK_IN_LIKE = "14";
+    public static final String SECOND_HAND_LIKE = "15";
 
 
     /**

+ 2 - 2
alien-store/src/main/java/shop/alien/store/util/ai/AiReportReviewUtil.java

@@ -203,7 +203,7 @@ public class AiReportReviewUtil {
                 }
             }
 
-            // 发送通知给举报人
+/*            // 发送通知给举报人
             String reporterPhoneId = getPhoneId(v.getReportingUserType(), v.getReportingUserId());
             if (StringUtils.hasText(reporterPhoneId)) {
                 LifeNotice notice = new LifeNotice();
@@ -227,7 +227,7 @@ public class AiReportReviewUtil {
                 websocketVo.setText(JSONObject.from(notice).toJSONString());
                 webSocketProcess.sendMessage(reporterPhoneId, JSONObject.from(websocketVo).toJSONString());
                 log.info("AI审核结果通知已发送给举报人,reportId={}, phoneId={}, success={}", v.getId(), reporterPhoneId, success);
-            }
+            }*/
 
             // 发送通知给被举报人(仅审核通过且有被举报通知内容时)
             if (StringUtils.hasText(reportedMessage)) {

+ 227 - 0
alien-store/src/main/java/shop/alien/store/util/ai/ReceiptAuditUtil.java

@@ -0,0 +1,227 @@
+package shop.alien.store.util.ai;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 小票审核工具类
+ * 调用消费凭证核验接口审核评论图片是否为真实消费凭证(小票、支付记录等)
+ */
+@Slf4j
+@Component
+@RefreshScope
+@RequiredArgsConstructor
+public class ReceiptAuditUtil {
+
+    private final RestTemplate restTemplate;
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+
+    /**
+     * 小票审核接口地址
+     */
+    @Value("${ai.service.receipt-audit-url:http://124.93.18.180:9000/ai/multimodal-services/api/v1/consumption-proof/verify}")
+    private String receiptAuditUrl;
+
+    /**
+     * 审核结果类
+     */
+    public static class ReceiptAuditResult {
+        private boolean passed;
+        private String failureReason;
+        private boolean isValidProof;
+        private String proofType;
+
+        public ReceiptAuditResult(boolean passed, String failureReason) {
+            this.passed = passed;
+            this.failureReason = failureReason;
+        }
+
+        public ReceiptAuditResult(boolean passed, String failureReason, boolean isValidProof, String proofType) {
+            this.passed = passed;
+            this.failureReason = failureReason;
+            this.isValidProof = isValidProof;
+            this.proofType = proofType;
+        }
+
+        public boolean isPassed() {
+            return passed;
+        }
+
+        public String getFailureReason() {
+            return failureReason;
+        }
+
+        public boolean isValidProof() {
+            return isValidProof;
+        }
+
+        public String getProofType() {
+            return proofType;
+        }
+    }
+
+    /**
+     * 审核评论图片是否为真实消费凭证
+     *
+     * @param imageUrls 图片URL列表
+     * @param storeName 店铺名称(可选,用于核验凭证中的商户是否匹配)
+     * @return 审核结果
+     */
+    public ReceiptAuditResult auditReceipt(List<String> imageUrls, String storeName) {
+        log.info("开始小票审核:imageCount={}, storeName={}",
+                imageUrls != null ? imageUrls.size() : 0, storeName);
+
+        try {
+            // 如果没有图片,直接返回审核通过(不强制要求必须有小票)
+            if (imageUrls == null || imageUrls.isEmpty()) {
+                log.info("没有图片,跳过小票审核");
+                return new ReceiptAuditResult(true, null, false, null);
+            }
+
+            // 调用审核接口
+            return callReceiptAuditApi(imageUrls, storeName);
+
+        } catch (Exception e) {
+            log.error("小票审核异常", e);
+            // 审核异常时,为了不影响用户体验,默认通过(但记录日志)
+            return new ReceiptAuditResult(true, "小票审核异常,已通过", false, null);
+        }
+    }
+
+    /**
+     * 调用小票审核接口
+     *
+     * @param imageUrls 图片URL列表
+     * @param storeName 店铺名称
+     * @return 审核结果
+     */
+    private ReceiptAuditResult callReceiptAuditApi(List<String> imageUrls, String storeName) {
+        try {
+            // 获取访问令牌
+            String accessToken = aiAuthTokenUtil.getAccessToken();
+            if (!StringUtils.hasText(accessToken)) {
+                log.error("调用小票审核接口失败,获取accessToken失败");
+                return new ReceiptAuditResult(true, "获取访问令牌失败,已通过", false, null);
+            }
+            
+            // 构建请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.set("Authorization", "Bearer " + accessToken);
+
+            // 构建请求体
+            JSONObject requestBody = new JSONObject();
+            requestBody.put("image_urls", imageUrls);
+            if (StringUtils.hasText(storeName)) {
+                requestBody.put("store_name", storeName);
+            }
+
+            String requestBodyStr = requestBody.toJSONString();
+            if (requestBodyStr == null || requestBodyStr.isEmpty()) {
+                log.error("构建小票审核请求体失败");
+                return new ReceiptAuditResult(true, "构建请求体失败,已通过", false, null);
+            }
+            // 确保 requestBodyStr 不为 null(用于消除 IDE 警告)
+            String nonNullRequestBody = Objects.requireNonNull(requestBodyStr);
+            HttpEntity<String> requestEntity = new HttpEntity<>(nonNullRequestBody, headers);
+
+            log.info("调用小票审核接口:url={}, imageCount={}, storeName={}",
+                    receiptAuditUrl, imageUrls.size(), storeName);
+
+            // 发送请求
+            ResponseEntity<String> response = restTemplate.postForEntity(receiptAuditUrl, requestEntity, String.class);
+
+            if (response.getStatusCode() == HttpStatus.OK) {
+                String responseBody = response.getBody();
+                log.info("小票审核接口响应:{}", responseBody);
+
+                if (StringUtils.hasText(responseBody)) {
+                    JSONObject jsonResponse = JSONObject.parseObject(responseBody);
+                    return parseReceiptAuditResult(jsonResponse);
+                } else {
+                    log.error("小票审核接口返回空响应");
+                    return new ReceiptAuditResult(true, "小票审核接口返回空响应,已通过", false, null);
+                }
+            } else {
+                log.error("小票审核接口调用失败,状态码:{}", response.getStatusCode());
+                return new ReceiptAuditResult(true, "小票审核接口调用失败,已通过", false, null);
+            }
+
+        } catch (Exception e) {
+            log.error("调用小票审核接口异常", e);
+            return new ReceiptAuditResult(true, "小票审核接口调用异常,已通过", false, null);
+        }
+    }
+
+    /**
+     * 解析小票审核结果
+     *
+     * @param jsonResponse API响应JSON
+     * @return 审核结果
+     */
+    private ReceiptAuditResult parseReceiptAuditResult(JSONObject jsonResponse) {
+        try {
+            // API返回格式:
+            // {
+            //     "code": 200,
+            //     "message": "操作成功",
+            //     "data": {
+            //         "can_comment": true/false,
+            //         "is_valid_proof": true/false,
+            //         "proof_type": "小票"/"支付记录"等,
+            //         "reason": "核验结论说明",
+            //         "image_results": [...]
+            //     },
+            //     "timestamp": 1704067200.123
+            // }
+
+            Integer code = jsonResponse.getInteger("code");
+            if (code == null || code != 200) {
+                String message = jsonResponse.getString("message");
+                log.warn("小票审核接口返回错误:code={}, message={}", code, message);
+                // 接口返回错误时,为了不影响用户体验,默认通过
+                return new ReceiptAuditResult(true, "小票审核接口返回错误,已通过", false, null);
+            }
+
+            JSONObject data = jsonResponse.getJSONObject("data");
+            if (data == null) {
+                log.warn("小票审核接口返回数据为空");
+                return new ReceiptAuditResult(true, "小票审核接口返回数据为空,已通过", false, null);
+            }
+
+            Boolean canComment = data.getBoolean("can_comment");
+            Boolean isValidProof = data.getBoolean("is_valid_proof");
+            String proofType = data.getString("proof_type");
+            String reason = data.getString("reason");
+
+            if (canComment != null && canComment) {
+                // 允许评论,审核通过
+                log.info("小票审核通过:isValidProof={}, proofType={}, reason={}", isValidProof, proofType, reason);
+                return new ReceiptAuditResult(true, null, isValidProof != null && isValidProof, proofType);
+            } else {
+                // 不允许评论,审核失败
+                String failureReason = StringUtils.hasText(reason) ? reason : "图片不是有效的消费凭证";
+                log.warn("小票审核失败:reason={}", failureReason);
+                return new ReceiptAuditResult(false, failureReason, isValidProof != null && isValidProof, proofType);
+            }
+
+        } catch (Exception e) {
+            log.error("解析小票审核结果异常", e);
+            return new ReceiptAuditResult(true, "解析小票审核结果异常,已通过", false, null);
+        }
+    }
+}

+ 11 - 1
alien-util/src/main/java/shop/alien/util/common/VideoUtils.java

@@ -45,7 +45,17 @@ public class VideoUtils {
         }
 
         // 调用ffmpeg 执行截取命令,需要服务器中安装了ffmpeg并配置了环境变量
-        processBuilder.command(ffmpegPath, "-i", videoFilePath, "-ss", "00:00:01", "-vframes", "1", imgFilePath);
+//        processBuilder.command(ffmpegPath, "-i", videoFilePath, "-ss", "00:00:00", "-vframes", "1", imgFilePath);
+        // 核心修改1:调整-ss位置到-i前面,补充-y(覆盖文件)和-f image2(格式兼容)
+        processBuilder.command(
+                ffmpegPath,
+                "-ss", "00:00:00",    // 先定位到0秒(第一帧),再读视频,提升精准度
+                "-i", videoFilePath,  // 输入视频文件
+                "-vframes", "1",      // 只截取1帧
+                "-f", "image2",       // 显式指定图片格式,兼容更多视频类型
+                "-y",                 // 覆盖已存在的图片文件,避免进程阻塞
+                imgFilePath           // 输出图片路径
+        );
         try {
             Process process = processBuilder.start();
             // 获取流信息

+ 2 - 1
alien-util/src/main/java/shop/alien/util/common/constant/CommentSourceTypeEnum.java

@@ -4,7 +4,8 @@ package shop.alien.util.common.constant;
 public enum CommentSourceTypeEnum {
     STORE_COMMENT(1, "店铺评价"),
     DYNAMIC_COMMENT(2, "动态评论"),
-    CLOCK_IN_COMMENT(3, "打卡评论");
+    CLOCK_IN_COMMENT(3, "打卡评论"),
+    SECOND_HAND_COMMENT(4, "二手商品评论");
     private final Integer type;
     private final String info;
 

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff