88 Commits d3c143da63 ... 962a9e441b

Auteur SHA1 Bericht Datum
  penghao 962a9e441b Merge branch 'sit' into sit-OrderFood 2 maanden geleden
  panzhilin 626ed581ad Merge branch 'BugFix-panzhilin' into sit 2 maanden geleden
  panzhilin 731aa59191 BugFix:2889 演出(提测0130):可以选择不是这时间范围的星期几 2 maanden geleden
  fcw c08ab63145 feat(entity): 为LifeNotice实体添加标题字段映射 2 maanden geleden
  wuchen 154972fc6f 中台子账号代码修改 2 maanden geleden
  刘云鑫 59c5cc2632 bugfix:tags为空情况 2 maanden geleden
  刘云鑫 856bd69465 bugfix:接受参数改驼峰 2 maanden geleden
  刘云鑫 2019cf773e Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  刘云鑫 4bade1884d Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  penghao 811d7d91cc fix: 修改ai证书审核结果类型判断 2 maanden geleden
  刘云鑫 78609d28a3 bugfix:2892审核失败成功修改store_info状态 2 maanden geleden
  fcw 0191904c00 feat(entity): 为LifeNotice实体添加标题字段映射 2 maanden geleden
  zc d0dd4e9df7 修改演出列表显示下线演员问题 2 maanden geleden
  penghao 90a7ecf8df bugfix: 修复视频相册数量未计算问题 2 maanden geleden
  刘云鑫 4b47dad103 Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  刘云鑫 2cea933c61 feat:ai标签 2 maanden geleden
  fcw a9450883e6 fix(ai): 修复邮件反馈服务URL配置键名 2 maanden geleden
  qinxuyang 450db59e10 律师 申诉请求AI 2 maanden geleden
  刘云鑫 5b57d0da50 feat:ai标签 2 maanden geleden
  qinxuyang b13323446d 运营活动 StoreOperationalActivityDetailVo 修改 2 maanden geleden
  fcw a4f6920d5d feat(activity): 添加运营活动审核时间记录功能 2 maanden geleden
  zc 0c53dad0f3 修改活动状态bug 2 maanden geleden
  lutong 39b7a7d909 复制接口 2 maanden geleden
  fcw 98ee10e3b1 feat(store): 添加活动成果上传状态功能 2 maanden geleden
  zc 41287aa417 修改活动逻辑 2 maanden geleden
  fcw 8acd6a2d74 feat(activity): 添加运营活动报名管理和案例查询功能 2 maanden geleden
  zc 5125666186 修改活动逻辑 2 maanden geleden
  lutong 8343404c9c 开发完成中台 运营活动列表和 详情 2 maanden geleden
  zc 77feddce4f 代码回滚 2 maanden geleden
  zc cfae23a768 修改活动逻辑 2 maanden geleden
  zc ed8ef1c423 修改活动逻辑 2 maanden geleden
  zc 408f546970 新增活动逻辑 2 maanden geleden
  zc 58208fee1e 新增活动逻辑 2 maanden geleden
  zc bd7d564001 新增活动逻辑 2 maanden geleden
  fcw 576c899fc6 feat(activity): 添加活动结果上传功能 2 maanden geleden
  zc 9768f287eb 新增活动逻辑 2 maanden geleden
  fcw 22f259e043 feat(storePlatform): 添加运营活动结果类型和媒体支持 2 maanden geleden
  zc 5de9a4132f 新增运营活动报名表 2 maanden geleden
  fcw 8911299b56 feat(storePlatform): 添加店铺运营活动实体字段 2 maanden geleden
  penghao 9304da5162 fix: 相册视频功能修改 2 maanden geleden
  zhangchen 081f34a338 bug2866修改,当演出人员下线是,不显示演出人员的信息 2 maanden geleden
  fcw c6bbf5fb4d feat(email): 新增AI反馈邮件发送功能 2 maanden geleden
  fcw 1f1d9a988c fix(bad-review-appeal): 修复申诉通知消息标题设置顺序问题 2 maanden geleden
  李亚非 5f1765b787 Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  李亚非 2e7f13d444 fix:禅道2442,过滤下架和未审核的选项 2 maanden geleden
  刘云鑫 564ecb6b96 bugfix:黑名单过滤 2 maanden geleden
  liudongzhi 532cc175fe Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  liudongzhi 0f638053e9 增加判断主账号是正常店铺还是装修店铺的问题 2 maanden geleden
  刘云鑫 e21ab606de Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  刘云鑫 5fcc6017e0 bugfix:评分统计只查审核通过的 2 maanden geleden
  penghao 46177622b8 bugfix: 修改U宝语音识别ip端口 2 maanden geleden
  fcw d111d5c129 fix(job): 修复申诉通知标题显示问题 2 maanden geleden
  penghao 6a58d6d160 fix: 修改人员配置ai审核通过后逻辑,清空拒绝原因 2 maanden geleden
  penghao c7618ad792 Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  penghao ac5ce982a9 bugfix: 解开注释方法,修复动态评论未成功问题 2 maanden geleden
  fcw e1fc5d0618 feat(bad-review-appeal): 添加差评通知标题 2 maanden geleden
  qinxuyang 87f6071a14 bugfix:用户关注店铺 详情回显 2 maanden geleden
  penghao 3700dfd104 bugfix: 修改U宝语音识别失败问题 2 maanden geleden
  liudongzhi 3c5e37e900 删除子账号,踢出子账号登录 2 maanden geleden
  zc 610fc886f2 解决相册数量查询问题 2 maanden geleden
  刘云鑫 f6c6104ad1 bugfix:2792黑名单过滤 2 maanden geleden
  wuchen ae97c59478 Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  刘云鑫 02632801bf bugfix:2791搜索评价数量 2 maanden geleden
  wuchen cb74bd93e8 Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  刘云鑫 dfd6f0a9e1 bugfix:2792查询列表过滤黑名单 2 maanden geleden
  wuchen 23919c9dbf 商家端好友证券bug修复 2 maanden geleden
  刘云鑫 685162b6f6 Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  zc 74bec0f3d5 解决bug:2778 2 maanden geleden
  qinxuyang 40c05f50f2 子账号功能代码格式优化 2 maanden geleden
  qinxuyang a5ec06eb7f 子账号切换无痕登录 子账号切换列表 2 maanden geleden
  刘云鑫 b44b62ed7a bugfix:举报添加ai审核,举报管理中台添加人工复核 2 maanden geleden
  penghao c0815b22f2 bugfix: 商家端修改密码因数据库密码加密报原密码错误bug 2 maanden geleden
  lutong fb393f41cc Merge branch 'release_lutong_bug' into sit 2 maanden geleden
  lutong 2f5360ab45 更改沟通标识 2 maanden geleden
  lutong 41b21186f5 Merge branch 'release_lutong_bug' into sit 2 maanden geleden
  lutong e71b0506c7 打开聊天按钮 2 maanden geleden
  panzhilin c69c64b6cf Merge branch 'BugFix-panzhilin' into sit 2 maanden geleden
  panzhilin ff43252372 顾客评价(老板提测0128):查询条件:回复状态是“”未回复“”,列表显示的是全部的数据 2 maanden geleden
  wuchen 82173d8344 Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  fcw 6bc762456f fix(store): 修复评论申诉服务中的图片处理逻辑 2 maanden geleden
  wuchen 229478f476 Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  liudongzhi ff41cb4df4 修改提示语,按照产品要求重新改 2 maanden geleden
  liudongzhi 395bcd4d56 修改提示语,按照产品要求重新改 2 maanden geleden
  lutong 126c44f215 Merge branch 'release_lutong_bug' into sit 2 maanden geleden
  lutong 61226930b8 完善查询店铺是屏蔽黑名单 开发通过手机号查询店铺详情接口 2 maanden geleden
  wuchen 51170d254f Merge remote-tracking branch 'origin/sit' into sit 2 maanden geleden
  liudongzhi 0007f97d9d 修改提示语 2 maanden geleden
  wuchen 136f718ce6 中台页面bug修复 2 maanden geleden
96 gewijzigde bestanden met toevoegingen van 5618 en 436 verwijderingen
  1. 3 3
      alien-entity/src/main/java/shop/alien/entity/store/CommentAppeal.java
  2. 1 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeNotice.java
  3. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreImg.java
  4. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreOfficialAlbum.java
  5. 33 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreVideoSaveDto.java
  6. 3 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreInfoVo.java
  7. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreSubExcelVo.java
  8. 7 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/StoreUserVo.java
  9. 39 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java
  10. 75 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivityAchievement.java
  11. 93 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivitySignup.java
  12. 61 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseDetailVo.java
  13. 42 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseItemVo.java
  14. 66 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseVo.java
  15. 46 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementVo.java
  16. 26 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDTO.java
  17. 132 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDetailVo.java
  18. 79 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityMySignupVo.java
  19. 126 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityPlatformDetailVo.java
  20. 30 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupCheckVo.java
  21. 23 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupIdDTO.java
  22. 32 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupQueryDTO.java
  23. 70 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupVO.java
  24. 6 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityVO.java
  25. 1 1
      alien-entity/src/main/java/shop/alien/mapper/CommonRatingMapper.java
  26. 23 2
      alien-entity/src/main/java/shop/alien/mapper/StoreOfficialAlbumMapper.java
  27. 11 0
      alien-entity/src/main/java/shop/alien/mapper/StoreTrackEventMapper.java
  28. 7 0
      alien-entity/src/main/java/shop/alien/mapper/SubAccountStoreMapper.java
  29. 68 0
      alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivityAchievementMapper.java
  30. 81 0
      alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivitySignupMapper.java
  31. 74 7
      alien-entity/src/main/resources/mapper/SubAccountStoreMapper.xml
  32. 157 0
      alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivityAchievementMapper.xml
  33. 124 0
      alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivitySignupMapper.xml
  34. 8 4
      alien-gateway/src/main/java/shop/alien/gateway/controller/StoreUserController.java
  35. 1 1
      alien-gateway/src/main/java/shop/alien/gateway/service/StoreUserService.java
  36. 104 105
      alien-gateway/src/main/java/shop/alien/gateway/service/impl/StoreUserServiceImpl.java
  37. 12 1
      alien-job/src/main/java/shop/alien/job/store/BadReviewAppealJob.java
  38. 1 1
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/AiAutoReview.java
  39. 18 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java
  40. 111 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivitySignupController.java
  41. 2 5
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StorePlatformUserRoleController.java
  42. 9 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/OperationalActivityService.java
  43. 51 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/OperationalActivitySignupService.java
  44. 2 1
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/StorePlatformUserRoleService.java
  45. 174 7
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java
  46. 217 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivitySignupServiceImpl.java
  47. 2 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformLoginServiceImpl.java
  48. 47 13
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformUserRoleServiceImpl.java
  49. 49 13
      alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java
  50. 218 0
      alien-store/src/main/java/shop/alien/store/controller/AiTagsController.java
  51. 106 0
      alien-store/src/main/java/shop/alien/store/controller/FeedbackEmailController.java
  52. 11 6
      alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponStoreFriendController.java
  53. 1 1
      alien-store/src/main/java/shop/alien/store/controller/OperationalActivityController.java
  54. 15 1
      alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java
  55. 79 13
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  56. 13 2
      alien-store/src/main/java/shop/alien/store/controller/StoreOfficialAlbumController.java
  57. 301 0
      alien-store/src/main/java/shop/alien/store/controller/StoreOperationalActivityController.java
  58. 1 0
      alien-store/src/main/java/shop/alien/store/controller/StoreUserController.java
  59. 12 0
      alien-store/src/main/java/shop/alien/store/controller/StoreVideoController.java
  60. 23 0
      alien-store/src/main/java/shop/alien/store/controller/TrackEventController.java
  61. 31 0
      alien-store/src/main/java/shop/alien/store/dto/StoreOperationalActivityAchievementDto.java
  62. 32 0
      alien-store/src/main/java/shop/alien/store/dto/StoreOperationalActivityCaseQueryDto.java
  63. 28 0
      alien-store/src/main/java/shop/alien/store/dto/StoreOperationalActivitySignupDto.java
  64. 3 0
      alien-store/src/main/java/shop/alien/store/service/CommonRatingService.java
  65. 8 5
      alien-store/src/main/java/shop/alien/store/service/OperationalActivityService.java
  66. 7 0
      alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java
  67. 77 0
      alien-store/src/main/java/shop/alien/store/service/StoreOperationalActivityAchievementService.java
  68. 84 0
      alien-store/src/main/java/shop/alien/store/service/StoreOperationalActivityService.java
  69. 1 1
      alien-store/src/main/java/shop/alien/store/service/StoreUserService.java
  70. 9 0
      alien-store/src/main/java/shop/alien/store/service/StoreVideoService.java
  71. 8 0
      alien-store/src/main/java/shop/alien/store/service/TrackEventService.java
  72. 46 0
      alien-store/src/main/java/shop/alien/store/service/impl/BarPerformanceServiceImpl.java
  73. 2 1
      alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java
  74. 13 8
      alien-store/src/main/java/shop/alien/store/service/impl/LicenseAuditAsyncService.java
  75. 37 37
      alien-store/src/main/java/shop/alien/store/service/impl/LifeMessageServiceImpl.java
  76. 7 2
      alien-store/src/main/java/shop/alien/store/service/impl/LifeUserViolationServiceImpl.java
  77. 41 8
      alien-store/src/main/java/shop/alien/store/service/impl/OperationalActivityServiceImpl.java
  78. 196 6
      alien-store/src/main/java/shop/alien/store/service/impl/PerformanceListServiceImpl.java
  79. 28 24
      alien-store/src/main/java/shop/alien/store/service/impl/StoreCommentAppealServiceImpl.java
  80. 19 7
      alien-store/src/main/java/shop/alien/store/service/impl/StoreCommentServiceImpl.java
  81. 3 1
      alien-store/src/main/java/shop/alien/store/service/impl/StoreCuisineServiceImpl.java
  82. 21 17
      alien-store/src/main/java/shop/alien/store/service/impl/StoreImgServiceImpl.java
  83. 107 10
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  84. 143 7
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOfficialAlbumServiceImpl.java
  85. 179 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityAchievementServiceImpl.java
  86. 498 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityServiceImpl.java
  87. 214 37
      alien-store/src/main/java/shop/alien/store/service/impl/StorePlatformMenuServiceImpl.java
  88. 19 9
      alien-store/src/main/java/shop/alien/store/service/impl/StoreRenovationRequirementServiceImpl.java
  89. 17 14
      alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffAuditAsyncService.java
  90. 16 8
      alien-store/src/main/java/shop/alien/store/service/impl/StoreUserServiceImpl.java
  91. 250 40
      alien-store/src/main/java/shop/alien/store/service/impl/StoreVideoServiceImpl.java
  92. 27 1
      alien-store/src/main/java/shop/alien/store/service/impl/SubAccountStoreServiceImpl.java
  93. 17 0
      alien-store/src/main/java/shop/alien/store/service/impl/TrackEventServiceImpl.java
  94. 4 3
      alien-store/src/main/java/shop/alien/store/util/ai/AiFeedbackAssignUtils.java
  95. 219 0
      alien-store/src/main/java/shop/alien/store/util/ai/AiFeedbackEmailUtil.java
  96. 71 0
      alien-store/src/main/java/shop/alien/store/util/ai/AiReportReviewUtil.java

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

@@ -97,8 +97,8 @@ public class CommentAppeal extends Model<CommentAppeal> {
     @TableField("order_id")
     private Integer orderId;
 
-//    @ApiModelProperty(value = "评价图片")
-//    @TableField("review_images")
-//    private String reviewImages;
+    @ApiModelProperty(value = "评价图片")
+    @TableField("review_images")
+    private String reviewImages;
 }
 

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

@@ -30,6 +30,7 @@ public class LifeNotice {
     private Integer businessId;
 
     @ApiModelProperty(value = "标题")
+    @TableField(value = "title", insertStrategy = FieldStrategy.IGNORED)
     private String title;
 
     @ApiModelProperty(value = "公告内容")

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

@@ -84,4 +84,8 @@ public class StoreImg extends Model<StoreImg> {
     @ApiModelProperty(value = "相册名称")
     @TableField(exist = false)
     private String albumName;
+
+    @ApiModelProperty(value = "活动类型:COMMENT-评论有礼,MARKETING-营销活动")
+    @TableField(exist = false)
+    private String activityType;
 }

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

@@ -32,6 +32,10 @@ public class StoreOfficialAlbum extends Model<StoreOfficialAlbum> {
     @ApiModelProperty(value = "相册名称")
     @TableField("album_name")
     private String albumName;
+    
+    @ApiModelProperty(value = "是否固定 (0-否, 1-是)")
+    @TableField("is_fixed")
+    private Integer isFixed;
 
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")

+ 33 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreVideoSaveDto.java

@@ -0,0 +1,33 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 视频保存DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreVideoSaveDto对象", description = "视频保存DTO")
+public class StoreVideoSaveDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "门店ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "相册ID(业务ID)")
+    private Integer businessId;
+
+    @ApiModelProperty(value = "视频URL列表", required = true)
+    private List<String> videoUrls;
+
+    @ApiModelProperty(value = "视频ID列表")
+    private List<Integer> videoIds;
+
+}

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

@@ -148,6 +148,9 @@ public class StoreInfoVo extends StoreInfo {
     @ApiModelProperty(value = "是否收藏")
     private Integer collection;
 
+    @ApiModelProperty(value = "是否关注(1:已关注,0:未关注)")
+    private Integer isFollowed;
+
     @ApiModelProperty(value = "优惠券列表")
     private List<LifeCouponVo> couponList;
 

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

@@ -15,7 +15,7 @@ public class StoreSubExcelVo {
     @ApiModelProperty(value = "序号")
     private Integer serialNumber;
 
-    @ApiModelProperty(value = "主键")
+    @ApiModelProperty(value = "账号ID")
     @ExcelHeader(value = "账号ID")
     private Integer id;
 

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

@@ -62,4 +62,11 @@ public class StoreUserVo extends StoreUser {
     @ApiModelProperty(value = "角色名称(当前账号在目标门店的角色名称)")
     private String roleName;
 
+    @ApiModelProperty(value = "是否有子账号(主账号且有子账号时为true)")
+    private boolean hasSubAccount;
+
+
+    @ApiModelProperty(value = "门店类型(0-其他,1-装修公司)")
+    private Integer storeTickets;
+
 }

+ 39 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java

@@ -97,5 +97,44 @@ public class StoreOperationalActivity {
     @ApiModelProperty(value = "拒绝原因")
     @TableField("approval_comments")
     private String approvalComments;
+
+    @ApiModelProperty(value = "活动类型:COMMENT-评论有礼, MARKETING-营销活动")
+    @TableField("activity_type")
+    private String activityType;
+
+    @ApiModelProperty(value = "报名开始时间")
+    @TableField("signup_start_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupStartTime;
+
+    @ApiModelProperty(value = "报名结束时间")
+    @TableField("signup_end_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupEndTime;
+
+    @ApiModelProperty(value = "活动限制人数")
+    @TableField("activity_limit_people")
+    private Integer activityLimitPeople;
+
+    @ApiModelProperty(value = "活动详情")
+    @TableField("activity_details")
+    private String activityDetails;
+
+    @ApiModelProperty(value = "结果类型:0-文字, 1-图片")
+    @TableField("result_type")
+    private Integer resultType;
+
+    @ApiModelProperty(value = "结果文字")
+    @TableField("result_text")
+    private String resultText;
+
+    @ApiModelProperty(value = "结果图片地址")
+    @TableField("result_media_url")
+    private String resultMediaUrl;
+
+    @ApiModelProperty(value = "审核时间")
+    @TableField("audit_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditTime;
 }
 

+ 75 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivityAchievement.java

@@ -0,0 +1,75 @@
+package shop.alien.entity.storePlatform;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 运营活动成果表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_operational_activity_achievement")
+@ApiModel(value = "StoreOperationalActivityAchievement对象", description = "运营活动成果表")
+public class StoreOperationalActivityAchievement {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "活动ID")
+    @TableField("activity_id")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "用户ID")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名ID")
+    @TableField("signup_id")
+    private Integer signupId;
+
+    @ApiModelProperty(value = "成果描述")
+    @TableField("achievement_desc")
+    private String achievementDesc;
+
+    @ApiModelProperty(value = "图片/视频URL(逗号分隔)")
+    @TableField("media_urls")
+    private String mediaUrls;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}

+ 93 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivitySignup.java

@@ -0,0 +1,93 @@
+package shop.alien.entity.storePlatform;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 运营活动报名表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_operational_activity_signup")
+@ApiModel(value = "StoreOperationalActivitySignup对象", description = "运营活动报名表")
+public class StoreOperationalActivitySignup {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "活动ID")
+    @TableField("activity_id")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "商户ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "用户ID")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名人姓名")
+    @TableField("user_name")
+    private String userName;
+
+    @ApiModelProperty(value = "手机号")
+    @TableField("phone")
+    private String phone;
+
+    @ApiModelProperty(value = "报名状态:0-待审核,1-拒绝,2-通过")
+    @TableField("status")
+    private Integer status;
+
+    @ApiModelProperty(value = "审核拒绝原因")
+    @TableField("reject_reason")
+    private String rejectReason;
+
+    @ApiModelProperty(value = "审核时间")
+    @TableField("audit_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditTime;
+
+    @ApiModelProperty(value = "报名时间")
+    @TableField("signup_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupTime;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}

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

@@ -0,0 +1,61 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 运营活动案例详情
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivityAchievementCaseDetailVo对象", description = "运营活动案例详情")
+public class StoreOperationalActivityAchievementCaseDetailVo {
+
+    @ApiModelProperty(value = "活动ID")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "活动名称")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "活动结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "用户姓名")
+    private String userName;
+
+    @ApiModelProperty(value = "联系方式(手机号)")
+    private String phone;
+
+    @ApiModelProperty(value = "用户头像")
+    private String userImage;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "上传情况:0-未上传, 1-已上传")
+    private Integer hasResult;
+
+    @ApiModelProperty(value = "成果列表")
+    private List<StoreOperationalActivityAchievementCaseItemVo> achievementList;
+}

+ 42 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementCaseItemVo.java

@@ -0,0 +1,42 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 运营活动案例详情-成果条目
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivityAchievementCaseItemVo对象", description = "运营活动案例详情-成果条目")
+public class StoreOperationalActivityAchievementCaseItemVo {
+
+    @ApiModelProperty(value = "成果ID")
+    private Integer achievementId;
+
+    @ApiModelProperty(value = "成果描述")
+    private String achievementDesc;
+
+    @ApiModelProperty(value = "图片/视频URL(逗号分隔)")
+    private String mediaUrls;
+
+    @ApiModelProperty(value = "图片/视频URL列表")
+    private List<String> mediaUrlList;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+}

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

@@ -0,0 +1,66 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 运营活动成果案例列表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreOperationalActivityAchievementCaseVo对象", description = "运营活动成果案例列表")
+public class StoreOperationalActivityAchievementCaseVo {
+
+    @ApiModelProperty(value = "成果ID")
+    private Integer achievementId;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "活动ID")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "活动名称")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "活动结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "活动状态")
+    private Integer activityStatus;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "店铺用户头像")
+    private String storeUserHeadImg;
+
+    @ApiModelProperty(value = "店铺用户昵称")
+    private String storeUserNickName;
+
+    @ApiModelProperty(value = "联系方式(手机号)")
+    private String phone;
+
+    @ApiModelProperty(value = "上传情况:0-未上传, 1-已上传")
+    private Integer hasResult;
+
+    @ApiModelProperty(value = "成果首图")
+    private String firstMediaUrl;
+
+    @ApiModelProperty(value = "更新时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+}

+ 46 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementVo.java

@@ -0,0 +1,46 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+import java.util.Date;
+
+/**
+ * 运营活动成果返回对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivityAchievementVo", description = "运营活动成果返回对象")
+public class StoreOperationalActivityAchievementVo {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "活动ID")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名ID")
+    private Integer signupId;
+
+    @ApiModelProperty(value = "成果描述")
+    private String achievementDesc;
+
+    @ApiModelProperty(value = "图片/视频URL(逗号分隔)")
+    private String mediaUrls;
+
+    @ApiModelProperty(value = "图片/视频URL列表")
+    private List<String> mediaUrlList;
+
+    @ApiModelProperty(value = "创建时间")
+    private Date createdTime;
+}

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

@@ -82,5 +82,31 @@ public class StoreOperationalActivityDTO {
 
     @ApiModelProperty(value = "AI审核的输入参数")
     private JsonNode auditParam;
+
+    @ApiModelProperty(value = "活动类型:COMMENT-评论有礼, MARKETING-营销活动")
+    private String activityType;
+
+    @ApiModelProperty(value = "报名开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date signupStartTime;
+
+    @ApiModelProperty(value = "报名结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date signupEndTime;
+
+    @ApiModelProperty(value = "活动限制人数")
+    private Integer activityLimitPeople;
+
+    @ApiModelProperty(value = "活动详情")
+    private String activityDetails;
+
+    @ApiModelProperty(value = "结果类型:0-文字, 1-图片")
+    private Integer resultType;
+
+    @ApiModelProperty(value = "结果文字")
+    private String resultText;
+
+    @ApiModelProperty(value = "结果图片地址")
+    private String resultMediaUrl;
 }
 

+ 132 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDetailVo.java

@@ -0,0 +1,132 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 运营活动详情返回对象(用户端)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreOperationalActivityDetailVo", description = "运营活动详情返回对象")
+public class StoreOperationalActivityDetailVo {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "商户ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "活动名称")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动宣传图URL")
+    private String promotionalImage;
+
+    @ApiModelProperty(value = "活动开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "活动结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "用户可参与次数,0表示不限制")
+    private Integer participationLimit;
+
+    @ApiModelProperty(value = "活动规则")
+    private String activityRule;
+
+    @ApiModelProperty(value = "奖励类型")
+    private String rewardType;
+
+    @ApiModelProperty(value = "优惠券ID")
+    private Integer couponId;
+
+    @ApiModelProperty(value = "优惠券发放数量")
+    private Integer couponQuantity;
+
+    @ApiModelProperty(value = "状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "拒绝原因")
+    private String approvalComments;
+
+    @ApiModelProperty(value = "活动类型")
+    private String activityType;
+
+    @ApiModelProperty(value = "报名开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupStartTime;
+
+    @ApiModelProperty(value = "报名结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupEndTime;
+
+    @ApiModelProperty(value = "活动限制人数")
+    private Integer activityLimitPeople;
+
+    @ApiModelProperty(value = "活动详情")
+    private String activityDetails;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "活动状态名称")
+    private String statusName;
+
+    @ApiModelProperty(value = "优惠券名称")
+    private String couponName;
+
+    @ApiModelProperty(value = "商户名称")
+    private String storeName;
+
+    @ApiModelProperty(value = "活动标题图片URL")
+    private String activityTitleImgUrl;
+
+    @ApiModelProperty(value = "活动详情图片URL")
+    private String activityDetailImgUrl;
+
+    @ApiModelProperty(value = "活动详情图片URL列表")
+    private List<String> activityDetailImgUrlList;
+
+    @ApiModelProperty(value = "当前报名人数")
+    private Integer currentSignupCount;
+
+    @ApiModelProperty(value = "当前通过人数")
+    private Integer currentApprovedCount;
+
+    @ApiModelProperty(value = "是否已报名")
+    private Boolean signedUp;
+
+    @ApiModelProperty(value = "是否在报名时间内")
+    private Boolean inSignupTime;
+
+    @ApiModelProperty(value = "详情按钮状态:0-活动已结束,1-报名已截止,2-等待活动开始,3-报名成功,4-已报名,5-立即报名")
+    private Integer detailStatus;
+
+    @ApiModelProperty(value = "结果类型:0-文字, 1-图片")
+    private Integer resultType;
+
+    @ApiModelProperty(value = "结果文字")
+    private String resultText;
+
+    @ApiModelProperty(value = "结果图片地址")
+    private String resultMediaUrl;
+
+    @ApiModelProperty(value = "审核时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditTime;
+
+}

+ 79 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityMySignupVo.java

@@ -0,0 +1,79 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 我的报名列表返回对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivityMySignupVo", description = "我的报名列表返回对象")
+public class StoreOperationalActivityMySignupVo {
+
+    @ApiModelProperty(value = "活动ID")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "报名ID")
+    private Integer signupId;
+
+    @ApiModelProperty(value = "商户ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "活动名称")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动宣传图URL")
+    private String promotionalImage;
+
+    @ApiModelProperty(value = "活动标题图URL")
+    private String activityTitleImgUrl;
+
+    @ApiModelProperty(value = "店铺用户头像")
+    private String storeUserHeadImg;
+
+    @ApiModelProperty(value = "店铺用户昵称")
+    private String storeUserNickName;
+
+    @ApiModelProperty(value = "活动开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "活动结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "活动状态")
+    private Integer activityStatus;
+
+    @ApiModelProperty(value = "报名状态")
+    private Integer signupStatus;
+
+    @JsonIgnore
+    private Integer signupAuditStatus;
+
+    @ApiModelProperty(value = "活动开始状态(0-已开始,1-已结束)")
+    private Integer activityStartStatus;
+
+    @ApiModelProperty(value = "是否有成果")
+    private Integer hasAchievement;
+
+    @ApiModelProperty(value = "列表状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "报名时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupTime;
+
+    @ApiModelProperty(value = "状态文案")
+    private String statusLabel;
+}

+ 126 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityPlatformDetailVo.java

@@ -0,0 +1,126 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 运营活动详情返回对象(用户端)
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivityDetailVo", description = "运营活动详情返回对象")
+public class StoreOperationalActivityPlatformDetailVo {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "商户ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "活动名称")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动宣传图URL")
+    private String promotionalImage;
+
+    @ApiModelProperty(value = "活动开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date startTime;
+
+    @ApiModelProperty(value = "活动结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date endTime;
+
+    @ApiModelProperty(value = "用户可参与次数,0表示不限制")
+    private Integer participationLimit;
+
+    @ApiModelProperty(value = "活动规则")
+    private String activityRule;
+
+    @ApiModelProperty(value = "奖励类型")
+    private String rewardType;
+
+    @ApiModelProperty(value = "优惠券ID")
+    private Integer couponId;
+
+    @ApiModelProperty(value = "优惠券发放数量")
+    private Integer couponQuantity;
+
+    @ApiModelProperty(value = "状态")
+    private Integer status;
+
+    @ApiModelProperty(value = "拒绝原因")
+    private String approvalComments;
+
+    @ApiModelProperty(value = "活动类型")
+    private String activityType;
+
+    @ApiModelProperty(value = "报名开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupStartTime;
+
+    @ApiModelProperty(value = "报名结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupEndTime;
+
+    @ApiModelProperty(value = "活动限制人数")
+    private Integer activityLimitPeople;
+
+    @ApiModelProperty(value = "活动详情")
+    private String activityDetails;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "活动状态名称")
+    private String statusName;
+
+    @ApiModelProperty(value = "优惠券名称")
+    private String couponName;
+
+    @ApiModelProperty(value = "商户名称")
+    private String storeName;
+
+    @ApiModelProperty(value = "活动标题图片URL")
+    private String activityTitleImgUrl;
+
+    @ApiModelProperty(value = "活动详情图片URL")
+    private String activityDetailImgUrl;
+
+    @ApiModelProperty(value = "活动详情图片URL列表")
+    private List<String> activityDetailImgUrlList;
+
+    @ApiModelProperty(value = "当前报名人数")
+    private Integer currentSignupCount;
+
+    @ApiModelProperty(value = "当前通过人数")
+    private Integer currentApprovedCount;
+
+    @ApiModelProperty(value = "是否已报名")
+    private Boolean signedUp;
+
+    @ApiModelProperty(value = "是否在报名时间内")
+    private Boolean inSignupTime;
+
+    @ApiModelProperty(value = "详情按钮状态:0-活动已结束,1-报名已截止,2-等待活动开始,3-报名成功,4-已报名,5-立即报名")
+    private Integer detailStatus;
+
+    @ApiModelProperty(value = "结果类型:0-文字, 1-图片")
+    private Integer resultType;
+
+    @ApiModelProperty(value = "结果文字")
+    private String resultText;
+
+    @ApiModelProperty(value = "结果图片地址")
+    private String resultMediaUrl;
+}

+ 30 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupCheckVo.java

@@ -0,0 +1,30 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 运营活动报名校验结果
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivitySignupCheckVo", description = "运营活动报名校验结果")
+public class StoreOperationalActivitySignupCheckVo {
+
+    @ApiModelProperty(value = "是否已满")
+    private boolean full;
+
+    @ApiModelProperty(value = "是否已成功报名")
+    private boolean signedUp;
+
+    @ApiModelProperty(value = "当前报名人数")
+    private Integer currentSignupCount;
+
+    @ApiModelProperty(value = "活动限制人数")
+    private Integer activityLimitPeople;
+}

+ 23 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupIdDTO.java

@@ -0,0 +1,23 @@
+package shop.alien.entity.storePlatform.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 运营活动报名ID DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreOperationalActivitySignupIdDTO", description = "运营活动报名ID DTO")
+public class StoreOperationalActivitySignupIdDTO {
+
+    @ApiModelProperty(value = "报名ID", required = true)
+    private Integer id;
+
+    @ApiModelProperty(value = "审核拒绝原因(审核拒绝时必填)")
+    private String rejectReason;
+}
+

+ 32 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupQueryDTO.java

@@ -0,0 +1,32 @@
+package shop.alien.entity.storePlatform.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 运营活动报名查询DTO
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreOperationalActivitySignupQueryDTO", description = "运营活动报名查询DTO")
+public class StoreOperationalActivitySignupQueryDTO {
+
+    @ApiModelProperty(value = "商户ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "报名状态:0-待审核,1-拒绝,2-通过")
+    private Integer status;
+
+    @ApiModelProperty(value = "活动名称(模糊查询)")
+    private String activityName;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer pageNum;
+
+    @ApiModelProperty(value = "每页大小", example = "10")
+    private Integer pageSize;
+}
+

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

@@ -0,0 +1,70 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 运营活动报名列表返回对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivitySignupVO", description = "运营活动报名列表返回对象")
+public class StoreOperationalActivitySignupVO {
+
+    @ApiModelProperty(value = "主键ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "活动ID")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "商户ID")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名人姓名")
+    private String userName;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+
+    @ApiModelProperty(value = "所属活动")
+    private String activityName;
+
+    @ApiModelProperty(value = "活动类型:COMMENT-评论有礼, MARKETING-营销活动")
+    private String activityType;
+
+    @ApiModelProperty(value = "报名状态:0-待审核,1-拒绝,2-通过")
+    private Integer status;
+
+    @ApiModelProperty(value = "报名状态文字:待审核、拒绝、通过")
+    private String statusText;
+
+    @ApiModelProperty(value = "审核拒绝原因")
+    private String rejectReason;
+
+    @ApiModelProperty(value = "审核时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date auditTime;
+
+    @ApiModelProperty(value = "报名时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date signupTime;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+}
+

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

@@ -54,5 +54,11 @@ public class StoreOperationalActivityVO extends StoreOperationalActivity {
 
     @ApiModelProperty(value = "活动详情图片")
     private String activityDetailImgUrl;
+
+    @ApiModelProperty(value = "活动详情图片URL列表")
+    private java.util.List<String> activityDetailImgUrlList;
+
+    @ApiModelProperty(value = "活动类型名称")
+    private String activityTypeName;
 }
 

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

@@ -1,6 +1,5 @@
 package shop.alien.mapper;
 
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -40,6 +39,7 @@ public interface CommonRatingMapper extends BaseMapper<CommonRating> {
             "FROM `common_rating` " +  // FROM 后加空格,避免和表名拼接成 FROM`common_rating`
             "WHERE business_type = #{businessType} " +
             "AND delete_flag = 0 " +
+            "AND audit_status = 1 " +
             "AND business_id = #{businessId}")
     StoreInfoScoreVo getCommentCountAndScoreInfo(@Param("businessType")Integer businessType,@Param("businessId")Integer businessId);
 

+ 23 - 2
alien-entity/src/main/java/shop/alien/mapper/StoreOfficialAlbumMapper.java

@@ -23,10 +23,31 @@ public interface StoreOfficialAlbumMapper extends BaseMapper<StoreOfficialAlbum>
      */
     //"图片类型, 0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉, 10:商家头像, 11:店铺轮播图"
     @Select("SELECT a.*, b.img_url FROM store_official_album a LEFT JOIN store_img b ON b.id = " +
-            "(SELECT b2.id FROM store_img b2 WHERE b2.business_id = a.id and b2.img_type in ( '2','4') " +
+            "(SELECT b2.id FROM store_img b2 WHERE b2.business_id = a.id and b2.img_type in ( '2','4') and b2.delete_flag = 0 " +
             "order by b2.img_sort LIMIT 1) where a.delete_flag = '0' and  a.store_id = #{storeId}")
     List<StoreOfficialAlbumVo> getStoreOfficialAlbumList(@Param("storeId") Integer storeId);
 
-    @Select("select si.business_id as id ,count(*) as img_count from store_img si where si.delete_flag = '0' and si.store_id = #{storeId} group by si.business_id ")
+    @Select("select " +
+            " soa.id," +
+            " CASE " +
+            "     WHEN soa.album_name = '视频' THEN " +
+            "         (SELECT COUNT(*) " +
+            "          FROM store_video sv " +
+            "          WHERE sv.business_id = soa.id " +
+            "            AND sv.delete_flag = 0 " +
+            "            AND sv.store_id = #{storeId}) " +
+            "     ELSE " +
+            "         (SELECT COUNT(*) " +
+            "          FROM store_img b2 " +
+            "          WHERE b2.business_id = soa.id " +
+            "            AND b2.img_type IN ('2', '4') " +
+            "            AND b2.delete_flag = 0 " +
+            "            AND b2.store_id = #{storeId}) " +
+            " END as imgCount " +
+            " from" +
+            " store_official_album soa\n" +
+            " where " +
+            " soa.store_id = #{storeId} " +
+            " and soa.delete_flag = 0")
     List<StoreOfficialAlbum> getStoreOfficialAlbumImgCount(@Param("storeId") Integer storeId);
 }

+ 11 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreTrackEventMapper.java

@@ -137,4 +137,15 @@ public interface StoreTrackEventMapper extends BaseMapper<StoreTrackEvent> {
             "GROUP BY event_type")
     List<Map<String, Object>> calculateServiceStatistics(@Param("storeId") Integer storeId,
                                                           @Param("endDate") Date endDate);
+    
+    /**
+     * 统计店铺当前总浏览量(所有历史数据)
+     */
+    @Select("SELECT COUNT(*) as viewCount " +
+            "FROM store_track_event " +
+            "WHERE store_id = #{storeId} " +
+            "AND delete_flag = 0 " +
+            "AND event_category = 'TRAFFIC' " +
+            "AND event_type = 'VIEW'")
+    Long countStoreViewCount(@Param("storeId") Integer storeId);
 }

+ 7 - 0
alien-entity/src/main/java/shop/alien/mapper/SubAccountStoreMapper.java

@@ -21,4 +21,11 @@ public interface SubAccountStoreMapper extends BaseMapper<Object> {
      * @return 门店列表
      */
     List<SubAccountStoreListVo> selectSubAccountStoreListByUserId(@Param("userId") Integer userId);
+
+    /**
+     * 根据用户ID查询子账号关联的门店列表 (无主账号情况)
+     * @param userId 用户ID(store_user.id)
+     * @return 门店列表
+     */
+    List<SubAccountStoreListVo> selectSubAccountStoreListByUserIdTwo(@Param("userId") Integer userId);
 }

+ 68 - 0
alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivityAchievementMapper.java

@@ -0,0 +1,68 @@
+package shop.alien.mapper.storePlantform;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.storePlatform.StoreOperationalActivityAchievement;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseDetailVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseItemVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseVo;
+
+import java.util.List;
+
+/**
+ * 运营活动成果 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StoreOperationalActivityAchievementMapper extends BaseMapper<StoreOperationalActivityAchievement> {
+
+    /**
+     * 分页查询案例列表(同一用户同一活动最新成果)。
+     *
+     * @param page           分页
+     * @param activityStatus 活动状态
+     * @return 分页列表
+     */
+    IPage<StoreOperationalActivityAchievementCaseVo> selectCasePage(
+            IPage<?> page,
+            @Param("activityStatus") Integer activityStatus);
+
+    /**
+     * 案例详情头部信息
+     *
+     * @param activityId 活动ID
+     * @param userId 用户ID
+     * @return 详情
+     */
+    StoreOperationalActivityAchievementCaseDetailVo selectCaseHeader(@Param("activityId") Integer activityId,
+                                                                     @Param("userId") Integer userId);
+
+    /**
+     * 案例详情成果列表
+     *
+     * @param activityId 活动ID
+     * @param userId 用户ID
+     * @return 成果列表
+     */
+    List<StoreOperationalActivityAchievementCaseItemVo> selectCaseItems(@Param("activityId") Integer activityId,
+                                                                        @Param("userId") Integer userId);
+
+    /**
+     * 商家端分页查询案例列表(按商户ID、上传情况、活动名称筛选)
+     *
+     * @param page 分页
+     * @param storeId 商户ID
+     * @param hasResult 上传情况:0-未上传, 1-已上传
+     * @param activityName 活动名称(模糊查询)
+     * @return 分页列表
+     */
+    IPage<StoreOperationalActivityAchievementCaseVo> selectStoreCasePage(
+            IPage<?> page,
+            @Param("storeId") Integer storeId,
+            @Param("hasResult") Integer hasResult,
+            @Param("activityName") String activityName);
+}

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

@@ -0,0 +1,81 @@
+package shop.alien.mapper.storePlantform;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.storePlatform.StoreOperationalActivitySignup;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityMySignupVo;
+
+import java.util.List;
+
+/**
+ * 运营活动报名 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StoreOperationalActivitySignupMapper extends BaseMapper<StoreOperationalActivitySignup> {
+
+    /**
+     * 根据活动ID查询报名列表(仅未删除)。
+     *
+     * @param activityId 活动ID
+     * @return 报名列表
+     */
+    List<StoreOperationalActivitySignup> selectByActivityId(@Param("activityId") Integer activityId);
+
+    /**
+     * 统计活动当前报名人数(待审核+通过,未删除)。
+     *
+     * @param activityId 活动ID
+     * @return 报名人数
+     */
+    Integer countSignupByActivityId(@Param("activityId") Integer activityId);
+
+    /**
+     * 统计活动当前通过人数(通过状态,未删除)。
+     *
+     * @param activityId 活动ID
+     * @return 通过人数
+     */
+    Integer countApprovedByActivityId(@Param("activityId") Integer activityId);
+
+    /**
+     * 统计用户是否已通过报名。
+     *
+     * @param activityId 活动ID
+     * @param userId     用户ID
+     * @return 报名数量
+     */
+    Integer countApprovedByActivityAndUser(@Param("activityId") Integer activityId,
+                                           @Param("userId") Integer userId);
+
+    /**
+     * 统计用户是否已报名(待审核/通过)。
+     *
+     * @param activityId 活动ID
+     * @param userId     用户ID
+     * @return 报名数量
+     */
+    Integer countSignedUpByActivityAndUser(@Param("activityId") Integer activityId,
+                                           @Param("userId") Integer userId);
+
+    /**
+     * 获取用户最新报名状态。
+     *
+     * @param activityId 活动ID
+     * @param userId     用户ID
+     * @return 报名状态
+     */
+    Integer selectLatestSignupStatus(@Param("activityId") Integer activityId,
+                                     @Param("userId") Integer userId);
+
+    /**
+     * 查询我的报名列表。
+     *
+     * @param userId 用户ID
+     * @return 报名列表
+     */
+    List<StoreOperationalActivityMySignupVo> selectMySignups(@Param("userId") Integer userId);
+}

+ 74 - 7
alien-entity/src/main/resources/mapper/SubAccountStoreMapper.xml

@@ -6,7 +6,7 @@
 
     <!-- 根据用户ID查询子账号关联的门店列表(包含主账号的门店) -->
     <select id="selectSubAccountStoreListByUserId" resultType="shop.alien.entity.store.vo.SubAccountStoreListVo">
-        -- 查询子账号通过store_platform_user_role关联的门店
+        -- 查询子账号通过store_platform_user_role关联的门店(当前用户作为子账号的门店)
         SELECT
             si.id AS storeId,
             si.store_name COLLATE utf8mb4_unicode_ci AS storeName,
@@ -33,6 +33,74 @@
         UNION
         
         -- 查询主账号的门店(通过store_user.store_id关联)
+        -- 如果主账号没有门店,只查主账号信息(门店字段为NULL)
+        SELECT
+            si_main.id AS storeId,
+            si_main.store_name COLLATE utf8mb4_unicode_ci AS storeName,
+            si_main.store_address COLLATE utf8mb4_unicode_ci AS storeAddress,
+            si_main.store_tel COLLATE utf8mb4_unicode_ci AS storeTel,
+            si_main.business_status AS businessStatus,
+            NULL AS roleId,
+            NULL AS roleName,
+            su_main.id AS userId,
+            su_main.name COLLATE utf8mb4_unicode_ci AS accountName,
+            su_main.phone COLLATE utf8mb4_unicode_ci AS phone
+        FROM
+            store_user su_main
+        LEFT JOIN store_info si_main ON su_main.store_id = si_main.id 
+            AND (si_main.delete_flag = 0 OR si_main.delete_flag IS NULL)
+        WHERE
+            su_main.id = #{userId}
+            AND su_main.delete_flag = 0
+            AND (
+                -- 如果主账号在store_platform_user_role表中没有记录,或者是主账号但没有门店
+                NOT EXISTS (
+                    SELECT 1 FROM store_platform_user_role spur 
+                    WHERE spur.user_id = #{userId} 
+                    AND spur.delete_flag = 0
+                )
+                OR
+                -- 如果主账号在store_platform_user_role表中有记录,但没有门店(store_id为NULL或对应的store_info不存在)
+                (su_main.store_id IS NULL OR NOT EXISTS (
+                    SELECT 1 FROM store_info si_check 
+                    WHERE si_check.id = su_main.store_id 
+                    AND si_check.delete_flag = 0
+                ))
+            )
+        
+        
+        ORDER BY storeId DESC
+    </select>
+
+    <!-- 根据用户ID查询子账号关联的门店列表(包含主账号的门店) -->
+    <select id="selectSubAccountStoreListByUserIdTwo" resultType="shop.alien.entity.store.vo.SubAccountStoreListVo">
+        -- 查询子账号通过store_platform_user_role关联的门店
+        SELECT
+            si.id AS storeId,
+            si.store_name COLLATE utf8mb4_unicode_ci AS storeName,
+            si.store_address COLLATE utf8mb4_unicode_ci AS storeAddress,
+            si.store_tel COLLATE utf8mb4_unicode_ci AS storeTel,
+            si.business_status AS businessStatus,
+            spur.role_id AS roleId,
+            spr.role_name COLLATE utf8mb4_unicode_ci AS roleName,
+            spur.user_id AS userId,
+            spur.account_name COLLATE utf8mb4_unicode_ci AS accountName,
+            su.phone COLLATE utf8mb4_unicode_ci AS phone
+        FROM
+            store_platform_user_role spur
+                INNER JOIN store_user su ON spur.user_id = su.id
+                INNER JOIN store_info si ON spur.store_id = si.id
+                LEFT JOIN store_platform_role spr ON spur.role_id = spr.role_id
+                AND (spr.del_flag = '0' OR spr.del_flag IS NULL)
+        WHERE
+            spur.user_id = #{userId}
+          AND spur.delete_flag = 0
+          AND su.delete_flag = 0
+          AND si.delete_flag = 0
+
+        UNION
+
+        -- 查询主账号的门店(通过store_user.store_id关联)
         SELECT
             si_main.id AS storeId,
             si_main.store_name COLLATE utf8mb4_unicode_ci AS storeName,
@@ -46,20 +114,19 @@
             su_main.phone COLLATE utf8mb4_unicode_ci AS phone
         FROM
             store_user su_main
-        INNER JOIN store_info si_main ON su_main.store_id = si_main.id
+                INNER JOIN store_info si_main ON su_main.store_id = si_main.id
         WHERE
             (
                 -- 如果传入的是子账号,查询其主账号的门店
                 (su_main.id = (SELECT sub_account_id FROM store_user WHERE id = #{userId} AND account_type = 2 AND delete_flag = 0 LIMIT 1))
-                OR
-                -- 如果传入的是主账号,查询自己的门店
-                (su_main.id = #{userId} AND su_main.account_type = 1)
+           OR
+           -- 如果传入的是主账号,查询自己的门店
+            (su_main.id = #{userId} AND su_main.account_type = 1)
             )
             AND su_main.delete_flag = 0
             AND si_main.delete_flag = 0
             AND su_main.store_id IS NOT NULL
-        
+
         ORDER BY storeId DESC
     </select>
-
 </mapper>

+ 157 - 0
alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivityAchievementMapper.xml

@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.storePlantform.StoreOperationalActivityAchievementMapper">
+
+    <select id="selectCasePage" resultType="shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseVo">
+        SELECT
+            ach.id AS achievementId,
+            ach.user_id AS userId,
+            ach.activity_id AS activityId,
+            act.activity_name AS activityName,
+            act.start_time AS startTime,
+            act.end_time AS endTime,
+            CASE
+                WHEN act.status = 7 THEN 7
+                WHEN act.status IN (4, 5) AND act.end_time IS NOT NULL AND NOW() > act.end_time THEN 7
+                WHEN act.status IN (4, 5) AND (act.start_time IS NULL OR (NOW() >= act.start_time AND act.end_time > NOW())) THEN 5
+                ELSE act.status
+            END AS activityStatus,
+            u.user_name AS nickName,
+            su.head_img AS storeUserHeadImg,
+            su.nick_name AS storeUserNickName,
+            SUBSTRING_INDEX(ach.media_urls, ',', 1) AS firstMediaUrl,
+            COALESCE(ach.updated_time, ach.created_time) AS updatedTime
+        FROM store_operational_activity_achievement ach
+        INNER JOIN (
+            SELECT t.activity_id, t.user_id, MAX(t.id) AS max_id
+            FROM store_operational_activity_achievement t
+            INNER JOIN (
+                SELECT activity_id, user_id, MAX(COALESCE(updated_time, created_time)) AS max_time
+                FROM store_operational_activity_achievement
+                WHERE delete_flag = 0
+                GROUP BY activity_id, user_id
+            ) latest_time
+                ON t.activity_id = latest_time.activity_id
+                AND t.user_id = latest_time.user_id
+                AND COALESCE(t.updated_time, t.created_time) = latest_time.max_time
+            WHERE t.delete_flag = 0
+            GROUP BY t.activity_id, t.user_id
+        ) latest ON latest.max_id = ach.id
+        INNER JOIN store_operational_activity act ON act.id = ach.activity_id
+        LEFT JOIN life_user u ON u.id = ach.user_id
+        LEFT JOIN store_user su ON su.store_id = act.store_id
+            AND su.account_type = 1
+            AND su.delete_flag = 0
+        WHERE ach.delete_flag = 0
+          AND act.delete_flag = 0
+        <if test="activityStatus != null">
+            <if test="activityStatus == 5">
+                AND act.status IN (4, 5)
+            </if>
+            <if test="activityStatus != 5">
+                AND act.status = #{activityStatus}
+            </if>
+        </if>
+        ORDER BY COALESCE(ach.updated_time, ach.created_time) DESC, ach.id DESC
+    </select>
+
+    <select id="selectCaseHeader" resultType="shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseDetailVo">
+        SELECT
+            act.id AS activityId,
+            act.activity_name AS activityName,
+            act.start_time AS startTime,
+            act.end_time AS endTime,
+            u.id AS userId,
+            u.user_name AS nickName,
+            signup.user_name AS userName,
+            signup.phone AS phone,
+            signup.created_time AS createdTime,
+            u.user_image AS userImage
+        FROM store_operational_activity act
+        LEFT JOIN life_user u ON u.id = #{userId}
+        LEFT JOIN store_operational_activity_signup signup ON signup.activity_id = #{activityId}
+            AND signup.user_id = #{userId}
+            AND signup.delete_flag = 0
+        WHERE act.id = #{activityId}
+          AND act.delete_flag = 0
+          AND u.delete_flag = 0
+        LIMIT 1
+    </select>
+
+    <select id="selectCaseItems" resultType="shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseItemVo">
+        SELECT
+            ach.id AS achievementId,
+            ach.achievement_desc AS achievementDesc,
+            ach.media_urls AS mediaUrls,
+            ach.created_time AS createdTime,
+            COALESCE(ach.updated_time, ach.created_time) AS updatedTime
+        FROM store_operational_activity_achievement ach
+        WHERE ach.activity_id = #{activityId}
+          AND ach.user_id = #{userId}
+          AND ach.delete_flag = 0
+        ORDER BY COALESCE(ach.updated_time, ach.created_time) DESC, ach.id DESC
+    </select>
+
+    <select id="selectStoreCasePage" resultType="shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseVo">
+        SELECT
+            ach.id AS achievementId,
+            ach.user_id AS userId,
+            ach.activity_id AS activityId,
+            act.activity_name AS activityName,
+            CASE
+                WHEN act.status = 7 THEN 7
+                WHEN act.status IN (4, 5) AND act.end_time IS NOT NULL AND NOW() > act.end_time THEN 7
+                WHEN act.status IN (4, 5) AND (act.start_time IS NULL OR NOW() >= act.start_time) THEN 5
+                ELSE act.status
+            END AS activityStatus,
+            u.user_name AS nickName,
+            signup.phone AS phone,
+            CASE
+                WHEN (act.result_media_url IS NOT NULL AND act.result_media_url != '')
+                    OR (act.result_text IS NOT NULL AND act.result_text != '') THEN 1
+                ELSE 0
+            END AS hasResult,
+            SUBSTRING_INDEX(ach.media_urls, ',', 1) AS firstMediaUrl,
+            COALESCE(ach.updated_time, ach.created_time) AS updatedTime
+        FROM store_operational_activity_achievement ach
+        INNER JOIN (
+            SELECT t.activity_id, t.user_id, MAX(t.id) AS max_id
+            FROM store_operational_activity_achievement t
+            INNER JOIN (
+                SELECT activity_id, user_id, MAX(COALESCE(updated_time, created_time)) AS max_time
+                FROM store_operational_activity_achievement
+                WHERE delete_flag = 0
+                GROUP BY activity_id, user_id
+            ) latest_time
+                ON t.activity_id = latest_time.activity_id
+                AND t.user_id = latest_time.user_id
+                AND COALESCE(t.updated_time, t.created_time) = latest_time.max_time
+            WHERE t.delete_flag = 0
+            GROUP BY t.activity_id, t.user_id
+        ) latest ON latest.max_id = ach.id
+        INNER JOIN store_operational_activity act ON act.id = ach.activity_id
+        LEFT JOIN life_user u ON u.id = ach.user_id
+        LEFT JOIN store_operational_activity_signup signup ON signup.activity_id = ach.activity_id
+            AND signup.user_id = ach.user_id
+            AND signup.delete_flag = 0
+        WHERE ach.delete_flag = 0
+          AND act.delete_flag = 0
+          AND act.store_id = #{storeId}
+        <if test="hasResult != null">
+            <choose>
+                <when test="hasResult == 0">
+                    AND ((act.result_media_url IS NULL OR act.result_media_url = '')
+                    AND (act.result_text IS NULL OR act.result_text = ''))
+                </when>
+                <when test="hasResult == 1">
+                    AND ((act.result_media_url IS NOT NULL AND act.result_media_url != '')
+                    OR (act.result_text IS NOT NULL AND act.result_text != ''))
+                </when>
+            </choose>
+        </if>
+        <if test="activityName != null and activityName != ''">
+            AND act.activity_name LIKE CONCAT('%', #{activityName}, '%')
+        </if>
+        ORDER BY COALESCE(ach.updated_time, ach.created_time) DESC, ach.id DESC
+    </select>
+</mapper>

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

@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="shop.alien.mapper.storePlantform.StoreOperationalActivitySignupMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.storePlatform.StoreOperationalActivitySignup">
+        <id column="id" property="id" />
+        <result column="activity_id" property="activityId" />
+        <result column="store_id" property="storeId" />
+        <result column="user_id" property="userId" />
+        <result column="user_name" property="userName" />
+        <result column="phone" property="phone" />
+        <result column="status" property="status" />
+        <result column="signup_time" property="signupTime" />
+        <result column="delete_flag" property="deleteFlag" />
+        <result column="created_time" property="createdTime" />
+        <result column="created_user_id" property="createdUserId" />
+        <result column="updated_time" property="updatedTime" />
+        <result column="updated_user_id" property="updatedUserId" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, activity_id, store_id, user_id, user_name, phone, status, signup_time,
+        delete_flag, created_time, created_user_id, updated_time, updated_user_id
+    </sql>
+
+    <select id="selectByActivityId" resultMap="BaseResultMap">
+        SELECT
+        <include refid="Base_Column_List" />
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND delete_flag = 0
+        ORDER BY signup_time DESC
+    </select>
+
+    <select id="countSignupByActivityId" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND delete_flag = 0
+          AND status IN (0, 2)
+    </select>
+
+    <select id="countApprovedByActivityId" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND delete_flag = 0
+          AND status = 2
+    </select>
+
+    <select id="countApprovedByActivityAndUser" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND user_id = #{userId}
+          AND delete_flag = 0
+          AND status in (0,2)
+        LIMIT 1
+    </select>
+
+    <select id="countSignedUpByActivityAndUser" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND user_id = #{userId}
+          AND delete_flag = 0
+          AND status IN (0, 2)
+        LIMIT 1
+    </select>
+
+    <select id="selectLatestSignupStatus" resultType="java.lang.Integer">
+        SELECT status
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND user_id = #{userId}
+          AND delete_flag = 0
+        ORDER BY signup_time DESC, id DESC
+        LIMIT 1
+    </select>
+
+    <select id="selectMySignups" resultType="shop.alien.entity.storePlatform.vo.StoreOperationalActivityMySignupVo">
+        SELECT
+            s.id AS signupId,
+            s.activity_id AS activityId,
+            a.store_id AS storeId,
+            a.activity_name AS activityName,
+            a.promotional_image AS promotionalImage,
+            img.img_url AS activityTitleImgUrl,
+            su.head_img AS storeUserHeadImg,
+            su.nick_name AS storeUserNickName,
+            a.start_time AS startTime,
+            a.end_time AS endTime,
+            a.status AS activityStatus,
+            CASE
+                WHEN a.signup_start_time IS NOT NULL AND NOW() &lt; a.signup_start_time THEN 0
+                WHEN a.signup_end_time IS NOT NULL AND NOW() &gt; a.signup_end_time THEN 2
+                ELSE 1
+            END AS signupStatus,
+            s.status AS signupAuditStatus,
+            CASE WHEN a.status = 7 THEN 1 ELSE 0 END AS activityStartStatus,
+            CASE WHEN ach.signup_id IS NULL THEN 0 ELSE 1 END AS hasAchievement,
+            s.signup_time AS signupTime
+        FROM store_operational_activity_signup s
+        INNER JOIN store_operational_activity a ON a.id = s.activity_id
+        LEFT JOIN store_img img ON img.store_id = a.store_id
+            AND img.business_id = a.id
+            AND img.img_type = 26
+            AND img.delete_flag = 0
+        LEFT JOIN store_user su ON su.store_id = a.store_id
+            AND su.account_type = 1
+            AND su.delete_flag = 0
+        LEFT JOIN (
+            SELECT DISTINCT signup_id
+            FROM store_operational_activity_achievement
+            WHERE delete_flag = 0
+        ) ach ON ach.signup_id = s.id
+        WHERE s.user_id = #{userId}
+          AND s.delete_flag = 0
+          AND a.delete_flag = 0
+        ORDER BY s.signup_time DESC
+    </select>
+</mapper>

+ 8 - 4
alien-gateway/src/main/java/shop/alien/gateway/controller/StoreUserController.java

@@ -94,12 +94,16 @@ public class StoreUserController {
     @ApiOperationSupport(order = 2)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "userId", value = "用户ID(store_user.id)", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "storeId", value = "目标门店ID", dataType = "Integer", paramType = "query", required = true)
+            @ApiImplicitParam(name = "storeId", value = "目标门店ID", dataType = "Integer", paramType = "query", required = false),
+            @ApiImplicitParam(name = "accountType", value = "主子账号类型", dataType = "Integer", paramType = "query", required = true)
     })
     @GetMapping("/switchSubAccountStore")
-    public R<StoreUserVo> switchSubAccountStore(@RequestParam("userId") Integer userId, @RequestParam("storeId") Integer storeId) {
-        log.info("StoreUserController.switchSubAccountStore?userId={}, storeId={}", userId, storeId);
-        return storeUserService.switchSubAccountStore(userId, storeId);
+    public R<StoreUserVo> switchSubAccountStore(
+            @RequestParam("userId") Integer userId, 
+            @RequestParam(value = "storeId", required = false) Integer storeId,
+            @RequestParam("accountType") Integer accountType) {
+        log.info("StoreUserController.switchSubAccountStore?userId={}, storeId={}, accountType={}", userId, storeId, accountType);
+        return storeUserService.switchSubAccountStore(userId, storeId, accountType);
     }
 
 }

+ 1 - 1
alien-gateway/src/main/java/shop/alien/gateway/service/StoreUserService.java

@@ -25,6 +25,6 @@ public interface StoreUserService extends IService<StoreUser> {
      * @param storeId 目标门店ID
      * @return 用户信息和token
      */
-    R<StoreUserVo> switchSubAccountStore(Integer userId, Integer storeId);
+    R<StoreUserVo> switchSubAccountStore(Integer userId, Integer storeId, Integer accountType);
 
 }

+ 104 - 105
alien-gateway/src/main/java/shop/alien/gateway/service/impl/StoreUserServiceImpl.java

@@ -52,9 +52,9 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
     private final BaseRedisService baseRedisService;
 
     private final StorePlatformUserRoleGatewayMapper storePlatformUserRoleMapper;
-    
+
     private final StorePlatformRoleMenuGatewayMapper storePlatformRoleMenuMapper;
-    
+
     private final StorePlatformRoleGatewayMapper storePlatformRoleMapper;
 
     /**
@@ -96,62 +96,108 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
         tokenMap.put("userType", "store");
         storeUserVo.setToken(JwtUtil.createJWT("store_" + storeUser.getPhone(), storeUser.getName(), JSONObject.toJSONString(tokenMap), effectiveTimeIntLong));
         baseRedisService.setString("store_" + storeUser.getPhone(), storeUserVo.getToken());
-        if(storeUserVo.getStoreId() == null){
-            // 子账号登录,查询当前子账号权限最多的那个门店和角色id
-            log.info("子账号登录,storeId为空,查询权限最多的门店 - userId: {}", storeUser.getId());
-            
-            // 查询子账号关联的所有门店和角色
+
+        if (storeUserVo.getAccountType() == 1) {
+            // 主账号登录,判断是否有子账号
+            // 通过 store_platform_user_role 表中的关联记录判断
+            // 查询该门店下是否有其他账号(子账号)在 store_platform_user_role 表中有关联记录
             LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
-            roleWrapper.eq(StorePlatformUserRole::getUserId, storeUser.getId())
+            roleWrapper.eq(StorePlatformUserRole::getUserId, storeUser.getId()) // 排除当前账号自己
                     .eq(StorePlatformUserRole::getDeleteFlag, 0);
-            List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
-            
-            if (userRoles != null && !userRoles.isEmpty()) {
-                // 计算每个角色的菜单权限数量,选择权限最多的门店
-                StorePlatformUserRole maxPermissionRole = null;
-                int maxPermissionCount = -1;
-                
-                for (StorePlatformUserRole userRole : userRoles) {
-                    if (userRole.getRoleId() != null) {
-                        // 查询该角色的菜单权限数量
-                        Integer permissionCount = storePlatformRoleMenuMapper.countMenuByRoleId(userRole.getRoleId());
-                        if (permissionCount == null) {
-                            permissionCount = 0;
-                        }
-                        
-                        // 如果权限数量更多,或者权限数量相同但角色ID更大,则更新
-                        if (permissionCount > maxPermissionCount || 
-                            (permissionCount == maxPermissionCount && 
-                             (maxPermissionRole == null || userRole.getRoleId() > maxPermissionRole.getRoleId()))) {
-                            maxPermissionCount = permissionCount;
-                            maxPermissionRole = userRole;
-                        }
+            long subAccountCount = storePlatformUserRoleMapper.selectCount(roleWrapper);
+            if (subAccountCount > 0) {
+                storeUserVo.setHasSubAccount(true);
+            } else {
+                storeUserVo.setHasSubAccount(false);
+            }
+            log.info("主账号登录,检查子账号 - userId: {}, storeId: {}, hasSubAccount: {}, subAccountCount: {}",
+                    storeUser.getId(), storeUserVo.getStoreId(), false, subAccountCount);
+        } else {
+            // 子账号切换如果有storeId 则为子账号切换无感登录 否则为正常登录
+            if (storeUser.getStoreId() != null) {
+                LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
+                roleWrapper.eq(StorePlatformUserRole::getUserId, storeUser.getId())
+                        .eq(StorePlatformUserRole::getStoreId, storeUser.getStoreId())
+                        .eq(StorePlatformUserRole::getDeleteFlag, 0);
+                StorePlatformUserRole userRole = storePlatformUserRoleMapper.selectOne(roleWrapper);
+
+                if (userRole == null) {
+                    log.warn("子账号未关联目标门店 - userId: {}, storeId: {}", storeUser.getId(), storeUser.getStoreId());
+                    return R.fail("切换失败,请确认子账号是否关联了该门店");
+                }
+
+                int finalStoreId = userRole.getStoreId(); // 从关联表获取门店ID
+                StoreInfo storeInfo = storeInfoMapper.selectById(finalStoreId);
+                // 查询角色名称
+                if (userRole.getRoleId() != null) {
+                    StorePlatformRole role = storePlatformRoleMapper.selectById(userRole.getRoleId());
+                    if (role != null) {
+                        storeUserVo.setRoleName(role.getRoleName());
                     }
                 }
-                
-                if (maxPermissionRole != null) {
-                    // 设置门店ID和角色ID
-                    storeUserVo.setStoreId(maxPermissionRole.getStoreId());
-                    storeUserVo.setRoleId(maxPermissionRole.getRoleId());
-                    
-                    // 查询角色名称
-                    if (maxPermissionRole.getRoleId() != null) {
-                        StorePlatformRole role = storePlatformRoleMapper.selectById(maxPermissionRole.getRoleId());
-                        if (role != null) {
-                            storeUserVo.setRoleName(role.getRoleName());
+                storeUserVo.setRoleId(userRole.getRoleId());
+                storeUserVo.setStoreId(storeInfo.getId());
+                storeUserVo.setHasSubAccount(true);
+                storeUserVo.setRoleName(userRole.getAccountName());
+            } else {
+
+                // 子账号登录,查询当前子账号权限最多的那个门店和角色id
+                log.info("子账号登录,storeId为空,查询权限最多的门店 - userId: {}", storeUser.getId());
+                // 查询子账号关联的所有门店和角色
+                LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
+                roleWrapper.eq(StorePlatformUserRole::getUserId, storeUser.getId())
+                        .eq(StorePlatformUserRole::getDeleteFlag, 0);
+                List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
+
+                if (userRoles != null && !userRoles.isEmpty()) {
+                    // 计算每个角色的菜单权限数量,选择权限最多的门店
+                    StorePlatformUserRole maxPermissionRole = null;
+                    int maxPermissionCount = -1;
+
+                    for (StorePlatformUserRole userRole : userRoles) {
+                        if (userRole.getRoleId() != null) {
+                            // 查询该角色的菜单权限数量
+                            Integer permissionCount = storePlatformRoleMenuMapper.countMenuByRoleId(userRole.getRoleId());
+                            if (permissionCount == null) {
+                                permissionCount = 0;
+                            }
+
+                            // 如果权限数量更多,或者权限数量相同但角色ID更大,则更新
+                            if (permissionCount > maxPermissionCount ||
+                                    (permissionCount == maxPermissionCount &&
+                                            (maxPermissionRole == null || userRole.getRoleId() > maxPermissionRole.getRoleId()))) {
+                                maxPermissionCount = permissionCount;
+                                maxPermissionRole = userRole;
+                            }
+                        }
+                    }
+
+                    if (maxPermissionRole != null) {
+                        // 设置门店ID和角色ID
+                        storeUserVo.setStoreId(maxPermissionRole.getStoreId());
+                        storeUserVo.setRoleId(maxPermissionRole.getRoleId());
+                        storeUserVo.setHasSubAccount(true);
+
+                        // 查询角色名称
+                        if (maxPermissionRole.getRoleId() != null) {
+                            StorePlatformRole role = storePlatformRoleMapper.selectById(maxPermissionRole.getRoleId());
+                            if (role != null) {
+                                storeUserVo.setRoleName(role.getRoleName());
+                            }
                         }
+
+                        log.info("子账号权限最多的门店查询成功 - userId: {}, storeId: {}, roleId: {}, roleName: {}, 权限数量: {}",
+                                storeUser.getId(), maxPermissionRole.getStoreId(), maxPermissionRole.getRoleId(),
+                                storeUserVo.getRoleName(), maxPermissionCount);
+                    } else {
+                        log.warn("子账号关联的门店都没有角色ID - userId: {}", storeUser.getId());
                     }
-                    
-                    log.info("子账号权限最多的门店查询成功 - userId: {}, storeId: {}, roleId: {}, roleName: {}, 权限数量: {}", 
-                            storeUser.getId(), maxPermissionRole.getStoreId(), maxPermissionRole.getRoleId(), 
-                            storeUserVo.getRoleName(), maxPermissionCount);
                 } else {
-                    log.warn("子账号关联的门店都没有角色ID - userId: {}", storeUser.getId());
+                    log.warn("子账号未关联任何门店 - userId: {}", storeUser.getId());
                 }
-            } else {
-                log.warn("子账号未关联任何门店 - userId: {}", storeUser.getId());
             }
         }
+
         // 查询门店信息(如果storeId不为空)
         StoreInfo storeInfo = null;
         if (storeUserVo.getStoreId() != null) {
@@ -165,13 +211,13 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
     }
 
     @Override
-    public R<StoreUserVo> switchSubAccountStore(Integer userId, Integer storeId) {
+    public R<StoreUserVo> switchSubAccountStore(Integer userId, Integer storeId, Integer accountType) {
         log.info("StoreUserServiceImpl.switchSubAccountStore?userId={}, storeId={}", userId, storeId);
 
-        if (userId == null || storeId == null) {
-            log.warn("用户ID或门店ID为空 - userId: {}, storeId: {}", userId, storeId);
-            return R.fail("用户ID或门店ID不能为空");
-        }
+//        if (userId == null || storeId == null) {
+//            log.warn("用户ID或门店ID为空 - userId: {}, storeId: {}", userId, storeId);
+//            return R.fail("用户ID或门店ID不能为空");
+//        }
 
         // 1. 查询用户信息(类似登录接口,验证用户是否存在)
         StoreUser storeUser = this.getById(userId);
@@ -186,72 +232,25 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserGatewayMapper, St
             return R.fail("账号被禁用");
         }
 
-        // 3. 判断是主账号还是子账号,并验证门店关联
-        Long roleId = null;
-        Integer finalStoreId = null; // 最终要回显的门店ID
-        StoreInfo storeInfo = new StoreInfo();
-        if (storeUser.getStoreId() != null && storeUser.getStoreId().equals(storeId)) {
-            // 主账号:直接回显 store_user 的门店 id
-//            if (storeUser.getStoreId() == null || !storeUser.getStoreId().equals(storeId)) {
-//                log.warn("主账号门店ID不匹配 - userId: {}, 传入storeId: {}, 实际storeId: {}",
-//                        userId, storeId, storeUser.getStoreId());
-//                return R.fail("切换失败,门店ID不匹配");
-//            }
-            finalStoreId = storeUser.getStoreId();
-            roleId = null; // 主账号没有角色id
-        } else {
-            // 子账号:查询角色关联表,回显子账号关联的门店 id
-            LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
-            roleWrapper.eq(StorePlatformUserRole::getUserId, userId)
-                    .eq(StorePlatformUserRole::getStoreId, storeId)
-                    .eq(StorePlatformUserRole::getDeleteFlag, 0);
-            StorePlatformUserRole userRole = storePlatformUserRoleMapper.selectOne(roleWrapper);
-            
-            if (userRole == null) {
-                log.warn("子账号未关联目标门店 - userId: {}, storeId: {}", userId, storeId);
-                return R.fail("切换失败,请确认子账号是否关联了该门店");
-            }
-            
-            finalStoreId = userRole.getStoreId(); // 从关联表获取门店ID
-            storeInfo = storeInfoMapper.selectById(finalStoreId);
-            roleId = userRole.getRoleId() != null ? userRole.getRoleId() : null;
-        }
-
-        // 4. 删除旧的token(参考switchingStates逻辑)
+        // 3. 删除旧的token(参考switchingStates逻辑)
         baseRedisService.delete("store_" + storeUser.getPhone());
         baseRedisService.delete("storePlatform_" + storeUser.getPhone());
         log.info("删除用户token - phone: {}", storeUser.getPhone());
 
-        // 5. 创建临时用户对象用于生成token(设置正确的门店ID)
+        // 4. 创建临时用户对象用于生成token(设置正确的门店ID)
         StoreUser tempUser = new StoreUser();
         BeanUtils.copyProperties(storeUser, tempUser);
-        tempUser.setStoreId(finalStoreId);
+        tempUser.setAccountType(accountType);
+        tempUser.setStoreId(storeId);
 
-        // 6. 生成token(类似登录接口)
+        // 6. 生成token(类似登录接口)走切换账号逻辑
         R<StoreUserVo> tokenResult = createToKen(tempUser);
         if (tokenResult.getCode() != 200 || tokenResult.getData() == null) {
             log.error("生成token失败 - userId: {}", userId);
             return R.fail("切换失败,token生成失败");
         }
 
-        // 7. 设置角色ID和门店ID(如果没有角色id,该字段为null)
         StoreUserVo storeUserVo = tokenResult.getData();
-        storeUserVo.setRoleId(roleId);
-        storeUserVo.setStoreId(finalStoreId); // 确保回显正确的门店ID
-        
-        // 查询并设置角色名称
-        if (roleId != null) {
-            StorePlatformRole role = storePlatformRoleMapper.selectById(roleId);
-            if (role != null) {
-                storeUserVo.setRoleName(role.getRoleName());
-            }
-        }
-        
-        if(storeInfo != null){
-            storeUserVo.setName(storeInfo.getStoreName());
-        }
-
-        log.info("切换门店成功 - userId: {}, storeId: {}, roleId: {}", userId, finalStoreId, roleId);
         return R.data(storeUserVo);
     }
 

+ 12 - 1
alien-job/src/main/java/shop/alien/job/store/BadReviewAppealJob.java

@@ -222,13 +222,24 @@ public class BadReviewAppealJob {
                     // 发送通知
                     LifeNotice lifeMessage = new LifeNotice();
                     StoreUser storeUser = storeUserMapper.selectOne(new QueryWrapper<StoreUser>().eq("store_id", appeal.getStoreId()));
+                    if (storeUser == null) {
+                        log.warn("发送申诉通知失败,未找到店铺用户,申诉ID: {},店铺ID: {}", appeal.getId(), appeal.getStoreId());
+                        continue;
+                    }
+                    String title = "申诉通知";
+                    lifeMessage.setTitle(title);
                     lifeMessage.setReceiverId("store_" + storeUser.getPhone());
                     String text = "您的评论申诉结果为" + sCommentAppeal.getFinalResult();
                     lifeMessage.setContext(text);
                     lifeMessage.setSenderId("system");
                     lifeMessage.setIsRead(0);
                     lifeMessage.setNoticeType(1);
-                    lifeNoticeMapper.insert(lifeMessage);
+                    int insertResult = lifeNoticeMapper.insert(lifeMessage);
+                    if (insertResult > 0) {
+                        log.info("申诉通知发送成功,申诉ID: {},通知标题: {}", appeal.getId(), title);
+                    } else {
+                        log.error("申诉通知发送失败,申诉ID: {},通知标题: {}", appeal.getId(), title);
+                    }
 
                 } else {
                     if (analyzeResp != null) {

+ 1 - 1
alien-lawyer/src/main/java/shop/alien/lawyer/controller/AiAutoReview.java

@@ -88,7 +88,7 @@ public class AiAutoReview {
         //申诉图片
         commentAppeal.setImgUrl(requestBody.get("appeal_images").toString());
         //评价图片
-//        commentAppeal.setReviewImages(requestBody.get("review_images").toString());
+        commentAppeal.setReviewImages(requestBody.get("review_images").toString());
         //律师id
         commentAppeal.setLawyerUserId(requestBody.get("lawyer_user_id").toString());
         commentAppealService.submitAppeal(commentAppeal);

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

@@ -125,7 +125,7 @@ public class OperationalActivityController {
             if (dto.getStoreId() == null || dto.getStoreId() <= 0) {
                  return R.data(null, "暂无承载数据!!!");
             }
-            IPage<StoreOperationalActivityVO> result = activityService.queryActivityList( dto.getStoreId(), dto.getStatus(), dto.getActivityName(), dto.getPageNum(), dto.getPageSize());
+            IPage<StoreOperationalActivityVO> result = activityService.queryActivityList( dto.getStoreId(), dto.getStatus(), dto.getActivityType(), dto.getActivityName(), dto.getPageNum(), dto.getPageSize());
             return R.data(result);
         } catch (Exception e) {
             log.error("OperationalActivityController.qeryActivityList ERROR: {}", e.getMessage(), e);
@@ -156,5 +156,22 @@ public class OperationalActivityController {
             return R.fail(e.getMessage());
         }
     }
+
+    @ApiOperation("上传活动结果")
+    @ApiOperationSupport(order = 8)
+    @PostMapping("/uploadResult")
+    public R<String> uploadActivityResult(@RequestBody StoreOperationalActivityDTO dto) {
+        log.info("OperationalActivityController.uploadActivityResult: dto={}", dto);
+        try {
+            int result = activityService.uploadActivityResult(dto);
+            if (result > 0) {
+                return R.success("活动结果上传成功");
+            }
+            return R.fail("活动结果上传失败");
+        } catch (Exception e) {
+            log.error("OperationalActivityController.uploadActivityResult ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
 }
 

+ 111 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivitySignupController.java

@@ -0,0 +1,111 @@
+package shop.alien.storeplatform.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupVO;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupQueryDTO;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupIdDTO;
+import shop.alien.storeplatform.service.OperationalActivitySignupService;
+
+/**
+ * 运营活动报名管理控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"商家端-运营活动报名管理"})
+@ApiSort(11)
+@CrossOrigin
+@RestController
+@RequestMapping("/operationalActivitySignup")
+@RequiredArgsConstructor
+public class OperationalActivitySignupController {
+
+    private final OperationalActivitySignupService signupService;
+
+    @ApiOperation("根据商户ID查询报名列表")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/queryList")
+    public R<IPage<StoreOperationalActivitySignupVO>> querySignupList(@RequestBody StoreOperationalActivitySignupQueryDTO dto) {
+        log.info("OperationalActivitySignupController.querySignupList: dto={}", dto);
+        try {
+            if (dto.getStoreId() == null || dto.getStoreId() <= 0) {
+                return R.fail("商户ID不能为空");
+            }
+            Integer pageNum = dto.getPageNum() != null ? dto.getPageNum() : 1;
+            Integer pageSize = dto.getPageSize() != null ? dto.getPageSize() : 10;
+            IPage<StoreOperationalActivitySignupVO> result = signupService.querySignupList(
+                    dto.getStoreId(), dto.getStatus(), dto.getActivityName(), pageNum, pageSize);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("OperationalActivitySignupController.querySignupList ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据ID查询报名详情")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/queryById")
+    public R<StoreOperationalActivitySignupVO> querySignupById(@RequestBody StoreOperationalActivitySignupIdDTO dto) {
+        log.info("OperationalActivitySignupController.querySignupById: id={}", dto.getId());
+        try {
+            if (dto.getId() == null || dto.getId() <= 0) {
+                return R.fail("报名ID不能为空");
+            }
+            StoreOperationalActivitySignupVO result = signupService.querySignupById(dto.getId());
+            if (result == null) {
+                return R.fail("报名记录不存在");
+            }
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("OperationalActivitySignupController.querySignupById ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("审核通过")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/approve")
+    public R<String> approveSignup(@RequestBody StoreOperationalActivitySignupIdDTO dto) {
+        log.info("OperationalActivitySignupController.approveSignup: id={}", dto.getId());
+        try {
+            if (dto.getId() == null || dto.getId() <= 0) {
+                return R.fail("报名ID不能为空");
+            }
+            int result = signupService.approveSignup(dto.getId());
+            if (result > 0) {
+                return R.success("审核通过成功");
+            }
+            return R.fail("审核通过失败");
+        } catch (Exception e) {
+            log.error("OperationalActivitySignupController.approveSignup ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("审核拒绝")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/reject")
+    public R<String> rejectSignup(@RequestBody StoreOperationalActivitySignupIdDTO dto) {
+        log.info("OperationalActivitySignupController.rejectSignup: id={}, rejectReason={}", dto.getId(), dto.getRejectReason());
+        try {
+            if (dto.getId() == null || dto.getId() <= 0) {
+                return R.fail("报名ID不能为空");
+            }
+            int result = signupService.rejectSignup(dto.getId(), dto.getRejectReason());
+            if (result > 0) {
+                return R.success("审核拒绝成功");
+            }
+            return R.fail("审核拒绝失败");
+        } catch (Exception e) {
+            log.error("OperationalActivitySignupController.rejectSignup ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}
+

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

@@ -131,15 +131,12 @@ public class StorePlatformUserRoleController {
     public R<String> createAccountAndAssignRole(@RequestBody CreateAccountDto createAccountDto) {
         log.info("StorePlatformUserRoleController.createAccountAndAssignRole?phone={}, accountName={}, storeId={}, roleId={}", 
                 createAccountDto.getPhone(), createAccountDto.getAccountName(), createAccountDto.getStoreId(), createAccountDto.getRoleId());
-        boolean result = storePlatformUserRoleService.createAccountAndAssignRole(
+        R<String> result = storePlatformUserRoleService.createAccountAndAssignRole(
                 createAccountDto.getPhone(), 
                 createAccountDto.getAccountName(), 
                 createAccountDto.getStoreId(), 
                 createAccountDto.getRoleId());
-        if (result) {
-            return R.success("创建账号并分配角色成功");
-        }
-        return R.fail("创建账号并分配角色失败");
+        return result;
     }
 
 

+ 9 - 1
alien-store-platform/src/main/java/shop/alien/storeplatform/service/OperationalActivityService.java

@@ -56,7 +56,7 @@ public interface OperationalActivityService {
      * @param pageSize     每页大小
      * @return 分页结果
      */
-    IPage<StoreOperationalActivityVO> queryActivityList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize);
+    IPage<StoreOperationalActivityVO> queryActivityList(Integer storeId, Integer status,String activityType, String activityName,  Integer pageNum, Integer pageSize);
 
     /**
      * 启用/禁用活动
@@ -67,5 +67,13 @@ public interface OperationalActivityService {
      */
     int updateActivityStatus(Integer id, Integer status);
 
+    /**
+     * 上传活动结果
+     *
+     * @param dto 活动结果信息(包含活动ID和结果内容)
+     * @return 更新结果
+     */
+    int uploadActivityResult(StoreOperationalActivityDTO dto);
+
 }
 

+ 51 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/OperationalActivitySignupService.java

@@ -0,0 +1,51 @@
+package shop.alien.storeplatform.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupVO;
+
+/**
+ * 运营活动报名服务接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface OperationalActivitySignupService {
+
+    /**
+     * 根据商户ID分页查询报名列表
+     *
+     * @param storeId   商户ID
+     * @param status    报名状态(可选,0-待审核,1-拒绝,2-通过)
+     * @param activityName 活动名称(可选,模糊查询)
+     * @param pageNum   页码
+     * @param pageSize  每页大小
+     * @return 分页结果
+     */
+    IPage<StoreOperationalActivitySignupVO> querySignupList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize);
+
+    /**
+     * 根据ID查询报名详情
+     *
+     * @param id 报名ID
+     * @return 报名详情
+     */
+    StoreOperationalActivitySignupVO querySignupById(Integer id);
+
+    /**
+     * 审核通过
+     *
+     * @param id 报名ID
+     * @return 更新结果
+     */
+    int approveSignup(Integer id);
+
+    /**
+     * 审核拒绝
+     *
+     * @param id 报名ID
+     * @param rejectReason 拒绝原因
+     * @return 更新结果
+     */
+    int rejectSignup(Integer id, String rejectReason);
+}
+

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

@@ -1,6 +1,7 @@
 package shop.alien.storeplatform.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.result.R;
 import shop.alien.entity.store.StorePlatformUserRole;
 import shop.alien.entity.store.vo.SubAccountDetailVo;
 import shop.alien.entity.store.vo.SubAccountVo;
@@ -73,7 +74,7 @@ public interface StorePlatformUserRoleService extends IService<StorePlatformUser
      * @param roleId      角色ID
      * @return 是否成功
      */
-    boolean createAccountAndAssignRole(String phone, String accountName, Integer storeId, Long roleId);
+    R<String> createAccountAndAssignRole(String phone, String accountName, Integer storeId, Long roleId);
 
     /**
      * 查询当前店铺下的子账号列表

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

@@ -115,6 +115,16 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                     String authorization = "Bearer " + accessToken;
                     
                     JsonNode auditParam = dto.getAuditParam();
+                    // 如果 auditParam 是字符串,先解析为 JsonNode
+                    if (auditParam != null && auditParam.isTextual()) {
+                        try {
+                            auditParam = objectMapper.readTree(auditParam.asText());
+                        } catch (Exception e) {
+                            log.error("解析 auditParam JSON 字符串失败: {}", auditParam.asText(), e);
+                            auditParam = null;
+                        }
+                    }
+                    
                     String auditText = (auditParam != null && auditParam.has("text")) ? auditParam.get("text").asText() : "";
                     JsonNode imagesNode = (auditParam != null) ? auditParam.get("image_urls") : null;
                     
@@ -127,15 +137,37 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                     // 调用同步审核工具类
                     AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(auditText, imageUrls);
                     
+                    // 审核结束后,设置审核时间
+                    Date auditTime = new Date();
+                    activity.setAuditTime(auditTime);
+                    
+                    // 审核通过,根据活动时间自动设置状态
+                    Date currentTime = new Date();
+                    Date startTime = activity.getStartTime();
+                    Date endTime = activity.getEndTime();
+                    
+                    int status;
+                    if (currentTime.before(startTime)) {
+                        // 当前时间在活动开始时间之前,设置为未开始
+                        status = 2;
+                    } else if (currentTime.compareTo(startTime) >= 0 && currentTime.compareTo(endTime) <= 0) {
+                        // 当前时间在活动时间之间,设置为进行中
+                        status = 5;
+                    } else {
+                        // 当前时间在活动结束时间之后,设置为已结束
+                        status = 7;
+                    }
+                    activity.setStatus(status);
+
                     // 如果审核不通过,记录原因并提前结束
                     if (!auditResult.isPassed()) {
                         log.warn("AI内容审核未通过: {}", auditResult.getFailureReason());
                         failureReasonHolder.set(auditResult.getFailureReason());
-                        return 2; // 返回2表示审核失败
+                        activity.setApprovalComments(auditResult.getFailureReason());
+                        activity.setStatus(3);
+//                        return 2; // 返回2表示审核失败
                     }
-                    
-                    // 审核通过,执行入库操作(状态设为8:待生成海报)
-                    activity.setStatus(8);
+
                     result = activityMapper.insert(activity);
                     // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
                     if (dto.getUploadImgType() == 2) {
@@ -231,7 +263,114 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
         StoreOperationalActivity activity = new StoreOperationalActivity();
         BeanUtils.copyProperties(dto, activity);
-        Integer result = activityMapper.updateById(activity);
+        Integer result =0;
+        try {
+            String accessToken = getToken();
+            if (accessToken == null || accessToken.isEmpty()) {
+                log.error("获取AI服务access_token失败,无法生成促销图片");
+            } else {
+                // AI登录成功
+                // 先调用AI进行运营名称和图片的审核,同步调用
+                String authorization = "Bearer " + accessToken;
+
+                JsonNode auditParam = dto.getAuditParam();
+                // 如果 auditParam 是字符串,先解析为 JsonNode
+                if (auditParam != null && auditParam.isTextual()) {
+                    try {
+                        auditParam = objectMapper.readTree(auditParam.asText());
+                    } catch (Exception e) {
+                        log.error("解析 auditParam JSON 字符串失败: {}", auditParam.asText(), e);
+                        auditParam = null;
+                    }
+                }
+
+                String auditText = (auditParam != null && auditParam.has("text")) ? auditParam.get("text").asText() : "";
+                JsonNode imagesNode = (auditParam != null) ? auditParam.get("image_urls") : null;
+
+                List<String> imageUrls = (imagesNode != null && imagesNode.isArray())
+                        ? StreamSupport.stream(imagesNode.spliterator(), false)
+                        .map(JsonNode::asText)
+                        .collect(Collectors.toList())
+                        : new ArrayList<>();
+
+                // 调用同步审核工具类
+                AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(auditText, imageUrls);
+
+                // 审核结束后,设置审核时间
+                Date auditTime = new Date();
+                activity.setAuditTime(auditTime);
+
+                // 审核通过,根据活动时间自动设置状态
+                Date currentTime = new Date();
+                Date startTime = activity.getStartTime();
+                Date endTime = activity.getEndTime();
+
+                int status;
+                if (currentTime.before(startTime)) {
+                    // 当前时间在活动开始时间之前,设置为未开始
+                    status = 2;
+                } else if (currentTime.compareTo(startTime) >= 0 && currentTime.compareTo(endTime) <= 0) {
+                    // 当前时间在活动时间之间,设置为进行中
+                    status = 5;
+                } else {
+                    // 当前时间在活动结束时间之后,设置为已结束
+                    status = 7;
+                }
+                activity.setStatus(status);
+
+                // 如果审核不通过,记录原因并提前结束
+                if (!auditResult.isPassed()) {
+                    log.warn("AI内容审核未通过: {}", auditResult.getFailureReason());
+                    failureReasonHolder.set(auditResult.getFailureReason());
+                    activity.setApprovalComments(auditResult.getFailureReason());
+                    activity.setStatus(3);
+//                        return 2; // 返回2表示审核失败
+                }
+
+                result = activityMapper.updateById(activity);
+                // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
+                if (dto.getUploadImgType() == 2) {
+                    // 格式化输入AI参数
+                    ObjectNode requestBody = objectMapper.createObjectNode();
+                    String filled = String.format(
+                            tpl,
+                            dto.getActivityName(),
+                            dto.getStartTime(),
+                            dto.getEndTime(),
+                            dto.getParticipationLimit(),
+                            dto.getActivityRule(),
+                            dto.getCouponQuantity(),
+                            dto.getImgDescribe()
+                    );
+                    requestBody.put("text", filled);
+                    JsonNode imgResponse = alienAIFeign.generatePromotionImage(authorization, requestBody);
+                    // 解析响应
+                    if (imgResponse.has("data")) {
+                        JsonNode data = imgResponse.get("data");
+                        // 提取横向图(banner_image)的图片URL
+                        if (data.has("banner_image")) {
+                            JsonNode bannerImage = data.get("banner_image");
+                            if (bannerImage.has("image_url") && !bannerImage.get("image_url").isNull()) {
+                                String bannerImageUrl = bannerImage.get("image_url").asText();
+                                dto.getActivityTitleImg().setImgUrl(bannerImageUrl);
+                            }
+                        }
+                        // 提取竖向图(vertical_image)的图片URL
+                        if (data.has("vertical_image")) {
+                            JsonNode verticalImage = data.get("vertical_image");
+                            if (verticalImage.has("image_url") && !verticalImage.get("image_url").isNull()) {
+                                String verticalImageUrl = verticalImage.get("image_url").asText();
+                                dto.getActivityDetailImg().setImgUrl(verticalImageUrl);
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // AI调用失败,也可以添加数据
+            log.error("调用AI服务生成促销图片失败", e);
+        }
+//         result = activityMapper.updateById(activity);
 
         // 添加
         if (result > 0) {
@@ -342,7 +481,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
 
     @Override
-    public IPage<StoreOperationalActivityVO> queryActivityList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize) {
+    public IPage<StoreOperationalActivityVO> queryActivityList(Integer storeId, Integer status,String activityType, String activityName, Integer pageNum, Integer pageSize) {
         log.info("OperationalActivityServiceImpl.queryActivityList: storeId={}, status={}, activityName={}, pageNum={}, pageSize={}",
                 storeId, status, activityName, pageNum, pageSize);
 
@@ -350,6 +489,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
         wrapper.eq(storeId != null, StoreOperationalActivity::getStoreId, storeId);
         wrapper.like(activityName != null && activityName != "", StoreOperationalActivity::getActivityName, activityName);
         wrapper.eq(status != null, StoreOperationalActivity::getStatus, status);
+        wrapper.eq(activityType != null, StoreOperationalActivity::getActivityType, activityType);
 
         IPage<StoreOperationalActivity> list = activityMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
 
@@ -377,7 +517,13 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                 vo.setStatusName("已结束");
             }
 
-            vo.setCouponName(lifeDiscountCouponMapper.selectById(activity.getCouponId()).getName());
+            // 设置优惠券名称(判空处理)
+            if (activity.getCouponId() != null) {
+                LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
+                if (coupon != null) {
+                    vo.setCouponName(coupon.getName());
+                }
+            }
 
             voRecords.add(vo);
         }
@@ -433,5 +579,26 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
         return activityMapper.updateById(activity);
     }
+
+    @Override
+    public int uploadActivityResult(StoreOperationalActivityDTO dto) {
+        log.info("OperationalActivityServiceImpl.uploadActivityResult: dto={}", dto);
+
+        if (dto.getId() == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+
+        if (dto.getResultType() == null) {
+            throw new IllegalArgumentException("结果类型不能为空");
+        }
+
+        StoreOperationalActivity activity = new StoreOperationalActivity();
+        activity.setId(dto.getId());
+        activity.setResultType(dto.getResultType());
+        activity.setResultText(dto.getResultText());
+        activity.setResultMediaUrl(dto.getResultMediaUrl());
+
+        return activityMapper.updateById(activity);
+    }
 }
 

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

@@ -0,0 +1,217 @@
+package shop.alien.storeplatform.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.LifeUser;
+import shop.alien.entity.storePlatform.StoreOperationalActivity;
+import shop.alien.entity.storePlatform.StoreOperationalActivitySignup;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupVO;
+import shop.alien.mapper.LifeUserMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivitySignupMapper;
+import shop.alien.storeplatform.service.OperationalActivitySignupService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 运营活动报名服务实现类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class OperationalActivitySignupServiceImpl implements OperationalActivitySignupService {
+
+    private final StoreOperationalActivitySignupMapper signupMapper;
+    private final StoreOperationalActivityMapper activityMapper;
+    private final LifeUserMapper lifeUserMapper;
+
+    @Override
+    public IPage<StoreOperationalActivitySignupVO> querySignupList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize) {
+        log.info("OperationalActivitySignupServiceImpl.querySignupList: storeId={}, status={}, activityName={}, pageNum={}, pageSize={}", 
+                storeId, status, activityName, pageNum, pageSize);
+
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("商户ID不能为空");
+        }
+
+        // 构建查询条件
+        LambdaQueryWrapper<StoreOperationalActivitySignup> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StoreOperationalActivitySignup::getStoreId, storeId);
+        
+        if (status != null) {
+            wrapper.eq(StoreOperationalActivitySignup::getStatus, status);
+        }
+
+        // 如果指定了活动名称,先查询活动ID列表
+        if (activityName != null && !activityName.trim().isEmpty()) {
+            LambdaQueryWrapper<StoreOperationalActivity> activityWrapper = new LambdaQueryWrapper<>();
+            activityWrapper.eq(StoreOperationalActivity::getStoreId, storeId)
+                    .like(StoreOperationalActivity::getActivityName, activityName.trim())
+                    .eq(StoreOperationalActivity::getDeleteFlag, 0);
+            List<StoreOperationalActivity> activities = activityMapper.selectList(activityWrapper);
+            if (activities.isEmpty()) {
+                // 如果没有匹配的活动,返回空结果
+                return new Page<>(pageNum, pageSize);
+            }
+            List<Integer> activityIds = activities.stream()
+                    .map(StoreOperationalActivity::getId)
+                    .collect(Collectors.toList());
+            wrapper.in(StoreOperationalActivitySignup::getActivityId, activityIds);
+        }
+
+        wrapper.orderByDesc(StoreOperationalActivitySignup::getCreatedTime);
+
+        // 分页查询
+        IPage<StoreOperationalActivitySignup> page = new Page<>(pageNum, pageSize);
+        IPage<StoreOperationalActivitySignup> signupPage = signupMapper.selectPage(page, wrapper);
+
+        // 转换为VO
+        IPage<StoreOperationalActivitySignupVO> voPage = new Page<>(pageNum, pageSize);
+        voPage.setTotal(signupPage.getTotal());
+        voPage.setPages(signupPage.getPages());
+
+        List<StoreOperationalActivitySignupVO> voList = signupPage.getRecords().stream().map(signup -> {
+            StoreOperationalActivitySignupVO vo = new StoreOperationalActivitySignupVO();
+            BeanUtils.copyProperties(signup, vo);
+
+            // 查询活动名称和活动类型
+            LambdaQueryWrapper<StoreOperationalActivity> activityWrapper = new LambdaQueryWrapper<>();
+            activityWrapper.eq(StoreOperationalActivity::getId, signup.getActivityId())
+                    .eq(StoreOperationalActivity::getDeleteFlag, 0);
+            StoreOperationalActivity activity = activityMapper.selectOne(activityWrapper);
+            if (activity != null) {
+                vo.setActivityName(activity.getActivityName());
+                vo.setActivityType(activity.getActivityType());
+            }
+
+            // 查询用户昵称
+            if (signup.getUserId() != null) {
+                LifeUser lifeUser = lifeUserMapper.selectById(signup.getUserId());
+                if (lifeUser != null && lifeUser.getUserName() != null) {
+                    vo.setNickName(lifeUser.getUserName());
+                }
+            }
+
+            // 设置状态文字
+            vo.setStatusText(getStatusText(signup.getStatus()));
+
+            return vo;
+        }).collect(Collectors.toList());
+
+        voPage.setRecords(voList);
+        return voPage;
+    }
+
+    @Override
+    public StoreOperationalActivitySignupVO querySignupById(Integer id) {
+        log.info("OperationalActivitySignupServiceImpl.querySignupById: id={}", id);
+
+        if (id == null || id <= 0) {
+            throw new IllegalArgumentException("报名ID不能为空");
+        }
+
+        StoreOperationalActivitySignup signup = signupMapper.selectById(id);
+        if (signup == null) {
+            return null;
+        }
+
+        StoreOperationalActivitySignupVO vo = new StoreOperationalActivitySignupVO();
+        BeanUtils.copyProperties(signup, vo);
+
+        // 查询活动名称和活动类型
+        LambdaQueryWrapper<StoreOperationalActivity> activityWrapper = new LambdaQueryWrapper<>();
+        activityWrapper.eq(StoreOperationalActivity::getId, signup.getActivityId())
+                .eq(StoreOperationalActivity::getDeleteFlag, 0);
+        StoreOperationalActivity activity = activityMapper.selectOne(activityWrapper);
+        if (activity != null) {
+            vo.setActivityName(activity.getActivityName());
+            vo.setActivityType(activity.getActivityType());
+        }
+
+        // 查询用户昵称
+        if (signup.getUserId() != null) {
+            LifeUser lifeUser = lifeUserMapper.selectById(signup.getUserId());
+            if (lifeUser != null && lifeUser.getUserName() != null) {
+                vo.setNickName(lifeUser.getUserName());
+            }
+        }
+
+        // 设置状态文字
+        vo.setStatusText(getStatusText(signup.getStatus()));
+
+        return vo;
+    }
+
+    @Override
+    public int approveSignup(Integer id) {
+        log.info("OperationalActivitySignupServiceImpl.approveSignup: id={}", id);
+
+        if (id == null || id <= 0) {
+            throw new IllegalArgumentException("报名ID不能为空");
+        }
+
+        LambdaUpdateWrapper<StoreOperationalActivitySignup> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(StoreOperationalActivitySignup::getId, id)
+                .set(StoreOperationalActivitySignup::getStatus, 2) // 2-通过
+                .set(StoreOperationalActivitySignup::getAuditTime, new Date()); // 设置审核时间为当前时间
+
+        return signupMapper.update(null, wrapper);
+    }
+
+    @Override
+    public int rejectSignup(Integer id, String rejectReason) {
+        log.info("OperationalActivitySignupServiceImpl.rejectSignup: id={}, rejectReason={}", id, rejectReason);
+
+        if (id == null || id <= 0) {
+            throw new IllegalArgumentException("报名ID不能为空");
+        }
+
+        LambdaUpdateWrapper<StoreOperationalActivitySignup> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(StoreOperationalActivitySignup::getId, id)
+                .set(StoreOperationalActivitySignup::getStatus, 1) // 1-拒绝
+                .set(StoreOperationalActivitySignup::getAuditTime, new Date()); // 设置审核时间为当前时间
+        
+        // 如果提供了拒绝原因,则保存;否则使用默认值
+        if (rejectReason != null && !rejectReason.trim().isEmpty()) {
+            wrapper.set(StoreOperationalActivitySignup::getRejectReason, rejectReason.trim());
+        } else {
+            wrapper.set(StoreOperationalActivitySignup::getRejectReason, "审核未通过");
+        }
+
+        return signupMapper.update(null, wrapper);
+    }
+
+    /**
+     * 获取状态文字
+     *
+     * @param status 状态值
+     * @return 状态文字
+     */
+    private String getStatusText(Integer status) {
+        if (status == null) {
+            return "未知";
+        }
+        switch (status) {
+            case 0:
+                return "待审核";
+            case 1:
+                return "拒绝";
+            case 2:
+                return "通过";
+            default:
+                return "未知";
+        }
+    }
+}
+

+ 2 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformLoginServiceImpl.java

@@ -141,6 +141,8 @@ public class StorePlatformLoginServiceImpl extends ServiceImpl<StoreUserMapper,
                 storeUserVo.setBusinessSection(storeInfo.getBusinessSection());
                 storeUserVo.setBusinessTypesName(storeInfo.getBusinessTypesName());
                 storeUserVo.setMealsFlag(storeInfo.getMealsFlag());
+                storeUserVo.setStoreTickets(storeInfo.getStoreTickets());
+
             }
             return R.data(storeUserVo);
         } catch (Exception e) {

+ 47 - 13
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StorePlatformUserRoleServiceImpl.java

@@ -7,6 +7,8 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import shop.alien.config.redis.BaseRedisService;
+import shop.alien.entity.result.R;
 import shop.alien.entity.store.StorePlatformMenu;
 import shop.alien.entity.store.StorePlatformRole;
 import shop.alien.entity.store.StorePlatformUserRole;
@@ -43,6 +45,7 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
     private final StorePlatformRoleMenuService storePlatformRoleMenuService;
     private final StorePlatformRoleMapper storePlatformRoleMapper;
     private final StorePlatformMenuMapper storePlatformMenuMapper;
+    private final BaseRedisService baseRedisService;
 
     @Override
     public List<Long> getRoleIdsByUserId(Integer userId, Integer storeId) {
@@ -176,14 +179,30 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
                 return false;
             }
 
+
+            StoreUser storeUser = storeUserMapper.selectById(userId);
+            if (storeUser != null && storeUser.getPhone() != null) {
+                // 删除Redis中的token,key格式:storePlatform_手机号
+                String tokenKey = "store_" + storeUser.getPhone();
+                String existingToken = baseRedisService.getString(tokenKey);
+                if (existingToken != null) {
+                    baseRedisService.delete(tokenKey);
+                    log.info("清除角色编辑后的用户token成功, userId={}, phone={}, tokenKey={}",
+                             userId, storeUser.getPhone(), tokenKey);
+                } else {
+                    log.warn("用户token不存在或已过期, userId={}, phone={}, tokenKey={}",
+                             userId, storeUser.getPhone(), tokenKey);
+                }
+            }
+
             // 3. 如果只是一个账号的子账号(删除前只有1个),则需要进一步判断是否为主账号,再决定是否同时逻辑删除 store_user 表
             if (subAccountCount == 1) {
                 // 查询用户信息以判断是否为主账号
-                StoreUser user = storeUserMapper.selectById(userId);
-                if (user != null) {
+
+                if (storeUser != null) {
                     // 如果用户有 storeId 字段值,说明是主账号,不应删除
-                    if (user.getStoreId() != null && user.getStoreId() > 0) {
-                        log.info("用户是主账号,不删除 store_user 记录: userId={}, storeId={}", userId, user.getStoreId());
+                    if (storeUser.getStoreId() != null && storeUser.getStoreId() > 0) {
+                        log.info("用户是主账号,不删除 store_user 记录: userId={}, storeId={}", userId, storeUser.getStoreId());
                     } else {
                         // 不是主账号,可以安全删除
                         LambdaUpdateWrapper<StoreUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
@@ -215,10 +234,10 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
 
 
     @Override
-    public boolean createAccountAndAssignRole(String phone, String accountName, Integer storeId, Long roleId) {
+    public R<String> createAccountAndAssignRole(String phone, String accountName, Integer storeId, Long roleId) {
         if (phone == null || phone.isEmpty() || storeId == null || roleId == null) {
             log.error("参数不能为空: phone={}, storeId={}, roleId={}", phone, storeId, roleId);
-            return false;
+            return R.fail("参数不能为空");
         }
 
         try {
@@ -239,7 +258,7 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
                 
                 if (existingSubAccount != null) {
                     log.error("该手机号在当前店铺已存在子账号,不支持创建: phone={}, storeId={}", phone, storeId);
-                    return false;
+                    return R.fail("当前手机号已经在该店铺存在请勿重复添加");
                 }
             }
             
@@ -253,7 +272,7 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
                 
                 if (existingAccountName != null) {
                     log.error("该账号名称在当前店铺已存在子账号,不支持创建: accountName={}, storeId={}", accountName, storeId);
-                    return false;
+                    return R.fail("当前账号名称已在该店铺存在请勿重复添加");
                 }
             }
 
@@ -267,7 +286,7 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
                 int storeIdNew = storeUser.getStoreId();
                 if (storeIdNew == storeId) {
                     log.error("该手机号为当前当前店铺手机号,请勿重复创建: phone={}, storeId={}", phone, storeId);
-                    return false;
+                    return R.fail("该手机号为当前当前店铺手机号,请勿重复创建");
                 }
             }
 
@@ -292,7 +311,7 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
                 int insertResult = storeUserMapper.insert(newUser);
                 if (insertResult <= 0) {
                     log.error("插入 store_user 表失败: phone={}", phone);
-                    return false;
+                    return R.fail("创建子账号失败");
                 }
                 userId = newUser.getId();
                 log.info("成功创建新用户: phone={}, userId={}", phone, userId);
@@ -321,17 +340,17 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
                 boolean insertRoleResult = this.save(userRole);
                 if (!insertRoleResult) {
                     log.error("插入 store_platform_user_role 表失败: userId={}, storeId={}, roleId={}", userId, storeId, roleId);
-                    return false;
+                    return R.fail("创建子账号失败");
                 }
                 log.info("成功分配角色: userId={}, storeId={}, roleId={}", userId, storeId, roleId);
             } else {
                 log.info("用户角色关系已存在: userId={}, storeId={}, roleId={}", userId, storeId, roleId);
             }
 
-            return true;
+            return R.success("创建子账号成功");
         } catch (Exception e) {
             log.error("创建账号并分配角色失败: phone={}, storeId={}, roleId={}", phone, storeId, roleId, e);
-            return false;
+            return R.fail("创建账号并分配角色失败");
         }
     }
 
@@ -509,6 +528,21 @@ public class StorePlatformUserRoleServiceImpl extends ServiceImpl<StorePlatformU
                     continue; // 继续处理下一个用户
                 }
 
+                StoreUser storeUser = storeUserMapper.selectById(userId);
+                if (storeUser != null && storeUser.getPhone() != null) {
+                    // 删除Redis中的token,key格式:storePlatform_手机号
+                    String tokenKey = "store_" + storeUser.getPhone();
+                    String existingToken = baseRedisService.getString(tokenKey);
+                    if (existingToken != null) {
+                        baseRedisService.delete(tokenKey);
+                        log.info("清除角色编辑后的用户token成功 userId={}, phone={}, tokenKey={}",
+                                 userId, storeUser.getPhone(), tokenKey);
+                    } else {
+                        log.warn("用户token不存在或已过期,userId={}, phone={}, tokenKey={}",
+                                 userId, storeUser.getPhone(), tokenKey);
+                    }
+                }
+
                 log.info("逻辑删除 store_platform_user_role 记录成功: userId={}, storeId={}, 删除数量={}", userId, storeId, result);
 
                 // 3. 判断是否只有一个子账号,如果是则同时逻辑删除 store_user 表

+ 49 - 13
alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import io.swagger.annotations.Api;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.cloud.context.config.annotation.RefreshScope;
 import org.springframework.http.HttpEntity;
@@ -19,23 +20,19 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.client.RestTemplate;
 import shop.alien.entity.result.R;
+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.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.CommonRatingService;
 import shop.alien.store.service.StoreImgService;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
-import java.util.Comparator;
 
 
 @Slf4j
@@ -48,7 +45,8 @@ import java.util.Comparator;
 public class AiSearchController {
 
     private final StoreImgMapper storeImgMapper;
-    
+    private final StoreUserMapper storeUserMapper;
+
     @Value("${third-party-ai-search.exact.base-url:http://124.93.18.180:7870/api/v1/search}")
     private String aiSearchExactUrl;
     
@@ -59,6 +57,8 @@ public class AiSearchController {
     private final StoreImgService storeImgService;
     private final CommonRatingService commonRatingService;
 
+    private final LifeBlacklistMapper lifeBlacklistMapper;
+
     @TrackEvent(
             eventType = "SEARCH",
             eventCategory = "TRAFFIC",
@@ -85,15 +85,15 @@ public class AiSearchController {
 
         HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, null);
         try {
-            
+
             ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(aiSearchExactUrl, request, String.class);
             String body = stringResponseEntity.getBody();
             JSONObject jsonObject = JSONObject.parseObject(body);
             JSONObject jsonObject1 = new JSONObject();
             // 生活服务类别:转换为StoreInfoVo,确保返回的字段名按照StoreInfoVo定义
             // 模糊搜索:从related_results和matched_results字段获取数据
-            List<StoreInfoVo> relatedResult = convertToStoreInfoList(jsonObject.getJSONArray("related_results"));
-            List<StoreInfoVo> matchedResult = convertToStoreInfoList(jsonObject.getJSONArray("matched_results"));
+            List<StoreInfoVo> relatedResult = convertToStoreInfoList(jsonObject.getJSONArray("related_results"),map.get("userId"));
+            List<StoreInfoVo> matchedResult = convertToStoreInfoList(jsonObject.getJSONArray("matched_results"),map.get("userId"));
 
             // 查找图片并设置到result中(图片类型1-入口图)
             fillStoreImages(relatedResult, 1);
@@ -139,7 +139,7 @@ public class AiSearchController {
             JSONObject jsonObject = JSONObject.parseObject(body);
             JSONObject jsonObject1 = new JSONObject();
             // 模糊搜索:从related_results和matched_results字段获取数据
-            List<StoreInfoVo> result = convertToStoreInfoList(jsonObject.getJSONArray("results"));
+            List<StoreInfoVo> result = convertToStoreInfoList(jsonObject.getJSONArray("results"),map.get("userId"));
 
             // 查找图片并设置到result中(图片类型1-入口图)
             fillStoreImages(result, 1);
@@ -159,8 +159,23 @@ public class AiSearchController {
         return  R.fail("请求失败");
     }
 
-    private List<StoreInfoVo> convertToStoreInfoList(JSONArray results) {
+    private List<StoreInfoVo> convertToStoreInfoList(JSONArray results, String  userId) {
+        // 查找用户拉黑的商户
+        QueryWrapper<LifeBlacklist> queryWrapper = new QueryWrapper<LifeBlacklist>();
+        queryWrapper.eq("blocker_id",userId);
+        queryWrapper.eq("blocker_type",2);
+        queryWrapper.eq("blocked_type",1);
+        List<LifeBlacklist> lifeBlacklists = lifeBlacklistMapper.selectList(queryWrapper);
+        // 坑:查询出来的是拉黑的商户id,不是商铺id 😊彻底疯狂
+        List<String> blockedIds = lifeBlacklists.stream().map(x -> x.getBlockedId()).collect(Collectors.toList());
+        List<Integer> collect = new ArrayList<>();
+        if(blockedIds.size()>0){
+            List<StoreUser> storeUsers = storeUserMapper.selectBatchIds(blockedIds);
+            collect = storeUsers.stream().filter(x -> StringUtils.isNotBlank(x.getStoreId().toString())).map(x -> x.getStoreId()).collect(Collectors.toList());
+        }
+
         List<StoreInfoVo> storeInfoList = new ArrayList<>();
+
         if (results != null) {
             for (int i = 0; i < results.size(); i++) {
                 JSONObject item = results.getJSONObject(i);
@@ -183,6 +198,27 @@ public class AiSearchController {
 
                 // 使用JSON.parseObject方法进行转换
                 StoreInfoVo storeInfo = JSON.parseObject(camelCaseItem.toJSONString(), StoreInfoVo.class);
+                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());
                 storeInfoList.add(storeInfo);
             }
         }

+ 218 - 0
alien-store/src/main/java/shop/alien/store/controller/AiTagsController.java

@@ -0,0 +1,218 @@
+package shop.alien.store.controller;
+
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.http.*;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.CommonRating;
+import shop.alien.store.service.CommonRatingService;
+import shop.alien.store.util.ai.AiAuthTokenUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+@Api(tags = {"ai搜索标签"})
+@CrossOrigin
+@RestController
+@RequestMapping("/aiTags")
+@RequiredArgsConstructor
+@RefreshScope
+public class AiTagsController {
+
+    @Value("${third-part-ai-tags.tags-url:http://124.93.18.180:9000/ai/intelligent-analysis/api/v1/tag/get_store_level_tag}")
+    private String aiTagsUrl;
+
+    @Value("${third-part-ai-tags.tags-rating-url:http://124.93.18.180:9000/ai/intelligent-analysis/api/v1/tag/get_ratings_by_tag}")
+    private String tagsRatingUrl;
+
+    private static final Pattern PATTERN = Pattern.compile("([^()]+)\\((\\d+)\\)");
+
+    private final RestTemplate restTemplate;
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+    private final CommonRatingService commonRatingService;
+    /**
+     * 获取店铺标签
+     *
+     * @param storeId 店铺ID
+     * @param page    页码
+     * @param pageNum 每页数量
+     * @return 店铺标签列表
+     */
+    @ApiOperation("获取店铺标签")
+    @RequestMapping("/storeTags")
+    public R storeTags(@RequestParam("storeId") Long storeId,
+                       @RequestParam(value = "page", defaultValue = "1") Integer page,
+                       @RequestParam(value = "pageNum", defaultValue = "10") Integer pageNum) {
+        try {
+            // 1. 获取访问令牌(如果外部接口需要,可添加到请求头)
+            String accessToken = aiAuthTokenUtil.getAccessToken();
+
+            // 2. 构建请求头
+            HttpHeaders aiHeaders = new HttpHeaders();
+            aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+            // 如果外部接口需要 token 认证,添加到请求头(根据实际需求调整)
+            if (accessToken != null && !accessToken.isEmpty()) {
+                aiHeaders.set("Authorization", "Bearer " + accessToken);
+            }
+
+            // 3. 拼接请求 URL 和参数(重点:按外部接口格式拼接)
+            // 使用 String.format 拼接参数,注意参数名对应:page_size 而非 pageNum
+            String requestUrl = String.format("%s?store_id=%d&page=%d&page_size=%d",
+                    aiTagsUrl, storeId, page, pageNum);
+
+            HttpEntity<Void> requestEntity = new HttpEntity<>(aiHeaders);
+            ResponseEntity<String> exchange = restTemplate.exchange(requestUrl, HttpMethod.GET, requestEntity, String.class);
+//            ResponseEntity<String> responseEntity = restTemplate.exchange(requestUrl,"GET",requestEntity,String.class);
+            if(exchange.getStatusCode().is2xxSuccessful()){
+                // 4. 解析响应体(根据外部接口返回格式调整)
+                String responseBody = exchange.getBody();
+                JSONObject jsonObject = JSONObject.parseObject(responseBody);
+                JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("list");
+                if(jsonArray.isEmpty()){
+                    return R.data(new ArrayList<>());
+                }
+                String negativeComment = jsonArray.getJSONObject(0).getString("negative_comment");
+                // 这里简单返回原始字符串(根据实际情况调整)
+                return R.data(parseComment(negativeComment));
+            } else {
+                return R.fail("获取店铺标签失败:" + exchange.getStatusCode());
+            }
+        } catch (Exception e) {
+            // 补充异常处理:捕获调用外部接口的异常,返回友好提示
+            e.printStackTrace(); // 生产环境建议用日志框架记录
+            return R.fail("获取店铺标签失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 根据标签获取店铺评分
+     *
+     * @param storeId 店铺ID
+     * @param tag     标签(如:商品质量差)
+     * @param page    页码
+     * @param pageNum 每页数量(对应外部接口的page_size)
+     * @return 店铺评分列表
+     */
+    @ApiOperation("根据标签获取店铺评价")
+    @RequestMapping("/storeRatingsByTag")
+    public R storeRatingsByTag(@RequestParam("storeId") Long storeId,
+                               @RequestParam("tag") String tag,  // 新增标签参数,必填
+                               @RequestParam(value = "page", defaultValue = "1") Integer page,
+                               @RequestParam(value = "pageNum", defaultValue = "10") Integer pageNum) {
+        try {
+            // 1. 获取访问令牌(复用原有逻辑)
+            String accessToken = aiAuthTokenUtil.getAccessToken();
+
+            // 2. 构建请求头
+            HttpHeaders aiHeaders = new HttpHeaders();
+            aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+            // 添加token认证(如有需要)
+            if (accessToken != null && !accessToken.isEmpty()) {
+                aiHeaders.set("Authorization", "Bearer " + accessToken);
+            }
+
+            // 3. 拼接请求URL和参数(核心调整:新增tag参数,匹配外部接口参数名)
+            String requestUrl = String.format("%s?store_id=%d&tag=%s&page=%d&page_size=%d",
+                    tagsRatingUrl, storeId, tag, page, pageNum);
+
+            // 4. 发送GET请求
+            HttpEntity<Void> requestEntity = new HttpEntity<>(aiHeaders);
+            ResponseEntity<String> exchange = restTemplate.exchange(requestUrl, HttpMethod.GET, requestEntity, String.class);
+
+            // 5. 处理响应结果
+            if (exchange.getStatusCode().is2xxSuccessful()) {
+                String responseBody = exchange.getBody();
+                JSONObject jsonObject = JSONObject.parseObject(responseBody);
+                JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("list");
+                if(jsonArray.isEmpty()){
+                    return R.data(new ArrayList<>());
+                }
+                // 2. 提取list中的数值(这里list是数值数组,而非对象数组)
+                List<Long> idList = new ArrayList<>();
+                for (int i = 0; i < jsonArray.size(); i++) {
+                    // 直接获取数值并转为Long类型
+                    Object value = jsonArray.get(i);
+                    if (value instanceof Number) {
+                        idList.add(((Number) value).longValue());
+                    }
+                }
+                Page<CommonRating> page1 = new Page<>(page, pageNum);
+                QueryWrapper<CommonRating> wrapper = new QueryWrapper<>();
+                wrapper.in("id",idList);
+                IPage<CommonRating> page2 = commonRatingService.page(page1, wrapper);
+                // 保持和原有接口一致的返回格式:将data节点重新封装(可根据实际需求调整)
+                return commonRatingService.doListBusinessWithType(page2, 1, null, null);
+            } else {
+                return R.fail("获取店铺评价失败:" + exchange.getStatusCode());
+            }
+        } catch (Exception e) {
+            // 异常日志记录(生产环境建议替换为日志框架,如logback/log4j2)
+            e.printStackTrace();
+            return R.fail("获取店铺评分失败:" + e.getMessage());
+        }
+    }
+
+    public static List<JSONObject> parseComment(String negativeComment) {
+        // 最终返回的结果列表
+        List<JSONObject> resultList = new ArrayList<>();
+        // 临时存储标签-数值的映射,用于累加相同标签的数值
+        Map<String, Integer> tagCountMap = new HashMap<>();
+
+        // 1. 空值校验:避免空字符串/Null导致后续处理异常
+        if (negativeComment == null || negativeComment.trim().isEmpty()) {
+            return resultList;
+        }
+
+        // 2. 按分号;分割成多个片段(如:["差评(1)", "商品质量差(1),商品质量差(1)"])
+        String[] semicolonParts = negativeComment.split(";");
+        String trimPart = semicolonParts[1].trim();
+
+            // 3. 按逗号,分割当前片段内的多个标签(如:["商品质量差(1)", "商品质量差(1)"])
+            String[] commaParts = trimPart.split(",");
+            for (String tagWithNum : commaParts) {
+                String trimTag = tagWithNum.trim();
+                if (trimTag.isEmpty()) {
+                    continue;
+                }
+
+                // 4. 正则匹配标签和数值(如:"差评(1)" → 标签=差评,数值=1)
+                Matcher matcher = PATTERN.matcher(trimTag);
+                if (matcher.find()) {
+                    String label = matcher.group(1).trim(); // 标签(如:差评)
+                    int value = Integer.parseInt(matcher.group(2)); // 数值(如:1)
+
+                    // 5. 累加相同标签的数值
+                    tagCountMap.put(label, tagCountMap.getOrDefault(label, 0) + value);
+                }
+            }
+
+        // 6. 将Map转换为指定的List<JSONObject>格式
+        for (Map.Entry<String, Integer> entry : tagCountMap.entrySet()) {
+            JSONObject jsonObj = new JSONObject();
+            jsonObj.put("label", entry.getKey());
+            jsonObj.put("value", String.valueOf(entry.getValue())); // value转为字符串,匹配示例格式
+            resultList.add(jsonObj);
+        }
+
+        return resultList;
+    }
+}

+ 106 - 0
alien-store/src/main/java/shop/alien/store/controller/FeedbackEmailController.java

@@ -0,0 +1,106 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.*;
+import lombok.Data;
+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.store.util.ai.AiFeedbackEmailUtil;
+
+/**
+ * 邮件分发Controller
+ * 提供反馈邮件发送接口
+ *
+ * @author system
+ * @date 2025-01-20
+ */
+@Slf4j
+@Api(tags = {"邮件分发接口"})
+@CrossOrigin
+@RestController
+@RequestMapping("/feedback-email")
+@RequiredArgsConstructor
+public class FeedbackEmailController {
+
+    private final AiFeedbackEmailUtil aiFeedbackEmailUtil;
+
+    @ApiOperation(value = "发送反馈邮件", httpMethod = "POST")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/send")
+    public R<EmailSendResponse> sendFeedbackEmail(@RequestBody FeedbackEmailRequest request) {
+        log.info("FeedbackEmailController.sendFeedbackEmail, request={}", request);
+
+        // 参数校验
+        if (request == null || !StringUtils.hasText(request.getFeedbackContent())) {
+            log.error("反馈内容不能为空");
+            return R.fail("反馈内容不能为空");
+        }
+
+        try {
+            // 调用邮件发送工具类
+            AiFeedbackEmailUtil.EmailSendResult result = aiFeedbackEmailUtil.sendFeedbackEmail(
+                    request.getFeedbackType(),
+                    request.getFeedbackContent(),
+                    request.getUserId(),
+                    request.getFeedbackSource(),
+                    request.getContactWay()
+            );
+
+            if (result.isSuccess()) {
+                EmailSendResponse response = new EmailSendResponse();
+                response.setSuccess(true);
+                response.setMessage(result.getMessage());
+                response.setTicketId(result.getTicketId());
+                log.info("邮件发送成功:ticketId={}", result.getTicketId());
+                return R.data(response, "邮件发送成功");
+            } else {
+                log.warn("邮件发送失败:{}", result.getMessage());
+                return R.fail(result.getMessage());
+            }
+        } catch (Exception e) {
+            log.error("发送反馈邮件异常", e);
+            return R.fail("发送邮件异常:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 反馈邮件请求DTO
+     */
+    @Data
+    @ApiModel(value = "FeedbackEmailRequest", description = "反馈邮件请求参数")
+    public static class FeedbackEmailRequest {
+        @ApiModelProperty(value = "问题反馈类型(0-bug反馈,1-优化反馈,2-新增功能反馈)", example = "0")
+        private String feedbackType;
+
+        @ApiModelProperty(value = "反馈内容(必填)", required = true, example = "测试bug反馈邮件功能")
+        private String feedbackContent;
+
+        @ApiModelProperty(value = "用户ID(可选)", example = "1001")
+        private Integer userId;
+
+        @ApiModelProperty(value = "反馈来源(可选,0,1)", example = "0")
+        private String feedbackSource;
+
+        @ApiModelProperty(value = "联系方式(可选)", example = "test@example.com")
+        private String contactWay;
+    }
+
+    /**
+     * 邮件发送响应DTO
+     */
+    @Data
+    @ApiModel(value = "EmailSendResponse", description = "邮件发送响应结果")
+    public static class EmailSendResponse {
+        @ApiModelProperty(value = "是否成功")
+        private Boolean success;
+
+        @ApiModelProperty(value = "响应消息")
+        private String message;
+
+        @ApiModelProperty(value = "工单ID")
+        private Long ticketId;
+    }
+}
+

+ 11 - 6
alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponStoreFriendController.java

@@ -54,12 +54,12 @@ public class LifeDiscountCouponStoreFriendController {
         }
     }
 
-    @TrackEvent(
-            eventType = "COUPON_GIVE",
-            eventCategory = "COUPON",
-            storeId = "#{#lifeDiscountCouponStoreFriendDto.storeId}",
-            targetType = "COUPON"
-    )
+//    @TrackEvent(
+//            eventType = "COUPON_GIVE",
+//            eventCategory = "COUPON",
+//            storeId = "#{#lifeDiscountCouponStoreFriendDto.storeId}",
+//            targetType = "COUPON"
+//    )
     @ApiOperation("给好友发放优惠券")
     @ApiOperationSupport(order = 2)
     @PostMapping("/setFriendCoupon")
@@ -185,5 +185,10 @@ public class LifeDiscountCouponStoreFriendController {
     private R<List<LifeDiscountCouponFriendRuleVo>> getReceivedSendFriendCouponList(@RequestParam(value = "storeUserId",required = false) String storeUserId, @RequestParam(value = "friendStoreUserId",required = false)String friendStoreUserId, @RequestParam(value = "storeName",required = false)String storeName) {
         log.info("LifeDiscountCouponStoreFriendController.getReceivedSendFriendCouponList?storeId={},friendStoreUserId={},storeName={}", storeUserId,friendStoreUserId,storeName);
         return R.data(lifeDiscountCouponStoreFriendService.getReceivedSendFriendCouponList(storeUserId,friendStoreUserId,storeName));
+
+
+
+
+
     }
 }

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

@@ -63,7 +63,7 @@ public class OperationalActivityController {
             @RequestParam(value = "activityName", required = false) String activityName) {
         log.info("OperationalActivityController.pageActivityDetail storeId={}, storeName={}, pageNum={}, pageSize={}, status={}, activityName={}", storeId, storeName, pageNum, pageSize, status, activityName);
         try {
-            IPage<StoreOperationalActivityVO> result = activityService.pageActivityDetail(storeId, storeName, pageNum, pageSize, status, activityName);
+            IPage<StoreOperationalActivityVO> result = activityService.pageActivityDetail(storeId, storeName, pageNum, pageSize, status, activityName, null);
             return R.data(result);
         } catch (IllegalArgumentException e) {
             return R.fail(e.getMessage());

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

@@ -16,6 +16,7 @@ import shop.alien.store.service.StoreOfficialAlbumService;
 import shop.alien.store.util.GroupConstant;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * 二期-门店图片Controller
@@ -238,7 +239,20 @@ public class StoreImgController {
     @PostMapping("/delete")
     public R<String> delete(@RequestBody List<Integer> ids) {
         log.info("StoreImgController.delete?ids={}", ids);
-        if (storeImgService.removeByIds(ids)) {
+        // 参数校验:ID列表不能为空
+        if (ids == null || ids.isEmpty()) {
+            log.warn("删除图片失败,ID列表为空");
+            return R.fail("删除失败:ID列表不能为空");
+        }
+        // 过滤掉null值和无效ID
+        List<Integer> validIds = ids.stream()
+                .filter(id -> id != null && id > 0)
+                .collect(Collectors.toList());
+        if (validIds.isEmpty()) {
+            log.warn("删除图片失败,没有有效的ID");
+            return R.fail("删除失败:没有有效的ID");
+        }
+        if (storeImgService.removeByIds(validIds)) {
             return R.success("删除成功");
         }
         return R.fail("删除失败");

+ 79 - 13
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -24,10 +24,12 @@ import shop.alien.entity.store.vo.*;
 import shop.alien.entity.storePlatform.StoreLicenseHistory;
 import shop.alien.mapper.*;
 import shop.alien.mapper.storePlantform.StoreLicenseHistoryMapper;
-import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.service.StoreInfoService;
 import shop.alien.store.service.StoreQualificationService;
+import shop.alien.entity.store.UserLoginInfo;
+import shop.alien.util.common.TokenInfo;
+import springfox.documentation.annotations.ApiIgnore;
 
 import java.io.IOException;
 import java.util.*;
@@ -212,6 +214,37 @@ public class StoreInfoController {
         return R.data(storeInfoService.getDetail(id));
     }
 
+    @ApiOperation(value = "门店详细信息(通过商家手机号)")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({@ApiImplicitParam(name = "phone", value = "商家手机号", dataType = "String", paramType = "query", required = true)})
+    @GetMapping("/getDetailByPhone")
+    public R<StoreMainInfoVo> getDetailByPhone(@RequestParam("phone") String phone) {
+        log.info("StoreInfoController.getDetailByPhone?phone={}", phone);
+        try {
+            // 根据手机号查询 store_user
+            LambdaQueryWrapper<StoreUser> userWrapper = new LambdaQueryWrapper<>();
+            userWrapper.eq(StoreUser::getPhone, phone)
+                      .eq(StoreUser::getDeleteFlag, 0);
+            StoreUser storeUser = storeUserMapper.selectOne(userWrapper);
+            
+            if (storeUser == null) {
+                log.warn("未找到手机号对应的商家用户: phone={}", phone);
+                return R.fail("未找到该手机号对应的商家");
+            }
+            
+            // 如果 store_id 不为空,则使用该 store_id 查询门店详细信息
+            if (storeUser.getStoreId() != null && storeUser.getStoreId() > 0) {
+                return R.data(storeInfoService.getDetail(storeUser.getStoreId()));
+            } else {
+                log.warn("商家用户的 store_id 为空: phone={}, userId={}", phone, storeUser.getId());
+                return R.fail("该商家未关联门店");
+            }
+        } catch (Exception e) {
+            log.error("根据手机号查询门店详细信息失败: phone={}", phone, e);
+            return R.fail("查询失败: " + e.getMessage());
+        }
+    }
+
     @ApiOperation(value = "门店信息-修改后展示")
     @ApiOperationSupport(order = 6)
     @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "门店id", dataType = "Long", paramType = "query", required = true)})
@@ -460,6 +493,26 @@ public class StoreInfoController {
         }
     }
 
+    @ApiOperation(value = "web端人工复核举报")
+    @ApiImplicitParams({@ApiImplicitParam(name = "reportId", value = "举报ID", dataType = "int", paramType = "query"),
+            @ApiImplicitParam(name = "type", value = "类型(状态(0:待处理,1:已通过,2:已驳回))", dataType = "int", paramType = "query")})
+    @GetMapping("/manualReviewReport")
+    public R<Boolean> manualReviewReport(@RequestParam("reportId") Integer reportId, @RequestParam("type") Integer type) {
+        log.info("StoreInfoController.manualReviewReport?reportId={}&type={}", reportId, type);
+        try {
+            boolean success = storeInfoService.manualReviewReport(reportId,type);
+            if (success) {
+                return R.success("人工复核保存成功");
+            }
+            return R.fail("人工复核保存失败");
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreInfoController.manualReview ERROR", e);
+            return R.fail("人工复核失败");
+        }
+    }
+
     @ApiOperation(value = "中台web端获取店铺明细详情")
     @ApiOperationSupport(order = 21)
     @GetMapping("/getNewStoreDetail")
@@ -1481,6 +1534,7 @@ public class StoreInfoController {
     })
     @GetMapping("/getRandomPage")
     public R<IPage<StoreInfoWithHeadImg>> getRandomPage(
+            @ApiIgnore @TokenInfo UserLoginInfo userLoginInfo,
             @RequestParam(defaultValue = "1") int page,
             @RequestParam(defaultValue = "10") int size) {
         log.info("StoreInfoController.getRandomPage - page={}, size={}", page, size);
@@ -1489,20 +1543,32 @@ public class StoreInfoController {
             int pageNum = page > 0 ? page : 1;
             int pageSize = size > 0 ? size : 10;
             
-            // 查询所有被拉黑的店铺ID
-            LambdaQueryWrapper<LifeBlacklist> blacklistWrapper = new LambdaQueryWrapper<>();
-            blacklistWrapper.eq(LifeBlacklist::getBlockedType, "1") // 1表示商户
-                           .eq(LifeBlacklist::getDeleteFlag, 0);
-            List<LifeBlacklist> blacklist = lifeBlacklistMapper.selectList(blacklistWrapper);
+            // 查询当前登录用户拉黑的店铺ID
             List<Integer> blacklistedStoreIds = new ArrayList<>();
-            if (!CollectionUtils.isEmpty(blacklist)) {
-                for (LifeBlacklist item : blacklist) {
-                    try {
-                        if (item.getBlockedId() != null && !item.getBlockedId().trim().isEmpty()) {
-                            blacklistedStoreIds.add(Integer.parseInt(item.getBlockedId()));
+            if (userLoginInfo != null && userLoginInfo.getUserId() > 0) {
+                // 获取当前登录用户的类型和ID
+                String blockerType = userLoginInfo.getType(); // "user" 或 "store"
+                String blockerId = String.valueOf(userLoginInfo.getUserId());
+                
+                // 根据用户类型设置拉黑方类型(1:商户,2:用户)
+                String blockerTypeValue = "user".equals(blockerType) ? "2" : "1";
+                
+                LambdaQueryWrapper<LifeBlacklist> blacklistWrapper = new LambdaQueryWrapper<>();
+                blacklistWrapper.eq(LifeBlacklist::getBlockerType, blockerTypeValue) // 拉黑方类型
+                               .eq(LifeBlacklist::getBlockerId, blockerId) // 拉黑方ID(当前登录用户)
+                               .eq(LifeBlacklist::getBlockedType, "1") // 1表示商户
+                               .eq(LifeBlacklist::getDeleteFlag, 0);
+                List<LifeBlacklist> blacklist = lifeBlacklistMapper.selectList(blacklistWrapper);
+                
+                if (!CollectionUtils.isEmpty(blacklist)) {
+                    for (LifeBlacklist item : blacklist) {
+                        try {
+                            if (item.getBlockedId() != null && !item.getBlockedId().trim().isEmpty()) {
+                                blacklistedStoreIds.add(Integer.parseInt(item.getBlockedId()));
+                            }
+                        } catch (NumberFormatException e) {
+                            log.warn("拉黑记录中的店铺ID格式错误: {}", item.getBlockedId());
                         }
-                    } catch (NumberFormatException e) {
-                        log.warn("拉黑记录中的店铺ID格式错误: {}", item.getBlockedId());
                     }
                 }
             }

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

@@ -34,8 +34,19 @@ public class StoreOfficialAlbumController {
     @PostMapping("/createOrUpdateOfficialAlbum")
     public R<StoreOfficialAlbum> createOrUpdateOfficialAlbum(@RequestBody StoreOfficialAlbum storeOfficialAlbum) {
         log.info("StoreOfficialAlbumController.createOfficialAlbum?storeOfficialAlbum={}", storeOfficialAlbum);
-        StoreOfficialAlbum storeOfficial = storeOfficialAlbumService.createOrUpdateOfficialAlbum(storeOfficialAlbum);
-        return R.data(storeOfficial);
+        try {
+            StoreOfficialAlbum storeOfficial = storeOfficialAlbumService.createOrUpdateOfficialAlbum(storeOfficialAlbum);
+            return R.data(storeOfficial);
+        } catch (IllegalArgumentException e) {
+            log.warn("创建/更新官方相册失败,参数错误:{}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (RuntimeException e) {
+            log.error("创建/更新官方相册失败,业务错误:{}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("创建/更新官方相册失败,系统异常:{}", e.getMessage(), e);
+            return R.fail("操作失败:" + e.getMessage());
+        }
     }
 
     @ApiOperation("获取官方相册列表")

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

@@ -0,0 +1,301 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.storePlatform.vo.*;
+import shop.alien.store.dto.StoreOperationalActivityAchievementDto;
+import shop.alien.store.dto.StoreOperationalActivityCaseQueryDto;
+import shop.alien.store.dto.StoreOperationalActivitySignupDto;
+import shop.alien.store.service.StoreOperationalActivityAchievementService;
+import shop.alien.store.service.StoreOperationalActivityService;
+
+import java.util.List;
+
+/**
+ * 运营活动详情控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"用户端-运营活动详情"})
+@ApiSort(11)
+@CrossOrigin
+@RestController
+@RequestMapping("/storeOperationalActivity")
+@RequiredArgsConstructor
+public class StoreOperationalActivityController {
+
+    private final StoreOperationalActivityService operationalActivityService;
+    private final StoreOperationalActivityAchievementService achievementService;
+
+    @ApiOperation("查询活动详情")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @GetMapping("/detail")
+    public R<StoreOperationalActivityDetailVo> getActivityDetail(@RequestParam("id") Integer id,
+                                                                 @RequestParam("userId") Integer userId) {
+        /*
+         * 活动详情接口:
+         * 1. id 与 userId 必传,分别用于活动查询与用户报名状态判断。
+         * 2. 业务计算在服务层完成,控制器仅做参数透传与异常转换。
+         */
+        log.info("StoreOperationalActivityController.getActivityDetail id={}, userId={}", id, userId);
+        try {
+            StoreOperationalActivityDetailVo result = operationalActivityService.getActivityDetail(id, userId);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.getActivityDetail ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("服务中台-查询活动详情")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @GetMapping("/platform/detail")
+    public R<StoreOperationalActivityDetailVo> getActivityDetailForPlatform(@RequestParam("id") Integer id,
+                                                                             @RequestParam("userId") Integer userId) {
+        /*
+         * 服务中台活动详情接口:
+         * 1. id 与 userId 必传,分别用于活动查询与用户报名状态判断。
+         * 2. 业务计算在服务层完成,控制器仅做参数透传与异常转换。
+         */
+        log.info("StoreOperationalActivityController.getActivityDetailForPlatform id={}, userId={}", id, userId);
+        try {
+            StoreOperationalActivityDetailVo result = operationalActivityService.getActivityDetailForPlatform(id, userId);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.getActivityDetailForPlatform ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+
+    @ApiOperation("报名校验")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @GetMapping("/signup/check")
+    public R<StoreOperationalActivitySignupCheckVo> checkSignup(@RequestParam("activityId") Integer activityId,
+                                                                @RequestParam("userId") Integer userId) {
+        log.info("StoreOperationalActivityController.checkSignup activityId={}, userId={}", activityId, userId);
+        try {
+            StoreOperationalActivitySignupCheckVo result = operationalActivityService.checkSignup(activityId, userId);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.checkSignup ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("活动报名")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/signup")
+    public R<String> signup(@RequestBody StoreOperationalActivitySignupDto dto) {
+        log.info("StoreOperationalActivityController.signup dto={}", dto);
+        try {
+            boolean result = operationalActivityService.signup(dto);
+            return result ? R.success("报名成功") : R.fail("报名失败");
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.signup ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("新增成果")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/achievement/add")
+    public R<String> addAchievement(@RequestBody StoreOperationalActivityAchievementDto dto) {
+        log.info("StoreOperationalActivityController.addAchievement dto={}", dto);
+        try {
+            boolean result = achievementService.addAchievement(dto);
+            return result ? R.success("新增成功") : R.fail("新增失败");
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.addAchievement ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("成果详情")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParam(name = "id", value = "成果ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    @GetMapping("/achievement/detail")
+    public R<StoreOperationalActivityAchievementVo> getAchievementDetail(@RequestParam("id") Integer id) {
+        log.info("StoreOperationalActivityController.getAchievementDetail id={}", id);
+        try {
+            StoreOperationalActivityAchievementVo result = achievementService.getAchievementDetail(id);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.getAchievementDetail ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("成果列表")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "signupId", value = "报名ID", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "当前页", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "每页条数", dataTypeClass = Integer.class, paramType = "query")
+    })
+    @GetMapping("/achievement/list")
+    public R<IPage<StoreOperationalActivityAchievementVo>> listAchievements(
+            @RequestParam(value = "activityId", required = false) Integer activityId,
+            @RequestParam(value = "userId", required = false) Integer userId,
+            @RequestParam(value = "signupId", required = false) Integer signupId,
+            @RequestParam(value = "pageNum", required = false) Integer pageNum,
+            @RequestParam(value = "pageSize", required = false) Integer pageSize) {
+        log.info("StoreOperationalActivityController.listAchievements activityId={}, userId={}, signupId={}, pageNum={}, pageSize={}",
+                activityId, userId, signupId, pageNum, pageSize);
+        try {
+            IPage<StoreOperationalActivityAchievementVo> result =
+                    achievementService.listAchievements(activityId, userId, signupId, pageNum, pageSize);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.listAchievements ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("案例列表")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "activityStatus", value = "活动状态", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "当前页", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "每页条数", dataTypeClass = Integer.class, paramType = "query")
+    })
+    @GetMapping("/achievement/case/list")
+    public R<IPage<StoreOperationalActivityAchievementCaseVo>> listCasePage(
+            @RequestParam(value = "activityStatus", required = false) Integer activityStatus,
+            @RequestParam(value = "pageNum", required = false) Integer pageNum,
+            @RequestParam(value = "pageSize", required = false) Integer pageSize) {
+        log.info("StoreOperationalActivityController.listCasePage activityStatus={}, pageNum={}, pageSize={}",
+                activityStatus, pageNum, pageSize);
+        try {
+            IPage<StoreOperationalActivityAchievementCaseVo> result =
+                    achievementService.listCasePage(activityStatus, pageNum, pageSize);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.listCasePage ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("案例详情")
+    @ApiOperationSupport(order = 8)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @GetMapping("/achievement/case/detail")
+    public R<StoreOperationalActivityAchievementCaseDetailVo> getCaseDetail(@RequestParam("activityId") Integer activityId,
+                                                                            @RequestParam("userId") Integer userId) {
+        log.info("StoreOperationalActivityController.getCaseDetail activityId={}, userId={}", activityId, userId);
+        try {
+            StoreOperationalActivityAchievementCaseDetailVo result =
+                    achievementService.getCaseDetail(activityId, userId);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.getCaseDetail ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("我的报名列表")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    @GetMapping("/signup/my")
+    public R<List<StoreOperationalActivityMySignupVo>> listMySignups(@RequestParam("userId") Integer userId) {
+        /*
+         * 我的报名列表接口:
+         * 1. userId 必传,作为报名列表与状态计算的唯一依据。
+         * 2. 由服务层负责聚合活动信息、报名审核状态、成果状态等字段。
+         * 3. 接口仅做参数透传与异常转换,避免在控制器重复业务判断。
+         */
+        try {
+            List<StoreOperationalActivityMySignupVo> result = operationalActivityService.listMySignups(userId);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.listMySignups ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("删除报名及成果")
+    @ApiOperationSupport(order = 10)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "signupId", value = "报名ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @PostMapping("/signup/delete")
+    public R<String> deleteSignup(@RequestParam("activityId") Integer activityId,
+                                  @RequestParam("signupId") Integer signupId) {
+        /*
+         * 删除报名及成果接口:
+         * 1. 入参为活动ID与报名ID,必须匹配同一条报名记录。
+         * 2. 服务层校验报名是否存在且未删除。
+         * 3. 同时逻辑删除报名与关联成果记录。
+         */
+        try {
+            boolean result = operationalActivityService.deleteSignup(activityId, signupId);
+            return result ? R.success("删除成功") : R.fail("删除失败");
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.deleteSignup ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("商家端案例列表")
+    @ApiOperationSupport(order = 10)
+    @PostMapping("/achievement/case/store/list")
+    public R<IPage<StoreOperationalActivityAchievementCaseVo>> listStoreCasePage(
+            @RequestBody StoreOperationalActivityCaseQueryDto dto) {
+        log.info("StoreOperationalActivityController.listStoreCasePage dto={}", dto);
+        try {
+            if (dto == null || dto.getStoreId() == null || dto.getStoreId() <= 0) {
+                return R.fail("商户ID不能为空");
+            }
+            IPage<StoreOperationalActivityAchievementCaseVo> result = achievementService.listStoreCasePage(
+                    dto.getStoreId(), dto.getHasResult(), dto.getActivityName(), dto.getPageNum(), dto.getPageSize());
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.listStoreCasePage ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}

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

@@ -419,6 +419,7 @@ public class StoreUserController {
 
     /**
      * 手动删除商家账号及店铺
+     *  进行校验
      */
     @ApiOperation("手动删除商家账号及店铺")
     @PostMapping("/deleteStoreAccountInfo")

+ 12 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreVideoController.java

@@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreVideo;
+import shop.alien.entity.store.dto.StoreVideoSaveDto;
 import shop.alien.store.service.StoreVideoService;
 
 import java.util.List;
@@ -29,6 +30,17 @@ import java.util.List;
 public class StoreVideoController {
     private final StoreVideoService storeVideoService;
 
+    @ApiOperation("保存视频(单个或批量)")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/saveOrSaveBatch")
+    public R<String> saveOrSaveBatch(@RequestBody StoreVideoSaveDto dto) {
+        log.info("StoreVideoController.saveOrSaveBatch?dto={}", dto);
+        if (storeVideoService.saveOrSaveBatch(dto)) {
+            return R.success("保存成功");
+        }
+        return R.fail("保存失败");
+    }
+
     @ApiOperation("新增视频")
     @ApiOperationSupport(order = 1)
     @PostMapping("/save")

+ 23 - 0
alien-store/src/main/java/shop/alien/store/controller/TrackEventController.java

@@ -112,6 +112,27 @@ public class TrackEventController {
         }
     }
 
+    @ApiOperation("统计店铺浏览量")
+    @ApiOperationSupport(order = 3)
+    @GetMapping("/statistics/viewCount")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "店铺ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    public R<Long> getStoreViewCount(@RequestParam("storeId") Integer storeId) {
+        try {
+            if (storeId == null || storeId <= 0) {
+                return R.fail("店铺ID不能为空且必须大于0");
+            }
+            
+            Long viewCount = trackEventService.getStoreViewCount(storeId);
+            log.info("查询店铺浏览量: storeId={}, viewCount={}", storeId, viewCount);
+            return R.data(viewCount);
+        } catch (Exception e) {
+            log.error("统计店铺浏览量失败: storeId={}", storeId, e);
+            return R.fail("统计店铺浏览量失败: " + e.getMessage());
+        }
+    }
+
     /**
      * 获取客户端IP地址
      */
@@ -138,4 +159,6 @@ public class TrackEventController {
         }
         return ip;
     }
+
+
 }

+ 31 - 0
alien-store/src/main/java/shop/alien/store/dto/StoreOperationalActivityAchievementDto.java

@@ -0,0 +1,31 @@
+package shop.alien.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 运营活动成果新增请求
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreOperationalActivityAchievementDto", description = "运营活动成果新增请求")
+public class StoreOperationalActivityAchievementDto {
+
+    @ApiModelProperty(value = "活动ID", required = true)
+    private Integer activityId;
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名ID", required = true)
+    private Integer signupId;
+
+    @ApiModelProperty(value = "成果描述")
+    private String achievementDesc;
+
+    @ApiModelProperty(value = "图片/视频URL(逗号分隔)")
+    private String mediaUrls;
+}

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

@@ -0,0 +1,32 @@
+package shop.alien.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 商家端案例列表查询请求
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreOperationalActivityCaseQueryDto", description = "商家端案例列表查询请求")
+public class StoreOperationalActivityCaseQueryDto {
+
+    @ApiModelProperty(value = "商户ID", required = true)
+    private Integer storeId;
+
+    @ApiModelProperty(value = "上传情况:0-未上传, 1-已上传")
+    private Integer hasResult;
+
+    @ApiModelProperty(value = "所属活动(活动名称,支持模糊查询)")
+    private String activityName;
+
+    @ApiModelProperty(value = "当前页", example = "1")
+    private Integer pageNum;
+
+    @ApiModelProperty(value = "每页条数", example = "10")
+    private Integer pageSize;
+}
+

+ 28 - 0
alien-store/src/main/java/shop/alien/store/dto/StoreOperationalActivitySignupDto.java

@@ -0,0 +1,28 @@
+package shop.alien.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 运营活动报名请求
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreOperationalActivitySignupDto", description = "运营活动报名请求")
+public class StoreOperationalActivitySignupDto {
+
+    @ApiModelProperty(value = "活动ID", required = true)
+    private Integer activityId;
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名人姓名")
+    private String userName;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+}

+ 3 - 0
alien-store/src/main/java/shop/alien/store/service/CommonRatingService.java

@@ -1,8 +1,10 @@
 package shop.alien.store.service;
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.CommonRating;
+import shop.alien.entity.store.vo.CommonRatingVo;
 
 /**
  * 评价表 服务类
@@ -72,6 +74,7 @@ public interface CommonRatingService extends IService<CommonRating> {
      */
     R getMyRatingList(Integer pageNum, Integer pageSize, Integer businessType, Long userId, Integer auditStatus);
 
+    R<IPage<CommonRatingVo>> doListBusinessWithType(IPage<CommonRating> page2, Integer i, Long userId, Integer replyStatus);
 
   /*  /**
      * 根据业务类型和业务ID获取平均评分

+ 8 - 5
alien-store/src/main/java/shop/alien/store/service/OperationalActivityService.java

@@ -24,13 +24,16 @@ public interface OperationalActivityService {
     /**
      * 根据门店条件分页查询运营活动
      *
-     * @param storeId   门店ID
-     * @param storeName 门店名称(模糊)
-     * @param pageNum   页码
-     * @param pageSize  每页数量
+     * @param storeId      门店ID
+     * @param storeName    门店名称(模糊)
+     * @param pageNum      页码
+     * @param pageSize     每页数量
+     * @param activityStatus 活动状态
+     * @param activityName 活动名称(模糊)
+     * @param activityType 活动类型(COMMENT-评论有礼, MARKETING-营销活动)
      * @return 活动分页结果
      */
-    IPage<StoreOperationalActivityVO> pageActivityDetail(Integer storeId, String storeName, Integer pageNum, Integer pageSize, Integer activityStatus, String activityName);
+    IPage<StoreOperationalActivityVO> pageActivityDetail(Integer storeId, String storeName, Integer pageNum, Integer pageSize, Integer activityStatus, String activityName, String activityType);
 
     /**
      * 根据活动ID获取活动详情

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

@@ -578,4 +578,11 @@ public interface StoreInfoService extends IService<StoreInfo> {
      * @param headImgStatus 审核状态 1-审核通过,2-审核不通过
      */
     void updateHeadImgStatus(Integer storeId, Integer headImgStatus);
+    /**
+     * 人工复核举报
+     *
+     * @param reportId 举报ID
+     * @return 复核结果 true-复核通过,false-复核不通过
+     */
+    boolean manualReviewReport(Integer reportId, Integer type);
 }

+ 77 - 0
alien-store/src/main/java/shop/alien/store/service/StoreOperationalActivityAchievementService.java

@@ -0,0 +1,77 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseDetailVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementVo;
+import shop.alien.store.dto.StoreOperationalActivityAchievementDto;
+
+/**
+ * 运营活动成果服务
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreOperationalActivityAchievementService {
+
+    /**
+     * 新增成果
+     *
+     * @param dto 请求参数
+     * @return 是否成功
+     */
+    boolean addAchievement(StoreOperationalActivityAchievementDto dto);
+
+    /**
+     * 成果详情
+     *
+     * @param id 主键
+     * @return 详情
+     */
+    StoreOperationalActivityAchievementVo getAchievementDetail(Integer id);
+
+    /**
+     * 成果列表
+     *
+     * @param activityId 活动ID
+     * @param userId 用户ID
+     * @param signupId 报名ID
+     * @param pageNum 当前页
+     * @param pageSize 每页条数
+     * @return 分页列表
+     */
+    IPage<StoreOperationalActivityAchievementVo> listAchievements(Integer activityId, Integer userId, Integer signupId,
+                                                                  Integer pageNum, Integer pageSize);
+
+    /**
+     * 案例列表(同一用户同一活动最新成果)
+     *
+     * @param activityStatus 活动状态
+     * @param pageNum 当前页
+     * @param pageSize 每页条数
+     * @return 分页列表
+     */
+    IPage<StoreOperationalActivityAchievementCaseVo> listCasePage(Integer activityStatus, Integer pageNum, Integer pageSize);
+
+    /**
+     * 案例详情
+     *
+     * @param activityId 活动ID
+     * @param userId 用户ID
+     * @return 详情
+     */
+    StoreOperationalActivityAchievementCaseDetailVo getCaseDetail(Integer activityId, Integer userId);
+
+    /**
+     * 商家端案例列表(按商户ID、上传情况、活动名称筛选)
+     *
+     * @param storeId 商户ID
+     * @param hasResult 上传情况:0-未上传, 1-已上传
+     * @param activityName 活动名称(模糊查询)
+     * @param pageNum 当前页
+     * @param pageSize 每页条数
+     * @return 分页列表
+     */
+    IPage<StoreOperationalActivityAchievementCaseVo> listStoreCasePage(Integer storeId, Integer hasResult,
+                                                                        String activityName, Integer pageNum, Integer pageSize);
+}

+ 84 - 0
alien-store/src/main/java/shop/alien/store/service/StoreOperationalActivityService.java

@@ -0,0 +1,84 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityDetailVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupCheckVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityMySignupVo;
+import shop.alien.store.dto.StoreOperationalActivitySignupDto;
+import java.util.List;
+
+/**
+ * 用户端运营活动服务
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreOperationalActivityService {
+
+    /**
+     * 查询活动详情。
+     * <p>
+     * 说明:
+     * 1. id 为活动主键,userId 用于判断报名相关状态。
+     * 2. 若活动不存在,返回 null。
+     * </p>
+     *
+     * @param id 活动ID
+     * @param userId 用户ID
+     * @return 活动详情
+     */
+    StoreOperationalActivityDetailVo getActivityDetail(Integer id, Integer userId);
+
+    /**
+     * 查询活动详情。
+     * <p>
+     * 说明:
+     * 1. id 为活动主键,userId 用于判断报名相关状态。
+     * 2. 若活动不存在,返回 null。
+     * </p>
+     *
+     * @param id 活动ID
+     * @param userId 用户ID
+     * @return 活动详情
+     */
+    StoreOperationalActivityDetailVo getActivityDetailForPlatform(Integer id, Integer userId);
+
+    /**
+     * 报名校验:是否已满、是否已成功报名。
+     *
+     * @param activityId 活动ID
+     * @param userId     用户ID
+     * @return 校验结果
+     */
+    StoreOperationalActivitySignupCheckVo checkSignup(Integer activityId, Integer userId);
+
+    /**
+     * 查询我的报名列表
+     *
+     * @param userId 用户ID
+     * @return 报名列表
+     */
+    List<StoreOperationalActivityMySignupVo> listMySignups(Integer userId);
+
+    /**
+     * 删除报名及其成果。
+     * <p>
+     * 规则:
+     * 1. 必须传入活动ID与报名ID。
+     * 2. 报名必须存在且属于该活动。
+     * 3. 删除报名信息与关联成果信息(逻辑删除)。
+     * </p>
+     *
+     * @param activityId 活动ID
+     * @param signupId 报名ID
+     * @return 是否删除成功
+     */
+    boolean deleteSignup(Integer activityId, Integer signupId);
+
+    /**
+     * 活动报名
+     *
+     * @param dto 报名请求
+     * @return 是否成功
+     */
+    boolean signup(StoreOperationalActivitySignupDto dto);
+}

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

@@ -178,7 +178,7 @@ public interface StoreUserService extends IService<StoreUser> {
     void storeCancelAccountUn(StoreUserVo storeUserVo);
 
     /**
-     * 手动删除商家账号及店铺
+     * 手动删除商家账号及店铺 进行校验
      */
     void deleteStoreAccountInfo(StoreUserVo storeUserVo);
 

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

@@ -2,6 +2,7 @@ package shop.alien.store.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import shop.alien.entity.store.StoreVideo;
+import shop.alien.entity.store.dto.StoreVideoSaveDto;
 
 import java.util.List;
 
@@ -29,5 +30,13 @@ public interface StoreVideoService extends IService<StoreVideo> {
      * @return 视频列表
      */
     List<StoreVideo> getByStoreIdAndBusinessId(Integer storeId, Integer businessId);
+
+    /**
+     * 保存视频(单个或批量)
+     *
+     * @param dto 视频保存DTO,包含门店ID、相册ID和视频URL列表
+     * @return 是否保存成功
+     */
+    boolean saveOrSaveBatch(StoreVideoSaveDto dto);
 }
 

+ 8 - 0
alien-store/src/main/java/shop/alien/store/service/TrackEventService.java

@@ -35,4 +35,12 @@ public interface TrackEventService {
      * @param weekly          统计类型(DAILY/WEEKLY/MONTHLY)
      */
     void calculateAndSaveStatistics(Integer id, Date lastWeekMonday, String weekly);
+    
+    /**
+     * 统计店铺当前总浏览量
+     *
+     * @param storeId 店铺ID
+     * @return 浏览量
+     */
+    Long getStoreViewCount(Integer storeId);
 }

+ 46 - 0
alien-store/src/main/java/shop/alien/store/service/impl/BarPerformanceServiceImpl.java

@@ -21,8 +21,10 @@ import shop.alien.store.util.ai.AiContentModerationUtil;
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -162,6 +164,12 @@ public class BarPerformanceServiceImpl implements BarPerformanceService {
                 if (barPerformance.getSingleEndDatetime() == null) {
                     throw new IllegalArgumentException("每周定时演出必须填写结束时间");
                 }
+                // 校验:选择的日期(singleStartDatetime 的星期几)必须是 performanceWeek 中的某一天
+                int startDayOfWeek = getDayOfWeekAsPerformanceWeek(barPerformance.getSingleStartDatetime());
+                Set<Integer> allowedWeekDays = parsePerformanceWeekToSet(barPerformance.getPerformanceWeek());
+                if (!allowedWeekDays.contains(startDayOfWeek)) {
+                    throw new IllegalArgumentException("选择的开始日期对应的星期几必须在演出日期(周几)范围内");
+                }
                 // 从single_start_datetime和single_end_datetime中提取时间部分,存到daily_start_time和daily_end_time
                 LocalTime weeklyStartTime = barPerformance.getSingleStartDatetime().toInstant()
                         .atZone(ZoneId.systemDefault())
@@ -648,6 +656,44 @@ public class BarPerformanceServiceImpl implements BarPerformanceService {
     }
 
     /**
+     * 将 Date 的星期几转换为 performanceWeek 编码(0=周一,1=周二,…,6=周日)
+     * 用于校验选择的日期是否在演出日期(周几)范围内
+     *
+     * @param date 日期时间
+     * @return 0-6,对应周一至周日
+     */
+    private int getDayOfWeekAsPerformanceWeek(Date date) {
+        if (date == null) {
+            throw new IllegalArgumentException("日期不能为空");
+        }
+        java.util.Calendar cal = java.util.Calendar.getInstance();
+        cal.setTime(date);
+        // Calendar: Sunday=1, Monday=2, ..., Saturday=7
+        int calendarDay = cal.get(java.util.Calendar.DAY_OF_WEEK);
+        // 转为 performanceWeek 编码:0=周一, 1=周二, ..., 6=周日
+        return calendarDay == java.util.Calendar.SUNDAY ? 6 : calendarDay - 2;
+    }
+
+    /**
+     * 解析 performanceWeek 字符串为允许的星期几集合(0-6)
+     * 格式:逗号分隔,如 "0,2,5" 表示周一、周三、周六
+     *
+     * @param performanceWeek 演出日期字符串
+     * @return 允许的星期几集合,空字符串或无效时返回空集合
+     */
+    private Set<Integer> parsePerformanceWeekToSet(String performanceWeek) {
+        if (StringUtils.isEmpty(performanceWeek)) {
+            return java.util.Collections.emptySet();
+        }
+        return Arrays.stream(performanceWeek.split(","))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .filter(s -> s.matches("^[0-6]$"))
+                .map(Integer::parseInt)
+                .collect(Collectors.toSet());
+    }
+
+    /**
      * 比较两个时间(只比较时间部分,忽略日期部分)
      * 用于每天定时和每周定时演出的时间验证
      *

+ 2 - 1
alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java

@@ -548,7 +548,8 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
      * @param replyStatus 回复状态(0:全部, 1:已回复, 2:未回复)
      * @return R<IPage<CommonRatingVo>>
      */
-    private R<IPage<CommonRatingVo>> doListBusinessWithType(IPage<CommonRating> page1, Integer businessType, Long userId, Integer replyStatus) {
+    @Override
+    public R<IPage<CommonRatingVo>> doListBusinessWithType(IPage<CommonRating> page1, Integer businessType, Long userId, Integer replyStatus) {
         if(businessType == RatingBusinessTypeEnum.STORE_RATING.getBusinessType()){
             IPage<CommonRatingVo> result = new Page<>(page1.getCurrent(), page1.getSize(), page1.getTotal());
             List<CommonRatingVo> resultList = new ArrayList<>();

+ 13 - 8
alien-store/src/main/java/shop/alien/store/service/impl/LicenseAuditAsyncService.java

@@ -21,9 +21,7 @@ import shop.alien.mapper.storePlantform.StoreLicenseHistoryMapper;
 import shop.alien.store.util.ai.AiAuthTokenUtil;
 
 import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 /**
  * 证照审核异步服务
@@ -150,16 +148,17 @@ public class LicenseAuditAsyncService {
             String rejectReason = null;
 
             if (licenseStatus == 1) {
+                List<String> licenseTypes = Arrays.asList("business_license", "legal_person_business_license");
                 // 营业执照的审核逻辑
-                if (!"business_license".equals(licenseType)) {
-                    // 上传的图片不是营业执照licenseType="business_license"
+                if (!licenseTypes.contains(licenseType)) {
+                    // 上传的图片不是营业执照
                     needReject = true;
                     rejectReason = "上传的图片不是营业执照";
                 } else if (Boolean.TRUE.equals(isExpired)) {
                     // 营业执照已过期
                     needReject = true;
                     rejectReason = "已过期";
-                } else if (Boolean.TRUE.equals(isValid) && "business_license".equals(licenseType) && Boolean.FALSE.equals(isExpired)) {
+                } else if (Boolean.TRUE.equals(isValid) && licenseTypes.contains(licenseType) && Boolean.FALSE.equals(isExpired)) {
                     // 营业执照有效且未过期,审核通过
                     needApprove = true;
                 }
@@ -271,7 +270,10 @@ public class LicenseAuditAsyncService {
                             .set(StoreLicenseHistory::getReasonRefusal, rejectReason);
                     licenseHistoryMapper.update(null, updateWrapper);
                     log.info("{}AI审核拒绝,门店ID:{},图片URL:{},拒绝原因:{}", licenseTypeName, storeId, imageUrl, rejectReason);
-                    
+                    storeInfoMapper.update(null, new LambdaUpdateWrapper<StoreInfo>()
+                            .eq(StoreInfo::getId, storeId)
+                            .set(StoreInfo::getBusinessLicenseStatus, 2)
+                    );
                     // 审核拒绝时,删除store_img表中的记录(逻辑删除),避免前端展示审核拒绝的图片
                     LambdaUpdateWrapper<StoreImg> deleteImgWrapper = new LambdaUpdateWrapper<>();
                     deleteImgWrapper.eq(StoreImg::getStoreId, storeId)
@@ -286,7 +288,10 @@ public class LicenseAuditAsyncService {
                     updateWrapper.set(StoreLicenseHistory::getLicenseExecuteStatus, 1); // 1-审核通过
                     licenseHistoryMapper.update(null, updateWrapper);
                     log.info("{}AI审核通过,门店ID:{},图片URL:{}", licenseTypeName, storeId, imageUrl);
-                    
+                    storeInfoMapper.update(null, new LambdaUpdateWrapper<StoreInfo>()
+                            .eq(StoreInfo::getId, storeId)
+                            .set(StoreInfo::getBusinessLicenseStatus, 1)
+                    );
                     // 审核通过后,插入store_img表(检查是否已存在,避免重复插入)
                     StoreImg existingImg = storeImgMapper.selectOne(
                             new LambdaQueryWrapper<StoreImg>()

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

@@ -417,18 +417,18 @@ public class LifeMessageServiceImpl extends ServiceImpl<LifeMessageMapper, LifeM
                 }
             }
 
-//            // 查询两个人是否是陌生人(只有一条消息)
-//            QueryWrapper<LifeMessage> wrapper = new QueryWrapper<>();
-//            // 没聊过
-//            wrapper.apply("((sender_id = '" + senderId + "' and receiver_id = '" + receiverId + "') or (sender_id = '" + receiverId + "' and receiver_id = '" + senderId + "'))");
-//            wrapper.orderByDesc("created_time");
-//            int count = lifeMessageMapper.selectCount(wrapper);
+            // 查询两个人是否是陌生人(只有一条消息)
+            QueryWrapper<LifeMessage> wrapper = new QueryWrapper<>();
+            // 没聊过
+            wrapper.apply("((sender_id = '" + senderId + "' and receiver_id = '" + receiverId + "') or (sender_id = '" + receiverId + "' and receiver_id = '" + senderId + "'))");
+            wrapper.orderByDesc("created_time");
+            int count = lifeMessageMapper.selectCount(wrapper);
 
             JSONObject jsonObject = new JSONObject();
             jsonObject.put("messageList", lifeMessageVos);
-//            jsonObject.put("isStranger", count <= 1);
+            jsonObject.put("isStranger", count <= 1);
             // 陌生人不限制一条消息
-            jsonObject.put("isStranger", false);
+//            jsonObject.put("isStranger", false);
 
             return jsonObject;
         } catch (Exception e) {
@@ -449,37 +449,37 @@ public class LifeMessageServiceImpl extends ServiceImpl<LifeMessageMapper, LifeM
 //            return lifeMessageMapper.selectCount(wrapper) <= 1;
 
             // 查询我发送过的消息
-//            QueryWrapper<LifeMessage> wrapper = new QueryWrapper<>();
-//            //本人发送的数量
-//            wrapper.apply("(sender_id = '" + senderId + "' and receiver_id = '" + receiverId + "')");
-////            wrapper.eq("sender_id", senderId);
-////            wrapper.eq("receiver_id", receiverId);
-//
-//            int senderCount = lifeMessageMapper.selectCount(wrapper);
-//            //查询对方发送过的消息
-//            QueryWrapper<LifeMessage> wrapper1 = new QueryWrapper<>();
-//            wrapper1.apply("(sender_id = '" + receiverId + "' and receiver_id = '" + senderId + "')");
-//            int receiverCount = lifeMessageMapper.selectCount(wrapper1);
-//            if (senderCount == 0){
-//                //本条可以发
-//                return "1";
-//            }
-//            senderCount++;
-//            // 可以一直唠
-//            if (senderCount >= 1 && receiverCount >= 1){
-//            // 发送本条消息并可以一直发
-//               return "2";
-//            }
-//            // 本人发过了,等待对方发
-//            if (senderCount > 1 && receiverCount==0){
-//                // 不允许发送本条消息
-//                 return "0";
-//            }
-
-//            return "0";
+            QueryWrapper<LifeMessage> wrapper = new QueryWrapper<>();
+            //本人发送的数量
+            wrapper.apply("(sender_id = '" + senderId + "' and receiver_id = '" + receiverId + "')");
+//            wrapper.eq("sender_id", senderId);
+//            wrapper.eq("receiver_id", receiverId);
+
+            int senderCount = lifeMessageMapper.selectCount(wrapper);
+            //查询对方发送过的消息
+            QueryWrapper<LifeMessage> wrapper1 = new QueryWrapper<>();
+            wrapper1.apply("(sender_id = '" + receiverId + "' and receiver_id = '" + senderId + "')");
+            int receiverCount = lifeMessageMapper.selectCount(wrapper1);
+            if (senderCount == 0){
+                //本条可以发
+                return "1";
+            }
+            senderCount++;
+            // 可以一直唠
+            if (senderCount >= 1 && receiverCount >= 1){
+            // 发送本条消息并可以一直发
+               return "2";
+            }
+            // 本人发过了,等待对方发
+            if (senderCount > 1 && receiverCount==0){
+                // 不允许发送本条消息
+                 return "0";
+            }
+
+            return "0";
 
             // 陌生人不限制一条消息
-            return "2";
+//            return "2";
         } catch (Exception e) {
             log.error("LifeMessageServiceImpl.isStranger Error Mgs={}", e.getMessage());
             throw new Exception(e);

+ 7 - 2
alien-store/src/main/java/shop/alien/store/service/impl/LifeUserViolationServiceImpl.java

@@ -30,6 +30,7 @@ import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.service.*;
 import shop.alien.store.util.AiUserViolationUtils;
 import shop.alien.store.util.FunctionMagic;
+import shop.alien.store.util.ai.AiReportReviewUtil;
 import shop.alien.util.ali.AliOSSUtil;
 import shop.alien.util.common.EnumUtil;
 
@@ -87,6 +88,9 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 
     private final AiUserViolationUtils aiUserViolationUtils;
     private final CommonCommentMapper commonCommentMapper;
+    private final AiReportReviewUtil aiReportReviewUtil;
+
+
 
     @Autowired
     private SecondUserViolationMapper mapper;
@@ -110,6 +114,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
                 if (null != goodsRecord) lifeuserViolation.setBusinessId(goodsRecord.getId());
             }
             int result = lifeUserViolationMapper.insert(lifeuserViolation);
+            aiReportReviewUtil.reviewReport(lifeuserViolation.getId(), lifeuserViolation.getReportContextType());
             if (result > 0) {
                 // AI审核
                 //登录获取token
@@ -126,7 +131,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 
                 //String phoneId = Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getString("userType") + "_" + JwtUtil.getCurrentUserInfo().getString("phone");
                     // 举报人消息
-                    LifeNotice lifeNotice = getLifeNotice(lifeuserViolation);
+                  /*  LifeNotice lifeNotice = getLifeNotice(lifeuserViolation);
                     lifeNoticeMapper.insert(lifeNotice);
                     WebSocketVo websocketVo = new WebSocketVo();
                     websocketVo.setSenderId("system");
@@ -151,7 +156,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
                             websocketVoReported.setText(com.alibaba.fastjson2.JSONObject.from(lifeNoticeReported).toJSONString());
                             webSocketProcess.sendMessage(lifeNoticeReported.getReceiverId(), com.alibaba.fastjson2.JSONObject.from(websocketVoReported).toJSONString());
                         }
-                    }
+                    }*/
                 return result;
             }
         } catch (Exception e) {

+ 41 - 8
alien-store/src/main/java/shop/alien/store/service/impl/OperationalActivityServiceImpl.java

@@ -79,8 +79,8 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
     }
 
     @Override
-    public IPage<StoreOperationalActivityVO> pageActivityDetail(Integer storeId, String storeName, Integer pageNum, Integer pageSize, Integer activityStatus, String activityName) {
-        log.info("OperationalActivityServiceImpl.pageActivityDetail: storeId={}, storeName={}, pageNum={}, pageSize={}, activityStatus={}, activityName={}", storeId, storeName, pageNum, pageSize, activityStatus, activityName);
+    public IPage<StoreOperationalActivityVO> pageActivityDetail(Integer storeId, String storeName, Integer pageNum, Integer pageSize, Integer activityStatus, String activityName, String activityType) {
+        log.info("OperationalActivityServiceImpl.pageActivityDetail: storeId={}, storeName={}, pageNum={}, pageSize={}, activityStatus={}, activityName={}, activityType={}", storeId, storeName, pageNum, pageSize, activityStatus, activityName, activityType);
 
 //        if (storeId == null && StringUtils.isBlank(storeName)) {
 //            throw new IllegalArgumentException("请至少提供商户ID或商户名称");
@@ -118,6 +118,9 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
         if (StringUtils.isNotBlank(activityName)) {
             wrapper.like(StoreOperationalActivity::getActivityName, activityName);
         }
+        if (StringUtils.isNotBlank(activityType)) {
+            wrapper.eq(StoreOperationalActivity::getActivityType, activityType);
+        }
 //
 //        if (!storeIds.isEmpty()) {
 //            wrapper.in(StoreOperationalActivity::getStoreId, storeIds);
@@ -134,6 +137,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
             StoreOperationalActivityVO vo = new StoreOperationalActivityVO();
             BeanUtils.copyProperties(activity, vo);
             vo.setStatusName(resolveStatusName(activity.getStatus()));
+            vo.setActivityTypeName(resolveActivityTypeName(activity.getActivityType()));
 
             if (activity.getCouponId() != null) {
                 LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
@@ -168,6 +172,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
         StoreOperationalActivityVO vo = new StoreOperationalActivityVO();
         BeanUtils.copyProperties(activity, vo);
         vo.setStatusName(resolveStatusName(activity.getStatus()));
+        vo.setActivityTypeName(resolveActivityTypeName(activity.getActivityType()));
 
         if (activity.getCouponId() != null) {
             LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
@@ -241,24 +246,52 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
         if (vo == null || vo.getStoreId() == null || vo.getId() == null) {
             return;
         }
+        // 查询活动标题图(单张)
         StoreImg titleImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
                 .eq(StoreImg::getStoreId, vo.getStoreId())
                 .eq(StoreImg::getBusinessId, vo.getId())
                 .eq(StoreImg::getImgType, 26)
-                .eq(StoreImg::getDeleteFlag, 0));
+                .eq(StoreImg::getDeleteFlag, 0)
+                .orderByAsc(StoreImg::getImgSort)
+                .last("LIMIT 1"));
         if (titleImg != null) {
             vo.setActivityTitleImg(titleImg);
             vo.setActivityTitleImgUrl(titleImg.getImgUrl());
         }
 
-        StoreImg detailImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
+        // 查询活动详情图(多张)
+        List<StoreImg> detailImgs = imgMapper.selectList(new LambdaQueryWrapper<StoreImg>()
                 .eq(StoreImg::getStoreId, vo.getStoreId())
                 .eq(StoreImg::getBusinessId, vo.getId())
                 .eq(StoreImg::getImgType, 27)
-                .eq(StoreImg::getDeleteFlag, 0));
-        if (detailImg != null) {
-            vo.setActivityDetailImg(detailImg);
-            vo.setActivityDetailImgUrl(detailImg.getImgUrl());
+                .eq(StoreImg::getDeleteFlag, 0)
+                .orderByAsc(StoreImg::getImgSort));
+        if (detailImgs != null && !detailImgs.isEmpty()) {
+            // 设置第一张详情图(兼容旧字段)
+            vo.setActivityDetailImg(detailImgs.get(0));
+            vo.setActivityDetailImgUrl(detailImgs.get(0).getImgUrl());
+            // 设置详情图列表
+            List<String> detailImgUrls = new ArrayList<>();
+            for (StoreImg img : detailImgs) {
+                if (img != null && img.getImgUrl() != null && !img.getImgUrl().trim().isEmpty()) {
+                    detailImgUrls.add(img.getImgUrl().trim());
+                }
+            }
+            vo.setActivityDetailImgUrlList(detailImgUrls);
+        }
+    }
+
+    private String resolveActivityTypeName(String activityType) {
+        if (StringUtils.isBlank(activityType)) {
+            return null;
+        }
+        switch (activityType) {
+            case "COMMENT":
+                return "评论有礼";
+            case "MARKETING":
+                return "营销活动";
+            default:
+                return activityType;
         }
     }
 }

+ 196 - 6
alien-store/src/main/java/shop/alien/store/service/impl/PerformanceListServiceImpl.java

@@ -197,6 +197,22 @@ public class PerformanceListServiceImpl implements PerformanceListService {
     }
 
     /**
+     * 转换为VO列表(用于按日期分组查询,过滤下线演出人员)
+     *
+     * @param performances 演出列表
+     * @return VO列表
+     */
+    private List<PerformanceListVo> convertToVoListForGroupByDate(List<BarPerformance> performances) {
+        if (performances == null || performances.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        return performances.stream()
+                .map(this::convertToVoForGroupByDate)
+                .collect(Collectors.toList());
+    }
+
+    /**
      * 转换为VO
      *
      * @param performance 演出信息
@@ -209,8 +225,39 @@ public class PerformanceListServiceImpl implements PerformanceListService {
         vo.setPerformancePoster(performance.getPerformancePoster());
         vo.setPerformanceType(performance.getPerformanceType());
 
-        // 设置演出者信息
-        vo.setPerformersInfo(buildPerformersInfo(performance.getStaffConfigIds()));
+        // 设置演出者信息(过滤下线演出人员)
+        vo.setPerformersInfo(buildPerformersInfoFilterOffline(performance.getStaffConfigIds()));
+
+        // 设置日期范围
+        vo.setDateRange(buildDateRange(performance));
+
+        // 设置演出时间安排
+        vo.setScheduleInfo(buildScheduleInfo(performance));
+
+        // 设置演出风格(直接返回表中的值,不查询字典)
+        vo.setPerformanceStyle(performance.getPerformanceStyle());
+
+        // 设置演出人员头像列表(过滤下线演出人员)
+        vo.setStaffImageList(queryStaffImageListFilterOffline(performance.getStaffConfigIds()));
+
+        return vo;
+    }
+
+    /**
+     * 转换为VO(用于按日期分组查询,过滤下线演出人员)
+     *
+     * @param performance 演出信息
+     * @return VO对象
+     */
+    private PerformanceListVo convertToVoForGroupByDate(BarPerformance performance) {
+        PerformanceListVo vo = new PerformanceListVo();
+        vo.setId(performance.getId());
+        vo.setPerformanceName(performance.getPerformanceName());
+        vo.setPerformancePoster(performance.getPerformancePoster());
+        vo.setPerformanceType(performance.getPerformanceType());
+
+        // 设置演出者信息(过滤下线演出人员)
+        vo.setPerformersInfo(buildPerformersInfoFilterOffline(performance.getStaffConfigIds()));
 
         // 设置日期范围
         vo.setDateRange(buildDateRange(performance));
@@ -221,8 +268,8 @@ public class PerformanceListServiceImpl implements PerformanceListService {
         // 设置演出风格(直接返回表中的值,不查询字典)
         vo.setPerformanceStyle(performance.getPerformanceStyle());
 
-        // 设置演出人员头像列表
-        vo.setStaffImageList(queryStaffImageList(performance.getStaffConfigIds()));
+        // 设置演出人员头像列表(过滤下线演出人员)
+        vo.setStaffImageList(queryStaffImageListFilterOffline(performance.getStaffConfigIds()));
 
         return vo;
     }
@@ -295,6 +342,88 @@ public class PerformanceListServiceImpl implements PerformanceListService {
     }
 
     /**
+     * 构建演出者信息(过滤下线演出人员)
+     * <p>
+     * 从员工ID列表中查询第一个有名字且上线的员工,显示格式:
+     * - 1人:显示员工名字
+     * - 多人:显示"xx等X人"
+     * 只统计上线的员工数量
+     * </p>
+     *
+     * @param staffConfigIds 员工配置ID字符串(逗号分隔)
+     * @return 演出者信息(如:刘能等7人,仅包含上线员工)
+     */
+    private String buildPerformersInfoFilterOffline(String staffConfigIds) {
+        if (StringUtils.isEmpty(staffConfigIds)) {
+            return "";
+        }
+
+        try {
+            String[] ids = staffConfigIds.split(",");
+            List<Integer> staffIdList = new ArrayList<>();
+            
+            // 解析所有有效的员工ID
+            for (String idStr : ids) {
+                if (StringUtils.isNotEmpty(idStr.trim())) {
+                    try {
+                        Integer staffId = Integer.parseInt(idStr.trim());
+                        if (staffId > 0) {
+                            staffIdList.add(staffId);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("解析员工配置ID失败,无效的ID:{}", idStr);
+                    }
+                }
+            }
+
+            if (staffIdList.isEmpty()) {
+                return "";
+            }
+
+            // 批量查询员工信息
+            List<StoreStaffConfig> staffList = storeStaffConfigMapper.selectBatchIds(staffIdList);
+            
+            // 过滤出上线的员工
+            List<StoreStaffConfig> onlineStaffList = new ArrayList<>();
+            if (staffList != null && !staffList.isEmpty()) {
+                for (StoreStaffConfig staff : staffList) {
+                    if (staff != null 
+                            && CommonConstant.DELETE_FLAG_UNDELETE.equals(staff.getDeleteFlag())
+                            && "1".equals(staff.getStatus())
+                            && CommonConstant.ONLINE_STATUS.equals(staff.getOnlineStatus())) {
+                        onlineStaffList.add(staff);
+                    }
+                }
+            }
+
+            if (onlineStaffList.isEmpty()) {
+                return "";
+            }
+
+            int count = onlineStaffList.size();
+            String firstName = null;
+
+            // 找到第一个有名字的上线员工
+            for (StoreStaffConfig staff : onlineStaffList) {
+                if (StringUtils.isNotEmpty(staff.getName())) {
+                    firstName = staff.getName();
+                    break; // 找到第一个有名字的员工就退出
+                }
+            }
+
+            // 根据人数和是否找到名字来构建返回信息
+            if (count == 1) {
+                return firstName != null ? firstName : "1人";
+            } else {
+                return firstName != null ? firstName + "等" + count + "人" : count + "人";
+            }
+        } catch (Exception e) {
+            log.error("构建演出者信息异常(过滤下线),staffConfigIds={},异常信息:{}", staffConfigIds, e.getMessage(), e);
+            return "";
+        }
+    }
+
+    /**
      * 查询演出人员头像列表
      * <p>
      * 根据员工配置ID字符串(逗号分隔)查询store_staff_config表,获取所有员工的头像
@@ -352,6 +481,67 @@ public class PerformanceListServiceImpl implements PerformanceListService {
     }
 
     /**
+     * 查询演出人员头像列表(过滤下线演出人员)
+     * <p>
+     * 根据员工配置ID字符串(逗号分隔)查询store_staff_config表,只获取上线员工的头像
+     * </p>
+     *
+     * @param staffConfigIds 员工配置ID字符串(逗号分隔)
+     * @return 员工头像URL列表(仅包含上线员工)
+     */
+    private List<String> queryStaffImageListFilterOffline(String staffConfigIds) {
+        List<String> imageList = new ArrayList<>();
+        
+        if (StringUtils.isEmpty(staffConfigIds)) {
+            return imageList;
+        }
+
+        try {
+            String[] ids = staffConfigIds.split(",");
+            List<Integer> staffIdList = new ArrayList<>();
+            
+            // 解析所有有效的员工ID
+            for (String idStr : ids) {
+                if (StringUtils.isNotEmpty(idStr.trim())) {
+                    try {
+                        Integer staffId = Integer.parseInt(idStr.trim());
+                        if (staffId > 0) {
+                            staffIdList.add(staffId);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("解析员工配置ID失败,无效的ID:{}", idStr);
+                    }
+                }
+            }
+
+            if (staffIdList.isEmpty()) {
+                return imageList;
+            }
+
+            // 批量查询员工信息
+            List<StoreStaffConfig> staffList = storeStaffConfigMapper.selectBatchIds(staffIdList);
+            
+            if (staffList != null && !staffList.isEmpty()) {
+                // 只提取上线且非空的头像URL
+                for (StoreStaffConfig staff : staffList) {
+                    if (staff != null 
+                            && CommonConstant.DELETE_FLAG_UNDELETE.equals(staff.getDeleteFlag())
+                            && "1".equals(staff.getStatus())
+                            && CommonConstant.ONLINE_STATUS.equals(staff.getOnlineStatus())
+                            && StringUtils.isNotEmpty(staff.getStaffImage())) {
+                        imageList.add(staff.getStaffImage());
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("查询演出人员头像列表异常(过滤下线),staffConfigIds={},异常信息:{}", 
+                    staffConfigIds, e.getMessage(), e);
+        }
+
+        return imageList;
+    }
+
+    /**
      * 构建日期范围
      *
      * @param performance 演出信息
@@ -593,8 +783,8 @@ public class PerformanceListServiceImpl implements PerformanceListService {
                 return result;
             }
 
-            // 转换为VO列表
-            List<PerformanceListVo> voList = convertToVoList(performances);
+            // 转换为VO列表(过滤下线演出人员)
+            List<PerformanceListVo> voList = convertToVoListForGroupByDate(performances);
 
             // 生成演出日期映射(日期 -> 演出列表)
             Map<String, List<PerformanceListVo>> datePerformanceMap = generateDatePerformanceMap(performances, voList);

+ 28 - 24
alien-store/src/main/java/shop/alien/store/service/impl/StoreCommentAppealServiceImpl.java

@@ -761,35 +761,39 @@ public class StoreCommentAppealServiceImpl extends ServiceImpl<StoreCommentAppea
         // 商家图片:暂时传空数组,先测试文本分析流程
         // TODO: 后续需要将图片转换为Base64
         List<String> merchantImages = new ArrayList<>();
-        // if (org.springframework.util.StringUtils.hasText(storeCommentAppeal.getImgId())) {
-        //     String[] imgIds = storeCommentAppeal.getImgId().split(",");
-        //     for (String imgId : imgIds) {
-        //         StoreImg storeImg = storeImgMapper.selectById(imgId.trim());
-        //         if (storeImg != null && org.springframework.util.StringUtils.hasText(storeImg.getImgUrl())) {
-        //             String base64 = convertImageToBase64(storeImg.getImgUrl());
-        //             if (org.springframework.util.StringUtils.hasText(base64)) {
-        //                 merchantImages.add(base64);
-        //             }
-        //         }
-        //     }
-        // }
+         if (org.springframework.util.StringUtils.hasText(storeCommentAppeal.getImgId())) {
+             String[] imgIds = storeCommentAppeal.getImgId().split(",");
+             for (String imgId : imgIds) {
+                 StoreImg storeImg = storeImgMapper.selectById(imgId.trim());
+//                 if (storeImg != null && org.springframework.util.StringUtils.hasText(storeImg.getImgUrl())) {
+//                     String base64 = convertImageToBase64(storeImg.getImgUrl());
+//                     if (org.springframework.util.StringUtils.hasText(base64)) {
+//                         merchantImages.add(base64);
+//                     }
+//                 }
+                 if (storeImg.getImgUrl() != null) {
+                     merchantImages.add(storeImg.getImgUrl());
+                 }
+             }
+         }
         analyzeRequest.put("merchant_images", merchantImages);
         
         // 用户图片:暂时传空数组,先测试文本分析流程
         // TODO: 后续需要将图片转换为Base64
         List<String> userImages = new ArrayList<>();
-        // if (org.springframework.util.StringUtils.hasText(rating.getImageUrls())) {
-        //     String[] imageUrls = rating.getImageUrls().split(",");
-        //     for (String imageUrl : imageUrls) {
-        //         String trimmedUrl = imageUrl.trim();
-        //         if (org.springframework.util.StringUtils.hasText(trimmedUrl)) {
-        //             String base64 = convertImageToBase64(trimmedUrl);
-        //             if (org.springframework.util.StringUtils.hasText(base64)) {
-        //                 userImages.add(base64);
-        //             }
-        //         }
-        //     }
-        // }
+         if (org.springframework.util.StringUtils.hasText(rating.getImageUrls())) {
+             String[] imageUrls = rating.getImageUrls().split(",");
+             for (String imageUrl : imageUrls) {
+                 String trimmedUrl = imageUrl.trim();
+//                 if (org.springframework.util.StringUtils.hasText(trimmedUrl)) {
+//                     String base64 = convertImageToBase64(trimmedUrl);
+//                     if (org.springframework.util.StringUtils.hasText(base64)) {
+//                         userImages.add(base64);
+//                     }
+//                 }
+                 userImages.add(trimmedUrl);
+             }
+         }
         analyzeRequest.put("user_images", userImages);
         
         // 其他字段(可选)

+ 19 - 7
alien-store/src/main/java/shop/alien/store/service/impl/StoreCommentServiceImpl.java

@@ -1,8 +1,10 @@
 package shop.alien.store.service.impl;
 
+import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
@@ -164,6 +166,17 @@ public class StoreCommentServiceImpl extends ServiceImpl<StoreCommentMapper, Sto
             }
         }
 
+        // 回复状态筛选:在分页查询前加入SQL条件,保证「未回复」只显示未回复数据、总数和分页正确
+        if (replyStatus != null && (replyStatus == 1 || replyStatus == 2)) {
+            if (replyStatus == 1) {
+                // 已回复:存在商家回复(子评论中 phone_id 含 store_)
+                queryWrapper.apply("EXISTS (SELECT 1 FROM store_comment r WHERE r.reply_id = a.id AND r.phone_id LIKE '%store%' AND r.delete_flag = 0)");
+            } else if (replyStatus == 2) {
+                // 未回复:不存在商家回复
+                queryWrapper.apply("NOT EXISTS (SELECT 1 FROM store_comment r WHERE r.reply_id = a.id AND r.phone_id LIKE '%store%' AND r.delete_flag = 0)");
+            }
+        }
+
         //先查询父级评论
         queryWrapper.isNull(businessType != 1, "a.reply_id").eq("a.delete_flag", 0).orderByDesc("a.created_time");
         IPage<StoreCommentVo> page = storeCommentMapper.getCommentPage(storeCommentIPage, queryWrapper);
@@ -579,7 +592,7 @@ public class StoreCommentServiceImpl extends ServiceImpl<StoreCommentMapper, Sto
      */
     @Override
     public Integer addComment(MultipartRequest multipartRequest, Integer id, Integer businessId, Integer businessType, Integer storeId, Integer userId, Integer replyId, String commentContent, Double score, String otherScore, Integer isAnonymous, String evaluationTags, String phoneId) {
-      /*  try {
+        try {
             List<String> servicesList = Lists.newArrayList();
             servicesList.add(TextReviewServiceEnum.COMMENT_DETECTION_PRO.getService());
             servicesList.add(TextReviewServiceEnum.LLM_QUERY_MODERATION.getService());
@@ -588,10 +601,10 @@ public class StoreCommentServiceImpl extends ServiceImpl<StoreCommentMapper, Sto
                 return 2;
             }
 
-            *//*Map<String, String> checkText = TextCheckUtil.check(commentContent);
+            /*Map<String, String> checkText = TextCheckUtil.check(commentContent);
             if (null == checkText || checkText.get("result").equals("1")) {
                 return 2;
-            }*//*
+            }*/
             StoreComment storeComment = new StoreComment();
             storeComment.setId(id);
             storeComment.setStoreId(storeId);
@@ -617,10 +630,10 @@ public class StoreCommentServiceImpl extends ServiceImpl<StoreCommentMapper, Sto
                 StringBuilder imgId = new StringBuilder();
                 for (int i = 0; i < fileNameSet.size(); i++) {
                     MultipartFile multipartFile = multipartRequest.getFileMap().get(fileNameSet.get(i));
-                   //b
+                   /*//b
                     System.out.println(multipartFile.getSize());
                     //kb
-                    System.out.println(multipartFile.getSize() / 1024);
+                    System.out.println(multipartFile.getSize() / 1024);*/
                     if (null != multipartFile && multipartFile.getSize() / 1024 > 0) {
                         byte[] fileByte;
                         try {
@@ -701,8 +714,7 @@ public class StoreCommentServiceImpl extends ServiceImpl<StoreCommentMapper, Sto
             log.error("StoreCommentService.userComment ERROR Msg={}", e.getMessage());
             return 1;
         }
-*/
-        return 0;
+        // return 0;
     }
 
 

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

@@ -121,7 +121,9 @@ public class StoreCuisineServiceImpl extends ServiceImpl<StoreCuisineMapper, Sto
     @Override
     public List<StoreCuisine> getSingleName() {
         return lambdaQuery()
-                .eq(StoreCuisine::getCuisineType, 1)
+                .eq(StoreCuisine::getCuisineType, 1) // 类型为单品
+                .eq(StoreCuisine::getShelfStatus,1) // 上架
+                .eq(StoreCuisine::getStatus,1)  // 审核通过
                 .list();
     }
 

+ 21 - 17
alien-store/src/main/java/shop/alien/store/service/impl/StoreImgServiceImpl.java

@@ -185,21 +185,21 @@ public class StoreImgServiceImpl extends ServiceImpl<StoreImgMapper, StoreImg> i
         updateWrapper.eq(StoreOfficialAlbum::getId, businessId).set(StoreOfficialAlbum::getImgCount, imgCount);
         storeOfficialAlbumMapper.update(null, updateWrapper);
         List<StoreImg> resList = this.list(lambdaQueryWrapper);
-        // 查询视频列表,只获取第一个视频的封面
-        List<StoreVideo> byStoreId = storeVideoService.getByStoreId(storeId);
-        if (!CollectionUtils.isEmpty(byStoreId)) {
-            StoreVideo storeVideo = byStoreId.get(0);
-            String imgUrl = storeVideo.getImgUrl();
-            if (imgUrl != null && !imgUrl.trim().isEmpty()) {
-                try {
-                    // 解析JSON数组格式的imgUrl
-                    com.alibaba.fastjson.JSONArray jsonArray = com.alibaba.fastjson.JSONArray.parseArray(imgUrl);
-                    if (jsonArray != null && !jsonArray.isEmpty()) {
-                        com.alibaba.fastjson.JSONObject firstItem = jsonArray.getJSONObject(0);
-                        if (firstItem != null && firstItem.containsKey("cover")) {
-                            String coverUrl = firstItem.getString("cover");
+        // 查询对应相册的视频列表,获取第一个视频的封面
+        if (businessId != null && businessId > 0) {
+            List<StoreVideo> videoList = storeVideoService.getByStoreIdAndBusinessId(storeId, businessId);
+            if (!CollectionUtils.isEmpty(videoList)) {
+                StoreVideo storeVideo = videoList.get(0);
+                String imgUrl = storeVideo.getImgUrl();
+                if (imgUrl != null && !imgUrl.trim().isEmpty()) {
+                    try {
+                        // 解析JSON对象格式的imgUrl: {"video": "...", "cover": "..."}
+                        com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSON.parseObject(imgUrl);
+                        if (jsonObject != null && jsonObject.containsKey("cover")) {
+                            String coverUrl = jsonObject.getString("cover");
                             if (coverUrl != null && !coverUrl.trim().isEmpty()) {
-                                // 创建StoreImg对象,设置封面URL
+                                // 创建StoreImg对象,设置封面URL(注意:这是临时对象,id为null是正常的)
+                                // 如果需要避免id为null,可以考虑不添加此对象,或者从视频表查询对应的图片记录
                                 StoreImg storeImg = new StoreImg();
                                 storeImg.setImgUrl(coverUrl);
                                 storeImg.setStoreId(storeId);
@@ -207,13 +207,17 @@ public class StoreImgServiceImpl extends ServiceImpl<StoreImgMapper, StoreImg> i
                                 storeImg.setBusinessId(businessId);
                                 storeImg.setImgDescription("视频");
                                 storeImg.setImgSort(resList.size() + 1);
+                                // 设置一个临时ID标识,避免前端处理null值的问题
+                                // 使用负数作为临时标识,表示这是从视频中提取的封面
+                                int tempId = storeVideo.getId() != null ? -storeVideo.getId() : -(int)(System.currentTimeMillis() % Integer.MAX_VALUE);
+                                storeImg.setId(tempId);
                                 resList.add(storeImg);
-                                log.info("从第一个视频中提取封面成功,coverUrl: {}", coverUrl);
+                                log.info("从视频中提取封面成功,视频ID:{},coverUrl: {}", storeVideo.getId(), coverUrl);
                             }
                         }
+                    } catch (Exception e) {
+                        log.warn("解析视频imgUrl失败,imgUrl: {}, error: {}", imgUrl, e.getMessage());
                     }
-                } catch (Exception e) {
-                    log.warn("解析视频imgUrl失败,imgUrl: {}, error: {}", imgUrl, e.getMessage());
                 }
             }
         }

+ 107 - 10
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -178,6 +178,8 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
     @Resource
     private StoreIncomeDetailsRecordService storeIncomeDetailsRecordService;
 
+    private final LifeUserViolationMapper lifeUserViolationMapper;
+
 
     @Value("${spring.web.resources.excel-path}")
     private String excelPath;
@@ -1206,12 +1208,21 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
 
         storeInfoMapper.insert(storeInfo);
         result.setId(storeInfo.getId());
-        // 新建默认  环境 相册
-        StoreOfficialAlbum officialAlbum = new StoreOfficialAlbum();
-        officialAlbum.setStoreId(storeInfo.getId());
-        officialAlbum.setAlbumName("环境");
-        officialAlbum.setImgCount(0);
-        storeOfficialAlbumMapper.insert(officialAlbum);
+        // 新建默认固定相册:环境相册和视频相册
+        // 创建环境相册
+        StoreOfficialAlbum environmentAlbum = new StoreOfficialAlbum();
+        environmentAlbum.setStoreId(storeInfo.getId());
+        environmentAlbum.setAlbumName("环境");
+        environmentAlbum.setImgCount(0);
+        environmentAlbum.setIsFixed(1); // 1-固定相册
+        storeOfficialAlbumMapper.insert(environmentAlbum);
+        // 创建视频相册
+        StoreOfficialAlbum videoAlbum = new StoreOfficialAlbum();
+        videoAlbum.setStoreId(storeInfo.getId());
+        videoAlbum.setAlbumName("视频");
+        videoAlbum.setImgCount(0);
+        videoAlbum.setIsFixed(1); // 1-固定相册
+        storeOfficialAlbumMapper.insert(videoAlbum);
         if (StringUtils.isNotEmpty(storeInfoDto.getStorePositionLongitude()) && StringUtils.isNotEmpty(storeInfoDto.getStorePositionLatitude())) {
             nearMeService.inGeolocation(new Point(Double.parseDouble(storeInfoDto.getStorePositionLongitude()), Double.parseDouble(storeInfoDto.getStorePositionLatitude())), storeInfo.getId().toString(), Boolean.TRUE);
         }
@@ -3416,17 +3427,25 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         log.info("更新门店头图审核状态,storeId={}, headImgStatus={}", storeId, headImgStatus);
     }
 
+    @Override
+    public boolean manualReviewReport(Integer reportId, Integer type) {
+        LifeUserViolation lifeUserViolation = lifeUserViolationMapper.selectById(reportId);
+        lifeUserViolation.setProcessingStatus(type.toString());
+        int i = lifeUserViolationMapper.updateById(lifeUserViolation);
+        return i > 0 ;
+    }
+
     /**
      * 获取活动banner图
      * 关联查询 store_img 和 store_operational_activity 表
      * 关联条件:store_operational_activity.id = store_img.business_id
      */
     public List<StoreImg> getBannerUrl(String storeId) {
-        // 先查询符合条件的运营活动(只查询进行中的活动,status=5)
+        // 先查询符合条件的运营活动(包含未开始/已售罄/进行中/已结束
         LambdaQueryWrapper<StoreOperationalActivity> activityWrapper = new LambdaQueryWrapper<StoreOperationalActivity>()
                 .eq(StoreOperationalActivity::getStoreId, Integer.parseInt(storeId))
                 .eq(StoreOperationalActivity::getDeleteFlag, 0)
-                .eq(StoreOperationalActivity::getStatus, 5);
+                .in(StoreOperationalActivity::getStatus, 2, 5);
         List<StoreOperationalActivity> activities = storeOperationalActivityMapper.selectList(activityWrapper);
 
         // 如果没有活动,返回空列表
@@ -3434,8 +3453,32 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             return new ArrayList<>();
         }
 
+        // 过滤活动:MARKETING 且报名未开始的不展示
+        Date now = new Date();
+        List<StoreOperationalActivity> filteredActivities = activities.stream()
+                .filter(activity -> {
+                    if (activity == null) {
+                        return false;
+                    }
+                    Integer status = activity.getStatus();
+                    String activityType = activity.getActivityType();
+                    if (!"MARKETING".equals(activityType)) {
+                        // 非MARKETING且未开始,不展示
+                        return status == null || status != 2;
+                    }
+                    if (status == null || (status != 2 && status != 5)) {
+                        return false;
+                    }
+                    Date signupStartTime = activity.getSignupStartTime();
+                    return signupStartTime == null || !now.before(signupStartTime);
+                })
+                .collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(filteredActivities)) {
+            return new ArrayList<>();
+        }
+
         // 获取活动ID列表
-        List<Integer> activityIds = activities.stream()
+        List<Integer> activityIds = filteredActivities.stream()
                 .map(StoreOperationalActivity::getId)
                 .collect(Collectors.toList());
 
@@ -3445,7 +3488,23 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 .eq(StoreImg::getImgType, 26)
                 .eq(StoreImg::getDeleteFlag, 0)
                 .in(StoreImg::getBusinessId, activityIds);
-        return storeImgMapper.selectList(queryWrapper);
+        List<StoreImg> storeImgs = storeImgMapper.selectList(queryWrapper);
+        if (CollectionUtils.isEmpty(storeImgs)) {
+            return storeImgs;
+        }
+
+        // 活动类型映射到图片返回值
+        Map<Integer, String> activityTypeMap = filteredActivities.stream()
+                .collect(Collectors.toMap(StoreOperationalActivity::getId,
+                        activity -> activity.getActivityType() == null ? "" : activity.getActivityType(),
+                        (a, b) -> a));
+        for (StoreImg storeImg : storeImgs) {
+            if (storeImg == null || storeImg.getBusinessId() == null) {
+                continue;
+            }
+            storeImg.setActivityType(activityTypeMap.get(storeImg.getBusinessId()));
+        }
+        return storeImgs;
     }
 
 
@@ -5726,6 +5785,44 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             result.setCollection(1);
         }
 
+        // 当前登录用户是否关注该店铺
+        if (StringUtils.isNotEmpty(userId) && storeUser != null && StringUtils.isNotEmpty(storeUser.getPhone())) {
+            try {
+                // userId是用户ID(数字),查询用户信息获取手机号
+                Integer userIdInt = Integer.parseInt(userId);
+                LifeUser lifeUser = lifeUserMapper.selectById(userIdInt);
+                
+                if (lifeUser != null && StringUtils.isNotEmpty(lifeUser.getUserPhone())) {
+                    // 构造关注关系:followed_id = "store_" + 店铺手机号,fans_id = "user_" + 用户手机号
+                    String followedId = "store_" + storeUser.getPhone();
+                    String fansId = "user_" + lifeUser.getUserPhone();
+
+                    // 查询关注关系
+                    LambdaQueryWrapper<LifeFans> fansWrapper = new LambdaQueryWrapper<>();
+                    fansWrapper.eq(LifeFans::getFollowedId, followedId)
+                            .eq(LifeFans::getFansId, fansId)
+                            .eq(LifeFans::getDeleteFlag, 0);
+                    LifeFans lifeFans = lifeFansMapper.selectOne(fansWrapper);
+
+                    if (lifeFans != null) {
+                        result.setIsFollowed(1); // 已关注
+                    } else {
+                        result.setIsFollowed(0); // 未关注
+                    }
+                } else {
+                    result.setIsFollowed(0); // 无法获取用户手机号,默认未关注
+                }
+            } catch (NumberFormatException e) {
+                log.error("用户ID格式错误 - userId: {}, storeId: {}, error: {}", userId, storeId, e.getMessage());
+                result.setIsFollowed(0); // 用户ID格式错误,默认未关注
+            } catch (Exception e) {
+                log.error("查询用户关注状态失败 - userId: {}, storeId: {}, error: {}", userId, storeId, e.getMessage(), e);
+                result.setIsFollowed(0); // 查询失败,默认未关注
+            }
+        } else {
+            result.setIsFollowed(0); // 用户ID或店铺信息为空,默认未关注
+        }
+
         // 该用户的打卡记录
         LambdaQueryWrapper<StoreClockIn> clockInWrapper = new LambdaQueryWrapper<>();
         clockInWrapper.eq(StoreClockIn::getUserId, userId);

+ 143 - 7
alien-store/src/main/java/shop/alien/store/service/impl/StoreOfficialAlbumServiceImpl.java

@@ -1,5 +1,8 @@
 package shop.alien.store.service.impl;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
@@ -55,8 +58,35 @@ public class StoreOfficialAlbumServiceImpl extends ServiceImpl<StoreOfficialAlbu
     @Override
     @Transactional(rollbackFor = Exception.class)
     public StoreOfficialAlbum createOrUpdateOfficialAlbum(StoreOfficialAlbum storeOfficialAlbum) {
-        Integer id= storeOfficialAlbum.getId();
-        if (id!=null && id > 0) {
+        // 参数校验
+        if (storeOfficialAlbum == null) {
+            throw new IllegalArgumentException("相册信息不能为空");
+        }
+        if (storeOfficialAlbum.getStoreId() == null || storeOfficialAlbum.getStoreId() <= 0) {
+            throw new IllegalArgumentException("门店ID不能为空且必须大于0");
+        }
+        if (StringUtils.isBlank(storeOfficialAlbum.getAlbumName())) {
+            throw new IllegalArgumentException("相册名称不能为空");
+        }
+        
+        // 检查同一门店下相册名称是否重复
+        LambdaQueryWrapper<StoreOfficialAlbum> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreOfficialAlbum::getStoreId, storeOfficialAlbum.getStoreId())
+                .eq(StoreOfficialAlbum::getAlbumName, storeOfficialAlbum.getAlbumName())
+                .eq(StoreOfficialAlbum::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
+        
+        // 如果是更新操作,排除当前记录
+        Integer id = storeOfficialAlbum.getId();
+        if (id != null && id > 0) {
+            queryWrapper.ne(StoreOfficialAlbum::getId, id);
+        }
+        
+        long count = storeOfficialAlbumMapper.selectCount(queryWrapper);
+        if (count > 0) {
+            throw new RuntimeException("同一门店下相册名称不能重复");
+        }
+        
+        if (id != null && id > 0) {
             boolean updateSuccess = storeOfficialAlbumMapper.updateById(storeOfficialAlbum) > 0;
             if (!updateSuccess) {
                 throw new RuntimeException("更新官方相册失败");
@@ -84,10 +114,91 @@ public class StoreOfficialAlbumServiceImpl extends ServiceImpl<StoreOfficialAlbu
             this.updateBatchById(storeOfficialAlbumList);
 
             // 查询官方相册列表
-            return storeOfficialAlbumMapper.getStoreOfficialAlbumList(Integer.parseInt(storeId));
+            List<StoreOfficialAlbumVo> albumVoList = storeOfficialAlbumMapper.getStoreOfficialAlbumList(Integer.parseInt(storeId));
+            
+            // 为每个相册设置第一张图片,如果是视频相册则返回视频封面
+            for (StoreOfficialAlbumVo albumVo : albumVoList) {
+                // 如果是视频相册,查询视频封面
+                if ("视频".equals(albumVo.getAlbumName())) {
+                    String coverUrl = getVideoAlbumCover(Integer.parseInt(storeId), albumVo.getId());
+                    if (StringUtils.isNotBlank(coverUrl)) {
+                        albumVo.setImgUrl(coverUrl);
+                    }
+                }
+                // 其他相册的imgUrl已经在SQL查询中设置了第一张图片
+            }
+            
+            return albumVoList;
         }
         return Collections.emptyList();
     }
+    
+    /**
+     * 获取视频相册的封面(第一个视频的封面)
+     *
+     * @param storeId 门店ID
+     * @param albumId 相册ID
+     * @return 封面URL
+     */
+    private String getVideoAlbumCover(Integer storeId, Integer albumId) {
+        try {
+            // 查询该相册下的第一个视频
+            LambdaQueryWrapper<StoreVideo> videoQueryWrapper = new LambdaQueryWrapper<>();
+            videoQueryWrapper.eq(StoreVideo::getStoreId, storeId)
+                    .eq(StoreVideo::getBusinessId, albumId)
+                    .eq(StoreVideo::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE)
+                    .orderByAsc(StoreVideo::getImgSort)
+                    .orderByAsc(StoreVideo::getCreatedTime)
+                    .last("LIMIT 1");
+            
+            List<StoreVideo> videoList = storeVideoMapper.selectList(videoQueryWrapper);
+            if (!CollectionUtils.isEmpty(videoList)) {
+                StoreVideo firstVideo = videoList.get(0);
+                String imgUrl = firstVideo.getImgUrl();
+                if (StringUtils.isNotBlank(imgUrl)) {
+                    // 解析JSON格式,提取cover字段
+                    return extractCoverFromVideoUrl(imgUrl);
+                }
+            }
+        } catch (Exception e) {
+            log.error("获取视频相册封面失败,门店ID:{},相册ID:{},错误:{}", storeId, albumId, e.getMessage(), e);
+        }
+        return null;
+    }
+    
+    /**
+     * 从视频URL(JSON格式)中提取封面URL
+     *
+     * @param imgUrl 视频URL,格式:{"video": "...", "cover": "..."} 或 [{"video": "...", "cover": "..."}]
+     * @return 封面URL
+     */
+    private String extractCoverFromVideoUrl(String imgUrl) {
+        if (StringUtils.isBlank(imgUrl)) {
+            return null;
+        }
+        
+        try {
+            // 尝试解析为JSON对象
+            JSONObject jsonObject = JSON.parseObject(imgUrl);
+            if (jsonObject != null && jsonObject.containsKey("cover")) {
+                return jsonObject.getString("cover");
+            }
+        } catch (Exception e1) {
+            // 如果不是对象格式,尝试解析为数组格式
+            try {
+                JSONArray jsonArray = JSONArray.parseArray(imgUrl);
+                if (jsonArray != null && !jsonArray.isEmpty()) {
+                    JSONObject firstItem = jsonArray.getJSONObject(0);
+                    if (firstItem != null && firstItem.containsKey("cover")) {
+                        return firstItem.getString("cover");
+                    }
+                }
+            } catch (Exception e2) {
+                log.debug("解析视频URL失败,imgUrl:{}", imgUrl);
+            }
+        }
+        return null;
+    }
 
     /**
      * 根据storeId查询官方相册列表
@@ -149,11 +260,36 @@ public class StoreOfficialAlbumServiceImpl extends ServiceImpl<StoreOfficialAlbu
         }
         //视频
         if (type == 1) {
-            // 查询视频
-            LambdaQueryWrapper<StoreVideo> albumQueryWrapper = new LambdaQueryWrapper<>();
-            albumQueryWrapper.eq(StoreVideo::getStoreId, storeId)
+            // 查询视频相册(只查询视频表)
+            LambdaQueryWrapper<StoreVideo> videoQueryWrapper = new LambdaQueryWrapper<>();
+            videoQueryWrapper.eq(StoreVideo::getStoreId, storeId)
                     .eq(StoreVideo::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
-            List<StoreVideo> videoList = storeVideoMapper.selectList(albumQueryWrapper);
+            
+            // 如果指定了相册名称,根据相册名称找到对应的相册ID,然后查询该相册下的视频
+            if (StringUtils.isNotBlank(albumName)) {
+                LambdaQueryWrapper<StoreOfficialAlbum> albumQueryWrapper = new LambdaQueryWrapper<>();
+                albumQueryWrapper.eq(StoreOfficialAlbum::getStoreId, storeId)
+                        .eq(StoreOfficialAlbum::getAlbumName, albumName)
+                        .eq(StoreOfficialAlbum::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
+                List<StoreOfficialAlbum> albumList = storeOfficialAlbumMapper.selectList(albumQueryWrapper);
+                
+                if (!CollectionUtils.isEmpty(albumList)) {
+                    // 提取相册ID列表
+                    List<Integer> albumIds = albumList.stream()
+                            .map(StoreOfficialAlbum::getId)
+                            .collect(Collectors.toList());
+                    // 根据相册ID筛选视频
+                    videoQueryWrapper.in(StoreVideo::getBusinessId, albumIds);
+                } else {
+                    // 如果未找到对应的相册,返回空结果
+                    log.info("获取视频列表,门店ID:{},相册名称:{},未找到对应的相册", storeId, albumName);
+                    StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
+                    result.setVideoList(Collections.emptyList());
+                    return result;
+                }
+            }
+            
+            List<StoreVideo> videoList = storeVideoMapper.selectList(videoQueryWrapper);
             StoreOfficialAlbumImgVo result = new StoreOfficialAlbumImgVo();
             result.setVideoList(videoList);
             return result;

+ 179 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityAchievementServiceImpl.java

@@ -0,0 +1,179 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import shop.alien.entity.storePlatform.StoreOperationalActivityAchievement;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseDetailVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseItemVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementCaseVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementVo;
+import shop.alien.mapper.storePlantform.StoreOperationalActivityAchievementMapper;
+import shop.alien.store.dto.StoreOperationalActivityAchievementDto;
+import shop.alien.store.service.StoreOperationalActivityAchievementService;
+
+/**
+ * 运营活动成果服务实现
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StoreOperationalActivityAchievementServiceImpl implements StoreOperationalActivityAchievementService {
+
+    private final StoreOperationalActivityAchievementMapper achievementMapper;
+
+    @Override
+    public boolean addAchievement(StoreOperationalActivityAchievementDto dto) {
+        if (dto == null) {
+            throw new IllegalArgumentException("参数不能为空");
+        }
+        if (dto.getActivityId() == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        if (dto.getUserId() == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        if (dto.getSignupId() == null) {
+            throw new IllegalArgumentException("报名ID不能为空");
+        }
+
+        StoreOperationalActivityAchievement achievement = new StoreOperationalActivityAchievement();
+        achievement.setActivityId(dto.getActivityId());
+        achievement.setUserId(dto.getUserId());
+        achievement.setSignupId(dto.getSignupId());
+        achievement.setAchievementDesc(dto.getAchievementDesc());
+        achievement.setMediaUrls(dto.getMediaUrls());
+        achievement.setDeleteFlag(0);
+        achievement.setCreatedUserId(dto.getUserId());
+        return achievementMapper.insert(achievement) > 0;
+    }
+
+    @Override
+    public StoreOperationalActivityAchievementVo getAchievementDetail(Integer id) {
+        if (id == null) {
+            throw new IllegalArgumentException("成果ID不能为空");
+        }
+        StoreOperationalActivityAchievement achievement = achievementMapper.selectById(id);
+        if (achievement == null || achievement.getDeleteFlag() != null && achievement.getDeleteFlag() == 1) {
+            return null;
+        }
+        StoreOperationalActivityAchievementVo vo = new StoreOperationalActivityAchievementVo();
+        BeanUtils.copyProperties(achievement, vo);
+        vo.setMediaUrlList(splitMediaUrls(achievement.getMediaUrls()));
+        return vo;
+    }
+
+    @Override
+    public IPage<StoreOperationalActivityAchievementVo> listAchievements(Integer activityId, Integer userId, Integer signupId,
+                                                                         Integer pageNum, Integer pageSize) {
+        int current = pageNum == null || pageNum <= 0 ? 1 : pageNum;
+        int size = pageSize == null || pageSize <= 0 ? 10 : pageSize;
+        Page<StoreOperationalActivityAchievement> page = new Page<>(current, size);
+
+        LambdaQueryWrapper<StoreOperationalActivityAchievement> wrapper = new LambdaQueryWrapper<>();
+        if (activityId != null) {
+            wrapper.eq(StoreOperationalActivityAchievement::getActivityId, activityId);
+        }
+        if (userId != null) {
+            wrapper.eq(StoreOperationalActivityAchievement::getUserId, userId);
+        }
+        if (signupId != null) {
+            wrapper.eq(StoreOperationalActivityAchievement::getSignupId, signupId);
+        }
+        wrapper.eq(StoreOperationalActivityAchievement::getDeleteFlag, 0)
+                .orderByDesc(StoreOperationalActivityAchievement::getCreatedTime);
+
+        IPage<StoreOperationalActivityAchievement> entityPage = achievementMapper.selectPage(page, wrapper);
+        Page<StoreOperationalActivityAchievementVo> voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
+        if (entityPage.getRecords() != null) {
+            List<StoreOperationalActivityAchievementVo> voList = new ArrayList<>();
+            for (StoreOperationalActivityAchievement item : entityPage.getRecords()) {
+                if (item == null) {
+                    continue;
+                }
+                StoreOperationalActivityAchievementVo vo = new StoreOperationalActivityAchievementVo();
+                BeanUtils.copyProperties(item, vo);
+                vo.setMediaUrlList(splitMediaUrls(item.getMediaUrls()));
+                voList.add(vo);
+            }
+            voPage.setRecords(voList);
+        }
+        return voPage;
+    }
+
+    @Override
+    public IPage<StoreOperationalActivityAchievementCaseVo> listCasePage(Integer activityStatus, Integer pageNum, Integer pageSize) {
+        int current = pageNum == null || pageNum <= 0 ? 1 : pageNum;
+        int size = pageSize == null || pageSize <= 0 ? 10 : pageSize;
+        Page<StoreOperationalActivityAchievementCaseVo> page = new Page<>(current, size);
+        return achievementMapper.selectCasePage(page, activityStatus);
+    }
+
+    @Override
+    public StoreOperationalActivityAchievementCaseDetailVo getCaseDetail(Integer activityId, Integer userId) {
+        if (activityId == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        if (userId == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        StoreOperationalActivityAchievementCaseDetailVo header = achievementMapper.selectCaseHeader(activityId, userId);
+        if (header == null) {
+            return null;
+        }
+        List<StoreOperationalActivityAchievementCaseItemVo> items = achievementMapper.selectCaseItems(activityId, userId);
+        
+        // 根据成果数据判断上传情况:查到数据就是已上传,没有就是未上传
+        if (items != null && !items.isEmpty()) {
+            header.setHasResult(1); // 已上传
+            for (StoreOperationalActivityAchievementCaseItemVo item : items) {
+                if (item == null) {
+                    continue;
+                }
+                item.setMediaUrlList(splitMediaUrls(item.getMediaUrls()));
+            }
+        } else {
+            header.setHasResult(0); // 未上传
+        }
+        
+        header.setAchievementList(items);
+        return header;
+    }
+
+    @Override
+    public IPage<StoreOperationalActivityAchievementCaseVo> listStoreCasePage(Integer storeId, Integer hasResult,
+                                                                               String activityName, Integer pageNum, Integer pageSize) {
+        if (storeId == null || storeId <= 0) {
+            throw new IllegalArgumentException("商户ID不能为空");
+        }
+        int current = pageNum == null || pageNum <= 0 ? 1 : pageNum;
+        int size = pageSize == null || pageSize <= 0 ? 10 : pageSize;
+        Page<StoreOperationalActivityAchievementCaseVo> page = new Page<>(current, size);
+        return achievementMapper.selectStoreCasePage(page, storeId, hasResult, activityName);
+    }
+
+    private List<String> splitMediaUrls(String mediaUrls) {
+        if (mediaUrls == null || mediaUrls.trim().isEmpty()) {
+            return Collections.emptyList();
+        }
+        String[] parts = mediaUrls.split(",");
+        List<String> results = new ArrayList<>();
+        for (String part : parts) {
+            if (part != null && !part.trim().isEmpty()) {
+                results.add(part.trim());
+            }
+        }
+        return results;
+    }
+}

+ 498 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityServiceImpl.java

@@ -0,0 +1,498 @@
+package shop.alien.store.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.LifeDiscountCoupon;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.storePlatform.StoreOperationalActivity;
+import shop.alien.entity.storePlatform.StoreOperationalActivityAchievement;
+import shop.alien.entity.storePlatform.StoreOperationalActivitySignup;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityDetailVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityMySignupVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupCheckVo;
+import shop.alien.mapper.LifeDiscountCouponMapper;
+import shop.alien.mapper.StoreImgMapper;
+import shop.alien.mapper.StoreInfoMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivityAchievementMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivitySignupMapper;
+import shop.alien.store.config.BaseRedisService;
+import shop.alien.store.dto.StoreOperationalActivitySignupDto;
+import shop.alien.store.service.StoreOperationalActivityService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 用户端运营活动服务实现
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StoreOperationalActivityServiceImpl implements StoreOperationalActivityService {
+
+    private final StoreOperationalActivityMapper activityMapper;
+    private final StoreOperationalActivityAchievementMapper achievementMapper;
+    private final StoreImgMapper imgMapper;
+    private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
+    private final StoreInfoMapper storeInfoMapper;
+    private final StoreOperationalActivitySignupMapper signupMapper;
+    private final BaseRedisService baseRedisService;
+
+    @Override
+    public StoreOperationalActivityDetailVo getActivityDetail(Integer id, Integer userId) {
+        /**
+         * 活动详情查询:
+         * 1. 校验活动ID有效性;
+         * 2. 组装详情数据(报名状态、报名人数、活动图片等);
+         * 3. 根据用户报名状态计算 detailStatus。
+         */
+        log.info("StoreOperationalActivityServiceImpl.getActivityDetail: id={}, userId={}", id, userId);
+        if (id == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        StoreOperationalActivity activity = activityMapper.selectById(id);
+        if (activity == null) {
+            return null;
+        }
+
+        StoreOperationalActivityDetailVo vo = new StoreOperationalActivityDetailVo();
+        BeanUtils.copyProperties(activity, vo);
+        vo.setStatusName(resolveStatusName(activity.getStatus()));
+        // 报名状态相关字段填充
+        fillSignupFlags(vo, activity, userId);
+        // 报名人数统计
+        fillSignupCounts(vo);
+        Integer signupStatus = resolveCurrentUserSignupStatus(activity.getId(), userId);
+        // 详情按钮状态计算
+        vo.setDetailStatus(resolveDetailStatus(activity, signupStatus));
+
+        if (activity.getCouponId() != null) {
+            LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
+            if (coupon != null) {
+                vo.setCouponName(coupon.getName());
+            }
+        }
+
+        // 店铺信息与活动图片
+        attachStoreInfo(vo);
+        fillActivityImages(vo);
+        return vo;
+    }
+
+    @Override
+    public StoreOperationalActivityDetailVo getActivityDetailForPlatform(Integer id, Integer userId) {
+        /**
+         * 活动详情查询:
+         * 1. 校验活动ID有效性;
+         * 2. 组装详情数据(报名状态、报名人数、活动图片等);
+         * 3. 根据用户报名状态计算 detailStatus。
+         */
+        log.info("StoreOperationalActivityController.getActivityDetailForPlatform id={}, userId={}", id, userId);
+        if (id == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        StoreOperationalActivity activity = activityMapper.selectById(id);
+        if (activity == null) {
+            return null;
+        }
+
+        StoreOperationalActivityDetailVo vo = new StoreOperationalActivityDetailVo();
+        BeanUtils.copyProperties(activity, vo);
+        vo.setStatusName(resolveStatusName(activity.getStatus()));
+        // 报名状态相关字段填充
+        fillSignupFlags(vo, activity, userId);
+        // 报名人数统计
+        fillSignupCounts(vo);
+        Integer signupStatus = resolveCurrentUserSignupStatus(activity.getId(), userId);
+        // 详情按钮状态计算
+        vo.setDetailStatus(resolveDetailStatus(activity, signupStatus));
+
+        if (activity.getCouponId() != null) {
+            LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
+            if (coupon != null) {
+                vo.setCouponName(coupon.getName());
+            }
+        }
+
+        // 店铺信息与活动图片
+        attachStoreInfo(vo);
+        fillActivityImages(vo);
+        return vo;
+    }
+
+    @Override
+    public StoreOperationalActivitySignupCheckVo checkSignup(Integer activityId, Integer userId) {
+        log.info("StoreOperationalActivityServiceImpl.checkSignup: activityId={}, userId={}", activityId, userId);
+        if (activityId == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        if (userId == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        StoreOperationalActivity activity = activityMapper.selectById(activityId);
+        if (activity == null) {
+            throw new IllegalArgumentException("活动不存在");
+        }
+
+        Integer signupCount = signupMapper.countSignupByActivityId(activityId);
+        Integer limitPeople = activity.getActivityLimitPeople();
+        boolean full = limitPeople != null && limitPeople > 0 && signupCount != null && signupCount >= limitPeople;
+
+        Integer userSignupCount = signupMapper.countSignedUpByActivityAndUser(activityId, userId);
+        boolean signedUp = userSignupCount != null && userSignupCount > 0;
+
+        StoreOperationalActivitySignupCheckVo vo = new StoreOperationalActivitySignupCheckVo();
+        vo.setCurrentSignupCount(signupCount != null ? signupCount : 0);
+        vo.setActivityLimitPeople(limitPeople);
+        vo.setFull(full);
+        vo.setSignedUp(signedUp);
+        return vo;
+    }
+
+    @Override
+    public List<StoreOperationalActivityMySignupVo> listMySignups(Integer userId) {
+        /**
+         * 我的报名列表:
+         * - 聚合活动基础信息、报名审核状态、成果状态等字段;
+         * - 计算列表展示状态(status)与状态文案(statusLabel);
+         * - 只返回未删除的数据。
+         */
+        if (userId == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        List<StoreOperationalActivityMySignupVo> list = signupMapper.selectMySignups(userId);
+        if (list == null || list.isEmpty()) {
+            return Collections.emptyList();
+        }
+        for (StoreOperationalActivityMySignupVo item : list) {
+            if (item == null) {
+                continue;
+            }
+            // 基于报名时间窗口生成展示文案(报名未开始/报名中/报名已结束)
+            item.setStatusLabel(resolveSignupStatusLabel(item.getSignupStatus(), item.getActivityStatus()));
+            // 基于活动状态、报名审核状态与成果状态计算列表展示状态
+            item.setStatus(resolveMySignupStatus(item));
+        }
+        return list;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteSignup(Integer activityId, Integer signupId) {
+        if (activityId == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        if (signupId == null) {
+            throw new IllegalArgumentException("报名ID不能为空");
+        }
+        StoreOperationalActivitySignup signup = signupMapper.selectById(signupId);
+        if (signup == null || (signup.getDeleteFlag() != null && signup.getDeleteFlag() == 1)) {
+            throw new IllegalArgumentException("报名信息不存在或已删除");
+        }
+        if (!activityId.equals(signup.getActivityId())) {
+            throw new IllegalArgumentException("报名信息与活动不匹配");
+        }
+        int deletedSignup = signupMapper.delete(new LambdaQueryWrapper<StoreOperationalActivitySignup>()
+                .eq(StoreOperationalActivitySignup::getId, signupId)
+                .eq(StoreOperationalActivitySignup::getActivityId, activityId));
+        int deletedAchievement = achievementMapper.delete(new LambdaQueryWrapper<StoreOperationalActivityAchievement>()
+                .eq(StoreOperationalActivityAchievement::getSignupId, signupId)
+                .eq(StoreOperationalActivityAchievement::getActivityId, activityId));
+        log.info("deleteSignup success. activityId={}, signupId={}, deletedSignup={}, deletedAchievement={}",
+                activityId, signupId, deletedSignup, deletedAchievement);
+        return deletedSignup > 0 || deletedAchievement > 0;
+    }
+
+    @Override
+    public boolean signup(StoreOperationalActivitySignupDto dto) {
+        if (dto == null) {
+            throw new IllegalArgumentException("报名参数不能为空");
+        }
+        if (dto.getActivityId() == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        if (dto.getUserId() == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+
+        StoreOperationalActivity activity = activityMapper.selectById(dto.getActivityId());
+        if (activity == null) {
+            throw new IllegalArgumentException("活动不存在");
+        }
+        validateSignupTime(activity);
+
+        String lockKey = "activity:signup:" + dto.getActivityId();
+        String lockVal = baseRedisService.lock(lockKey, 5000, 5000);
+        if (lockVal == null) {
+            throw new IllegalArgumentException("系统繁忙,请稍后再试");
+        }
+
+        try {
+            Integer userSignupCount = signupMapper.countApprovedByActivityAndUser(dto.getActivityId(), dto.getUserId());
+            if (userSignupCount != null && userSignupCount > 0) {
+                throw new IllegalArgumentException("已成功报名,请勿重复报名");
+            }
+
+            Integer approvedCount = signupMapper.countSignupByActivityId(dto.getActivityId());
+            Integer limitPeople = activity.getActivityLimitPeople();
+            if (limitPeople != null && limitPeople > 0 && approvedCount != null && approvedCount >= limitPeople) {
+                throw new IllegalArgumentException("报名人数已满");
+            }
+
+            StoreOperationalActivitySignup signup = new StoreOperationalActivitySignup();
+            signup.setActivityId(dto.getActivityId());
+            signup.setStoreId(activity.getStoreId());
+            signup.setUserId(dto.getUserId());
+            signup.setUserName(dto.getUserName());
+            signup.setPhone(dto.getPhone());
+            signup.setStatus(0);
+            signup.setSignupTime(new Date());
+            signup.setDeleteFlag(0);
+            signup.setCreatedUserId(dto.getUserId());
+            return signupMapper.insert(signup) > 0;
+        } finally {
+            baseRedisService.unlock(lockKey, lockVal);
+        }
+    }
+
+    private void validateSignupTime(StoreOperationalActivity activity) {
+        Integer status = activity.getStatus();
+        if (status == null || (status != 2 && status != 5)) {
+            throw new IllegalArgumentException("活动状态不允许报名");
+        }
+        Date now = new Date();
+        if (activity.getSignupStartTime() != null && now.before(activity.getSignupStartTime())) {
+            throw new IllegalArgumentException("未到报名时间");
+        }
+        if (activity.getSignupEndTime() != null && now.after(activity.getSignupEndTime())) {
+            throw new IllegalArgumentException("报名已结束");
+        }
+        if (activity.getStartTime() != null && status == 5 && now.before(activity.getStartTime())) {
+            throw new IllegalArgumentException("活动未开始");
+        }
+        if (activity.getEndTime() != null && now.after(activity.getEndTime())) {
+            throw new IllegalArgumentException("活动已结束");
+        }
+    }
+
+    private String resolveStatusName(Integer status) {
+        if (status == null) {
+            return null;
+        }
+        switch (status) {
+            case 1:
+                return "待审核";
+            case 2:
+                return "未开始";
+            case 3:
+                return "审核拒绝";
+            case 4:
+                return "已售罄";
+            case 5:
+                return "进行中";
+            case 6:
+                return "已下架";
+            case 7:
+                return "已结束";
+            default:
+                return null;
+        }
+    }
+
+    private void attachStoreInfo(StoreOperationalActivityDetailVo vo) {
+        if (vo == null || vo.getStoreId() == null) {
+            return;
+        }
+        StoreInfo storeInfo = storeInfoMapper.selectById(vo.getStoreId());
+        if (storeInfo != null) {
+            vo.setStoreName(storeInfo.getStoreName());
+        }
+    }
+
+    private void fillActivityImages(StoreOperationalActivityDetailVo vo) {
+        if (vo == null || vo.getStoreId() == null || vo.getId() == null) {
+            return;
+        }
+        StoreImg titleImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
+                .eq(StoreImg::getStoreId, vo.getStoreId())
+                .eq(StoreImg::getBusinessId, vo.getId())
+                .eq(StoreImg::getImgType, 26)
+                .eq(StoreImg::getDeleteFlag, 0));
+        if (titleImg != null) {
+            vo.setActivityTitleImgUrl(titleImg.getImgUrl());
+        }
+
+        java.util.List<StoreImg> detailImgs = imgMapper.selectList(new LambdaQueryWrapper<StoreImg>()
+                .eq(StoreImg::getStoreId, vo.getStoreId())
+                .eq(StoreImg::getBusinessId, vo.getId())
+                .eq(StoreImg::getImgType, 27)
+                .eq(StoreImg::getDeleteFlag, 0));
+        if (detailImgs != null && !detailImgs.isEmpty()) {
+            List<String> detailUrls = new ArrayList<>();
+            for (StoreImg item : detailImgs) {
+                if (item != null && item.getImgUrl() != null && !item.getImgUrl().trim().isEmpty()) {
+                    detailUrls.add(item.getImgUrl().trim());
+                }
+            }
+            vo.setActivityDetailImgUrlList(detailUrls);
+        }
+    }
+
+    private void fillSignupCounts(StoreOperationalActivityDetailVo vo) {
+        if (vo == null || vo.getId() == null) {
+            return;
+        }
+        Integer signupCount = signupMapper.countSignupByActivityId(vo.getId());
+        Integer approvedCount = signupMapper.countApprovedByActivityId(vo.getId());
+        vo.setCurrentSignupCount(signupCount != null ? signupCount : 0);
+        vo.setCurrentApprovedCount(approvedCount != null ? approvedCount : 0);
+    }
+
+    private void fillSignupFlags(StoreOperationalActivityDetailVo vo, StoreOperationalActivity activity, Integer userId) {
+        if (vo == null || activity == null) {
+            return;
+        }
+        vo.setInSignupTime(resolveInSignupTime(activity));
+        if (userId == null) {
+            vo.setSignedUp(false);
+            return;
+        }
+        Integer signupCount = signupMapper.countSignedUpByActivityAndUser(activity.getId(), userId);
+        vo.setSignedUp(signupCount != null && signupCount > 0);
+    }
+
+    private boolean resolveInSignupTime(StoreOperationalActivity activity) {
+        if (activity == null) {
+            return false;
+        }
+        Date now = new Date();
+        Date start = activity.getSignupStartTime();
+        Date end = activity.getSignupEndTime();
+        if (start != null && now.before(start)) {
+            return false;
+        }
+        if (end != null && now.after(end)) {
+            return false;
+        }
+        return true;
+    }
+
+    private Integer resolveDetailStatus(StoreOperationalActivity activity, Integer signupStatus) {
+        if (activity == null) {
+            return null;
+        }
+        Date now = new Date();
+        if (activity.getStatus() != null && activity.getStatus() == 7) {
+            return 0;
+        }
+        if (activity.getEndTime() != null && now.after(activity.getEndTime())) {
+            return 0;
+        }
+        Integer status = activity.getStatus();
+        if (status != null && (status == 0 || status == 1 || status == 3 || status == 4 || status == 6)) {
+            return 1;
+        }
+        if (signupStatus != null && signupStatus == 2
+                && activity.getStartTime() != null && now.before(activity.getStartTime())) {
+            return 2;
+        }
+        if (activity.getSignupEndTime() != null && now.after(activity.getSignupEndTime())) {
+            if (status != null && status == 5 && signupStatus != null && signupStatus == 2) {
+                return 3;
+            }
+            return 1;
+        }
+        if (signupStatus != null) {
+            if (signupStatus == 2) {
+                return 3;
+            }
+            if (signupStatus == 0) {
+                return 4;
+            }
+            if (signupStatus == 1) {
+                if (activity.getSignupEndTime() != null && now.before(activity.getSignupEndTime())) {
+                    return 5;
+                }
+                return 1;
+            }
+        }
+        return 5;
+    }
+
+    private Integer resolveCurrentUserSignupStatus(Integer activityId, Integer userId) {
+        if (userId == null || activityId == null) {
+            return null;
+        }
+        return signupMapper.selectLatestSignupStatus(activityId, userId);
+    }
+
+    private String resolveSignupStatusLabel(Integer signupStatus, Integer activityStatus) {
+        if (signupStatus == null) {
+            return null;
+        }
+        if (signupStatus == 0) {
+            return "报名未开始";
+        }
+        if (signupStatus == 1) {
+            return "报名中";
+        }
+        if (signupStatus == 2) {
+            return "报名已结束";
+        }
+        return null;
+    }
+
+    private Integer resolveMySignupStatus(StoreOperationalActivityMySignupVo item) {
+        /**
+         * 列表状态计算规则:
+         * 1 -> 活动已结束且无成果
+         * 2 -> 活动已结束且有成果
+         * 3 -> 活动进行中且有成果
+         * 4 -> 活动进行中且已报名无成果
+         * 5 -> 活动未开始且报名已拒绝
+         * 6 -> 活动未开始且报名成功
+         * 7 -> 报名审核中
+         */
+        if (item == null) {
+            return null;
+        }
+        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 && now.after(endTime));
+        if (ended) {
+            return hasAchievement ? 2 : 1;
+        }
+        if (activityStatus != null && activityStatus == 5) {
+            return hasAchievement ? 3 : 4;
+        }
+        if (activityStatus != null && activityStatus == 2) {
+            if (auditStatus != null && auditStatus == 1) {
+                return 5;
+            }
+            if (auditStatus != null && auditStatus == 2) {
+                return 6;
+            }
+        }
+        if (auditStatus != null && auditStatus == 0
+                && startTime != null && now.before(startTime)) {
+            return 7;
+        }
+        return null;
+    }
+}

+ 214 - 37
alien-store/src/main/java/shop/alien/store/service/impl/StorePlatformMenuServiceImpl.java

@@ -224,55 +224,99 @@ public class StorePlatformMenuServiceImpl extends ServiceImpl<StorePlatformMenuM
                 || StringUtils.hasText(storePlatformMenu.getStatus());
 
         if (isUpdate) {
-            // 更新操作:只允许更新本级菜单的名称、路径、状态
+            // 更新操作:更新菜单的名称、路径、状态
             log.info("开始更新菜单信息,菜单ID={}", menuId);
 
-            // 只更新允许的字段:菜单名称、路径、状态
+            // 保存原始状态,用于判断是否需要级联更新
+            String originalStatus = currentMenu.getStatus();
+            String newStatus = storePlatformMenu.getStatus();
+            boolean statusChanged = StringUtils.hasText(newStatus) && !newStatus.equals(originalStatus);
+            
+            // 保存原始名称,用于判断是否需要校验重名
+            String originalMenuName = currentMenu.getMenuName();
+            String newMenuName = storePlatformMenu.getMenuName();
+            boolean nameChanged = StringUtils.hasText(newMenuName) && !newMenuName.equals(originalMenuName);
+
+            // 1. 如果菜单名称有变化,校验菜单名称是否重复(同一父菜单下不能有重名)
+            if (nameChanged) {
+                LambdaQueryWrapper<StorePlatformMenu> nameCheck = new LambdaQueryWrapper<>();
+                nameCheck.eq(StorePlatformMenu::getMenuName, newMenuName)
+                        .eq(StorePlatformMenu::getParentId, currentMenu.getParentId())
+                        .eq(StorePlatformMenu::getDelFlag, "0")
+                        .ne(StorePlatformMenu::getMenuId, menuId); // 排除当前菜单
+                StorePlatformMenu duplicateMenu = storePlatformMenuMapper.selectOne(nameCheck);
+                if (duplicateMenu != null) {
+                    log.warn("更新菜单失败:菜单名称已存在,菜单名称={}, parentId={}",
+                            newMenuName, currentMenu.getParentId());
+                    return R.fail("该菜单名称在同级菜单中已存在,请更换其他名称");
+                }
+            }
+
+            // 2. 更新菜单名称和路径(如果 provided)
+            boolean needUpdateNameOrPath = false;
             if (StringUtils.hasText(storePlatformMenu.getMenuName())) {
                 currentMenu.setMenuName(storePlatformMenu.getMenuName());
+                needUpdateNameOrPath = true;
             }
             if (StringUtils.hasText(storePlatformMenu.getPath())) {
                 currentMenu.setPath(storePlatformMenu.getPath());
-            }
-            if (StringUtils.hasText(storePlatformMenu.getStatus())) {
-                currentMenu.setStatus(storePlatformMenu.getStatus());
+                needUpdateNameOrPath = true;
             }
 
-            currentMenu.setUpdatedTime(new Date());
-            currentMenu.setUpdateBy("admin");
-//            // 切换状态:0启用,1禁用
-//            String newStatus = "0".equals(currentMenu.getStatus()) ? "1" : "0";
-//            currentMenu.setStatus(newStatus);
-//            // 如果状态为空,默认设置为"0"(开启)
-//            if (!StringUtils.hasText(currentMenu.getStatus())) {
-//                currentMenu.setStatus("0");
-//            }
-
-            // 4. 校验菜单名称是否重复(同一父菜单下不能有重名)
-            LambdaQueryWrapper<StorePlatformMenu> nameCheck = new LambdaQueryWrapper<>();
-            nameCheck.eq(StorePlatformMenu::getMenuName, currentMenu.getMenuName())
-                    .eq(StorePlatformMenu::getParentId, currentMenu.getParentId())
-                    .eq(StorePlatformMenu::getDelFlag, "0");
-            StorePlatformMenu duplicateMenu = storePlatformMenuMapper.selectOne(nameCheck);
-            if (duplicateMenu.getMenuId().equals(menuId)){
-                editUpdateMenuStatus(menuId,currentMenu.getStatus(),currentMenu.getLevel());
-                return R.data(currentMenu);
-            }
-            if (duplicateMenu != null) {
-                log.warn("更新菜单失败:菜单名称已存在,菜单名称={}, parentId={}",
-                        currentMenu.getMenuName(), currentMenu.getParentId());
-                return R.fail("该菜单名称在同级菜单中已存在,请更换其他名称");
+            // 3. 如果只更新名称或路径,直接更新
+            if (needUpdateNameOrPath && !statusChanged) {
+                currentMenu.setUpdatedTime(new Date());
+                currentMenu.setUpdateBy("admin");
+                boolean result = this.updateById(currentMenu);
+                if (result) {
+                    log.info("更新菜单信息成功,菜单ID={}, 菜单名称={}, 路径={}",
+                            menuId, currentMenu.getMenuName(), currentMenu.getPath());
+                    // 重新查询更新后的菜单
+                    currentMenu = storePlatformMenuMapper.selectById(menuId);
+                } else {
+                    log.warn("更新菜单信息失败,菜单ID={}", menuId);
+                    return R.fail("更新菜单信息失败");
+                }
             }
-
-            boolean result = this.updateById(currentMenu);
-            if (result) {
-                log.info("更新菜单信息成功,菜单ID={}, 菜单名称={}, 路径={}, 状态={}",
-                        menuId, currentMenu.getMenuName(), currentMenu.getPath(), currentMenu.getStatus());
+            // 4. 如果状态有变化,调用editUpdateMenuStatus进行级联更新
+            else if (statusChanged) {
+                // 如果同时更新了名称或路径,先更新名称和路径
+                if (needUpdateNameOrPath) {
+                    currentMenu.setUpdatedTime(new Date());
+                    currentMenu.setUpdateBy("admin");
+                    // 注意:这里不更新状态,状态由editUpdateMenuStatus处理
+                    String tempStatus = currentMenu.getStatus();
+                    currentMenu.setStatus(originalStatus); // 临时恢复原状态
+                    boolean result = this.updateById(currentMenu);
+                    if (!result) {
+                        log.warn("更新菜单名称或路径失败,菜单ID={}", menuId);
+                        return R.fail("更新菜单信息失败");
+                    }
+                    currentMenu.setStatus(tempStatus); // 恢复新状态
+                }
+                
+                // 调用editUpdateMenuStatus进行级联状态更新
+                Integer level = currentMenu.getLevel();
+                if (level == null) {
+                    log.warn("更新菜单状态失败:层级信息为空,菜单ID={}", menuId);
+                    return R.fail("层级信息不能为空");
+                }
+                
+                // 调用editUpdateMenuStatus方法,传入新的状态值
+                R<StorePlatformMenu> statusUpdateResult = editUpdateMenuStatusWithNewStatus(menuId, newStatus, level);
+                if (!statusUpdateResult.isSuccess()) {
+                    log.warn("更新菜单状态失败:{}", statusUpdateResult.getMsg());
+                    return statusUpdateResult;
+                }
+                
                 // 重新查询更新后的菜单
                 currentMenu = storePlatformMenuMapper.selectById(menuId);
-            } else {
-                log.warn("更新菜单信息失败,菜单ID={}", menuId);
-                return R.fail("更新菜单信息失败");
+                log.info("更新菜单信息成功(包含级联状态更新),菜单ID={}, 菜单名称={}, 路径={}, 状态={}",
+                        menuId, currentMenu.getMenuName(), currentMenu.getPath(), currentMenu.getStatus());
+            }
+            // 5. 如果只更新状态且状态没有变化,或者没有需要更新的内容
+            else {
+                log.info("没有需要更新的内容,菜单ID={}", menuId);
             }
         }
 
@@ -568,6 +612,139 @@ public class StorePlatformMenuServiceImpl extends ServiceImpl<StorePlatformMenuM
         return R.data(updatedMenu);
     }
 
+    /**
+     * 根据指定的新状态值更新菜单状态(级联更新子菜单)
+     * 用于编辑页面直接设置状态值
+     * 
+     * @param menuId 菜单ID
+     * @param newStatus 新的状态值("0"开启,"1"关闭)
+     * @param level 菜单层级
+     * @return 更新结果
+     */
+    private R<StorePlatformMenu> editUpdateMenuStatusWithNewStatus(Long menuId, String newStatus, Integer level) {
+        // 参数校验:菜单ID不能为空
+        if (menuId == null) {
+            log.warn("更新菜单状态失败:菜单ID为空");
+            return R.fail("菜单ID不能为空");
+        }
+
+        // 参数校验:状态值必须有效
+        if (!"0".equals(newStatus) && !"1".equals(newStatus)) {
+            log.warn("更新菜单状态失败:状态值无效,菜单ID={}, 状态值={}", menuId, newStatus);
+            return R.fail("状态值无效,应为0(开启)或1(关闭)");
+        }
+
+        // 查询当前菜单信息
+        StorePlatformMenu currentMenu = storePlatformMenuMapper.selectById(menuId);
+        if (currentMenu == null) {
+            log.warn("更新菜单状态失败:菜单不存在,菜单ID={}", menuId);
+            return R.fail("菜单不存在");
+        }
+
+        // 检查菜单是否已删除
+        if (!"0".equals(currentMenu.getDelFlag())) {
+            log.warn("更新菜单状态失败:菜单已被删除,菜单ID={}, del_flag={}", menuId, currentMenu.getDelFlag());
+            return R.fail("菜单已被删除");
+        }
+
+        int totalUpdateCount = 0;
+
+        // 根据层级进行级联更新状态
+        if (level == 1) {
+            // 关闭/开启一级菜单:关闭/开启本级和所有子级(二级和三级)
+            log.info("更新一级菜单状态,同时更新所有子级菜单状态,菜单ID={}, 层级={}, 新状态={}", menuId, level, newStatus);
+            
+            // 1. 查找所有子菜单ID(包括二级和三级)
+            List<Long> allChildrenIds = findAllChildrenMenuIds(menuId);
+            
+            // 2. 构建要更新状态的菜单ID列表(包括本级和所有子级)
+            List<Long> menuIdsToUpdate = new ArrayList<>();
+            menuIdsToUpdate.add(menuId);
+            menuIdsToUpdate.addAll(allChildrenIds);
+            
+            // 3. 批量更新状态
+            if (!CollectionUtils.isEmpty(menuIdsToUpdate)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper = new LambdaUpdateWrapper<>();
+                wrapper.in(StorePlatformMenu::getMenuId, menuIdsToUpdate)
+                       .eq(StorePlatformMenu::getDelFlag, "0")
+                       .set(StorePlatformMenu::getStatus, newStatus);
+                int count = storePlatformMenuMapper.update(null, wrapper);
+                totalUpdateCount = count;
+                log.info("更新一级菜单及其所有子菜单状态成功,菜单ID={}, 子菜单数量={}, 总更新记录数={}", 
+                        menuId, allChildrenIds.size(), totalUpdateCount);
+            } else {
+                // 只更新本级菜单状态
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper = new LambdaUpdateWrapper<>();
+                wrapper.eq(StorePlatformMenu::getMenuId, menuId)
+                       .eq(StorePlatformMenu::getDelFlag, "0")
+                       .set(StorePlatformMenu::getStatus, newStatus);
+                int count = storePlatformMenuMapper.update(null, wrapper);
+                totalUpdateCount = count;
+                log.info("更新一级菜单状态成功(无子菜单),菜单ID={}, 更新记录数={}", menuId, totalUpdateCount);
+            }
+
+        } else if (level == 2) {
+            // 关闭/开启二级菜单:关闭/开启本级和所有子级(三级)
+            log.info("更新二级菜单状态,同时更新所有子级菜单状态,菜单ID={}, 层级={}, 新状态={}", menuId, level, newStatus);
+            
+            // 1. 查找所有子菜单ID(三级菜单)
+            List<Long> allChildrenIds = findAllChildrenMenuIds(menuId);
+            
+            // 2. 构建要更新状态的菜单ID列表(包括本级和所有子级)
+            List<Long> menuIdsToUpdate = new ArrayList<>();
+            menuIdsToUpdate.add(menuId);
+            menuIdsToUpdate.addAll(allChildrenIds);
+            
+            // 3. 批量更新状态
+            if (!CollectionUtils.isEmpty(menuIdsToUpdate)) {
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper = new LambdaUpdateWrapper<>();
+                wrapper.in(StorePlatformMenu::getMenuId, menuIdsToUpdate)
+                       .eq(StorePlatformMenu::getDelFlag, "0")
+                       .set(StorePlatformMenu::getStatus, newStatus);
+                int count = storePlatformMenuMapper.update(null, wrapper);
+                totalUpdateCount = count;
+                log.info("更新二级菜单及其所有子菜单状态成功,菜单ID={}, 子菜单数量={}, 总更新记录数={}", 
+                        menuId, allChildrenIds.size(), totalUpdateCount);
+            } else {
+                // 只更新本级菜单状态
+                LambdaUpdateWrapper<StorePlatformMenu> wrapper = new LambdaUpdateWrapper<>();
+                wrapper.eq(StorePlatformMenu::getMenuId, menuId)
+                       .eq(StorePlatformMenu::getDelFlag, "0")
+                       .set(StorePlatformMenu::getStatus, newStatus);
+                int count = storePlatformMenuMapper.update(null, wrapper);
+                totalUpdateCount = count;
+                log.info("更新二级菜单状态成功(无子菜单),菜单ID={}, 更新记录数={}", menuId, totalUpdateCount);
+            }
+
+        } else if (level == 3) {
+            // 关闭/开启三级菜单:只更新本级
+            log.info("更新三级菜单状态,只更新当前菜单,菜单ID={}, 层级={}, 新状态={}", menuId, level, newStatus);
+            
+            LambdaUpdateWrapper<StorePlatformMenu> wrapper = new LambdaUpdateWrapper<>();
+            wrapper.eq(StorePlatformMenu::getMenuId, menuId)
+                   .eq(StorePlatformMenu::getDelFlag, "0")
+                   .set(StorePlatformMenu::getStatus, newStatus);
+            int count = storePlatformMenuMapper.update(null, wrapper);
+            totalUpdateCount = count;
+            
+            if (totalUpdateCount > 0) {
+                log.info("更新三级菜单状态成功,菜单ID={}, 层级={}, 更新记录数={}", menuId, level, totalUpdateCount);
+            } else {
+                log.warn("更新三级菜单状态未找到记录或已删除,菜单ID={}, 层级={}, del_flag={}", 
+                        menuId, level, currentMenu.getDelFlag());
+                return R.fail("操作失败:菜单不存在或已被删除");
+            }
+
+        } else {
+            log.warn("更新菜单状态失败:层级值不正确,菜单ID={}, 层级={}", menuId, level);
+            return R.fail("层级值不正确,应为1、2或3");
+        }
+
+        // 重新查询更新后的菜单信息
+        StorePlatformMenu updatedMenu = storePlatformMenuMapper.selectById(menuId);
+        return R.data(updatedMenu);
+    }
+
     @Override
     public R<StorePlatformMenu> addByMenu(StorePlatformMenu store) {
         log.info("开始新增菜单,菜单名称={}, 一级分类名称={}, 二级分类名称={}", 

+ 19 - 9
alien-store/src/main/java/shop/alien/store/service/impl/StoreRenovationRequirementServiceImpl.java

@@ -572,15 +572,25 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
                         Boolean hasCommunicated = communicationStatusMap.get(communicationKey);
 
                         if (hasCommunicated == null) {
-                            // 查询是否有消息记录(检查双向消息)
-                            LambdaQueryWrapper<LifeMessage> messageWrapper = new LambdaQueryWrapper<>();
-                            messageWrapper.eq(LifeMessage::getDeleteFlag, 0);
-                            messageWrapper.and(w -> w.and(w1 -> w1.eq(LifeMessage::getSenderId, currentUserPhoneId)
-                                            .eq(LifeMessage::getReceiverId, publishingShopPhoneId))
-                                    .or(w2 -> w2.eq(LifeMessage::getSenderId, publishingShopPhoneId)
-                                            .eq(LifeMessage::getReceiverId, currentUserPhoneId)));
-                            Integer messageCount = lifeMessageMapper.selectCount(messageWrapper);
-                            hasCommunicated = messageCount != null && messageCount > 0;
+                            // 判断互相都发送过消息才算沟通过
+                            // 检查 currentUserPhoneId 是否给 publishingShopPhoneId 发送过消息
+                            LambdaQueryWrapper<LifeMessage> messageWrapper1 = new LambdaQueryWrapper<>();
+                            messageWrapper1.eq(LifeMessage::getDeleteFlag, 0)
+                                    .eq(LifeMessage::getSenderId, currentUserPhoneId)
+                                    .eq(LifeMessage::getReceiverId, publishingShopPhoneId);
+                            Integer messageCount1 = lifeMessageMapper.selectCount(messageWrapper1);
+                            boolean hasSentMessage1 = messageCount1 != null && messageCount1 > 0;
+
+                            // 检查 publishingShopPhoneId 是否给 currentUserPhoneId 发送过消息
+                            LambdaQueryWrapper<LifeMessage> messageWrapper2 = new LambdaQueryWrapper<>();
+                            messageWrapper2.eq(LifeMessage::getDeleteFlag, 0)
+                                    .eq(LifeMessage::getSenderId, publishingShopPhoneId)
+                                    .eq(LifeMessage::getReceiverId, currentUserPhoneId);
+                            Integer messageCount2 = lifeMessageMapper.selectCount(messageWrapper2);
+                            boolean hasSentMessage2 = messageCount2 != null && messageCount2 > 0;
+
+                            // 只有双方都发送过消息才算沟通过
+                            hasCommunicated = hasSentMessage1 && hasSentMessage2;
                             communicationStatusMap.put(communicationKey, hasCommunicated);
                         }
 

+ 17 - 14
alien-store/src/main/java/shop/alien/store/service/impl/StoreStaffAuditAsyncService.java

@@ -1,5 +1,6 @@
 package shop.alien.store.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -62,11 +63,11 @@ public class StoreStaffAuditAsyncService {
             log.info("开始异步审核员工内容,staffId={}", staffId);
 
             // 将状态置为"审核中"(0),清空拒绝原因
-            StoreStaffConfig auditingUpdate = new StoreStaffConfig();
-            auditingUpdate.setId(staffId);
-            auditingUpdate.setStatus("0");
-            auditingUpdate.setRejectionReason(null);
-            storeStaffConfigMapper.updateById(auditingUpdate);
+            LambdaUpdateWrapper<StoreStaffConfig> auditingWrapper = new LambdaUpdateWrapper<>();
+            auditingWrapper.eq(StoreStaffConfig::getId, staffId)
+                          .set(StoreStaffConfig::getStatus, "0")
+                          .set(StoreStaffConfig::getRejectionReason, null);
+            storeStaffConfigMapper.update(null, auditingWrapper);
 
             // 组装 AI 审核文本和图片
             StringBuilder textContent = new StringBuilder();
@@ -123,13 +124,15 @@ public class StoreStaffAuditAsyncService {
             // 根据 AI 审核结果更新状态
             // 审核通过:状态保持为"1"(审核通过)
             // 审核失败:状态设置为"2"(审核拒绝)
-            StoreStaffConfig auditUpdate = new StoreStaffConfig();
-            auditUpdate.setId(staffId);
+            LambdaUpdateWrapper<StoreStaffConfig> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreStaffConfig::getId, staffId);
+            
             if (allPassed) {
-                // AI审核通过,状态保持为"审核中"(1)
-                auditUpdate.setStatus("1");
-                auditUpdate.setRejectionReason(null);
-                log.info("人员AI审核通过,状态设置为审核通过:staffId={}", staffId);
+                // AI审核通过,状态设置为"审核通过"(1),明确清空拒绝原因
+                updateWrapper.set(StoreStaffConfig::getStatus, "1")
+                             .set(StoreStaffConfig::getRejectionReason, null);
+                storeStaffConfigMapper.update(null, updateWrapper);
+                log.info("人员AI审核通过,状态设置为审核通过,已清空拒绝原因:staffId={}", staffId);
             } else {
                 // AI审核失败,状态设置为"审核拒绝"(2)
                 // 收集所有失败原因
@@ -147,11 +150,11 @@ public class StoreStaffAuditAsyncService {
                 }
 
                 String reason = failureReasons.isEmpty() ? "审核未通过" : String.join("; ", failureReasons);
+                updateWrapper.set(StoreStaffConfig::getStatus, "2")
+                             .set(StoreStaffConfig::getRejectionReason, reason);
+                storeStaffConfigMapper.update(null, updateWrapper);
                 log.warn("人员AI审核失败,状态设置为审核拒绝:staffId={}, reason={}", staffId, reason);
-                auditUpdate.setStatus("2");
-                auditUpdate.setRejectionReason(reason);
             }
-            storeStaffConfigMapper.updateById(auditUpdate);
 
             log.info("异步审核员工内容完成,staffId={}", staffId);
         } catch (Exception e) {

+ 16 - 8
alien-store/src/main/java/shop/alien/store/service/impl/StoreUserServiceImpl.java

@@ -252,10 +252,12 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
     public Map<String, String> changePhoneVerification(String phone, String oldPassword, String verificationCode) {
         Map<String, String> changePhoneMap = new HashMap<>();
         if (oldPassword != null && !oldPassword.equals("")) {
-            LambdaUpdateWrapper<StoreUser> userLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
-            userLambdaUpdateWrapper.eq(StoreUser::getPhone, phone);
-            StoreUser storeUser = this.getOne(userLambdaUpdateWrapper);
-            if (storeUser.getPassword().equals(oldPassword)) {
+            LambdaQueryWrapper<StoreUser> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
+            userLambdaQueryWrapper.eq(StoreUser::getPhone, phone);
+            StoreUser storeUser = this.getOne(userLambdaQueryWrapper);
+            // 由于password字段使用了EncryptTypeHandler,查询时密码会被自动解密
+            // 所以这里直接比较解密后的密码和用户输入的明文密码
+            if (storeUser != null && storeUser.getPassword() != null && storeUser.getPassword().equals(oldPassword)) {
                 changePhoneMap.put("passwordStatus", "1");
                 return changePhoneMap;
             } else {
@@ -269,7 +271,7 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
     }
 
     private void passwordVerification(String phone, String password, String newPassword, String confirmNewPassword) {
-        LambdaUpdateWrapper<StoreUser> wrapperFans = new LambdaUpdateWrapper<>();
+        LambdaQueryWrapper<StoreUser> wrapperFans = new LambdaQueryWrapper<>();
         wrapperFans.eq(StoreUser::getPhone, phone);
         StoreUser storeUser = this.getOne(wrapperFans);
         if (!newPassword.equals(confirmNewPassword)) {
@@ -280,9 +282,10 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
             log.info("该手机号没有注册过账户");
             throw new RuntimeException("该手机号没有注册过账户");
         } else {
-            wrapperFans.eq(StoreUser::getPassword, password);
-            StoreUser storeUserPw = this.getOne(wrapperFans);
-            if (storeUserPw == null || storeUserPw.getPassword().equals("")) {
+            // 由于password字段使用了EncryptTypeHandler,查询时密码会被自动解密
+            // 所以这里直接比较解密后的密码和用户输入的明文密码
+            String dbPassword = storeUser.getPassword();
+            if (dbPassword == null || dbPassword.isEmpty() || !dbPassword.equals(password)) {
                 log.info("原密码错误");
                 throw new RuntimeException("原密码错误");
             }
@@ -906,6 +909,11 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
         }
     }
 
+    /**
+     *  删除门店
+     * @param storeUserVo
+     */
+
     @Override
     public void deleteStoreAccountInfo(StoreUserVo storeUserVo) {
         LambdaQueryWrapper<StoreUser> queryWrapper = new LambdaQueryWrapper<>();

+ 250 - 40
alien-store/src/main/java/shop/alien/store/service/impl/StoreVideoServiceImpl.java

@@ -1,7 +1,6 @@
 package shop.alien.store.service.impl;
 
 import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -11,9 +10,14 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import shop.alien.entity.store.StoreOfficialAlbum;
 import shop.alien.entity.store.StoreVideo;
+import shop.alien.entity.store.dto.StoreVideoSaveDto;
+import shop.alien.mapper.StoreOfficialAlbumMapper;
 import shop.alien.mapper.StoreVideoMapper;
 import shop.alien.store.service.StoreVideoService;
+import shop.alien.store.util.CommonConstant;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import shop.alien.util.ali.AliOSSUtil;
 import shop.alien.util.common.RandomCreateUtil;
 import shop.alien.util.common.VideoUtils;
@@ -42,6 +46,8 @@ public class StoreVideoServiceImpl extends ServiceImpl<StoreVideoMapper, StoreVi
 
     private final AliOSSUtil aliOSSUtil;
 
+    private final StoreOfficialAlbumMapper storeOfficialAlbumMapper;
+
     /**
      * 视频文件类型列表
      */
@@ -51,42 +57,214 @@ public class StoreVideoServiceImpl extends ServiceImpl<StoreVideoMapper, StoreVi
     private String uploadDir;
 
     /**
-     * 保存视频,自动截取第一帧作为封面
+     * 保存视频(单个或批量)
      *
-     * @param entity 视频实体
+     * @param dto 视频保存DTO,包含门店ID、相册ID和视频URL列表
      * @return 是否保存成功
      */
     @Override
-    public boolean save(StoreVideo entity) {
+    public boolean saveOrSaveBatch(StoreVideoSaveDto dto) {
         // 参数验证
-        if (entity == null) {
-            log.error("StoreVideoServiceImpl.save ERROR: entity is null");
+        if (dto == null) {
+            log.error("StoreVideoServiceImpl.saveOrSaveBatch ERROR: dto is null");
+            return false;
+        }
+        if (dto.getStoreId() == null || dto.getStoreId() <= 0) {
+            log.error("StoreVideoServiceImpl.saveOrSaveBatch ERROR: storeId is invalid");
+            return false;
+        }
+        if (dto.getVideoUrls() == null || dto.getVideoUrls().isEmpty()) {
+            log.error("StoreVideoServiceImpl.saveOrSaveBatch ERROR: videoUrls is empty");
             return false;
         }
 
-        // 如果imgUrl不为空,尝试处理视频和封面
-        if (StringUtils.isNotBlank(entity.getImgUrl())) {
-            try {
-                // 处理视频URL,截取封面并更新imgUrl字段
-                String processedImgUrl = processVideoAndCover(entity.getImgUrl());
-                if (StringUtils.isNotBlank(processedImgUrl)) {
-                    entity.setImgUrl(processedImgUrl);
+        // 解析视频URL列表,前端只传入视频URL,后端自动匹配或生成封面
+        // 排序号将根据传入数组的顺序设置(从1开始),用于支持编辑排序功能
+        List<StoreVideo> videoList = new java.util.ArrayList<>();
+        List<String> videoUrls = dto.getVideoUrls();
+        List<Integer> videoIds = dto.getVideoIds(); // 可选的视频ID列表,用于编辑
+        
+        // 遍历URL列表,每个URL都是视频URL
+        int videoIndex = 0;
+        for (int i = 0; i < videoUrls.size(); i++) {
+            String videoUrl = videoUrls.get(i);
+            if (StringUtils.isBlank(videoUrl)) {
+                log.warn("视频URL为空,跳过该视频");
+                continue;
+            }
+            
+            // 获取对应的视频ID(如果存在)
+            Integer videoId = null;
+            if (videoIds != null && i < videoIds.size()) {
+                videoId = videoIds.get(i);
+            }
+            
+            // 如果ID存在且有效,尝试查询现有视频进行更新
+            StoreVideo storeVideo = null;
+            if (videoId != null && videoId > 0) {
+                try {
+                    storeVideo = this.getById(videoId);
+                    if (storeVideo != null && storeVideo.getDeleteFlag() == CommonConstant.DELETE_FLAG_UNDELETE) {
+                        log.info("找到现有视频,ID:{},将进行更新,排序号将根据数组顺序设置为:{}", videoId, videoIndex + 1);
+                    } else {
+                        log.warn("视频ID:{} 不存在或已删除,将作为新增处理", videoId);
+                        storeVideo = null;
+                    }
+                } catch (Exception e) {
+                    log.error("查询视频失败,ID:{},错误:{},将作为新增处理", videoId, e.getMessage());
+                    storeVideo = null;
+                }
+            }
+            
+            // 如果不存在现有视频,创建新对象
+            if (storeVideo == null) {
+                storeVideo = new StoreVideo();
+                storeVideo.setId(null); // 确保ID为null,表示新增
+                // 新增时,根据数组顺序设置排序号(从1开始)
+                storeVideo.setImgSort(videoIndex + 1);
+                storeVideo.setImgDescription("相册视频");
+            } else {
+                // 更新时,根据数组顺序更新排序号(从1开始)
+                // 这样可以根据传入的数组顺序来调整视频的排序
+                storeVideo.setImgSort(videoIndex + 1);
+                // 更新时,如果描述未设置,使用默认值
+                if (StringUtils.isBlank(storeVideo.getImgDescription())) {
+                    storeVideo.setImgDescription("相册视频");
+                }
+            }
+            
+            // 设置基本信息
+            storeVideo.setStoreId(dto.getStoreId());
+            storeVideo.setBusinessId(dto.getBusinessId());
+            
+            // 尝试根据视频URL自动匹配封面URL(从/file/uploadMore接口获取的封面URL格式)
+            // 封面URL格式:视频URL的扩展名从.mp4等替换为.jpg
+            String coverUrl = tryMatchCoverUrl(videoUrl);
+            
+            // 如果匹配不到封面URL,尝试处理视频生成封面
+            if (StringUtils.isBlank(coverUrl)) {
+                log.info("未找到匹配的封面URL,视频URL:{},将尝试处理生成封面", videoUrl);
+                try {
+                    // 调用processVideoAndCover处理,生成封面
+                    String processedImgUrl = processVideoAndCover(videoUrl);
+                    if (StringUtils.isNotBlank(processedImgUrl)) {
+                        // 如果处理成功,processedImgUrl已经是JSON格式,直接使用
+                        storeVideo.setImgUrl(processedImgUrl);
+                        videoList.add(storeVideo);
+                        videoIndex++;
+                        continue;
+                    }
+                } catch (Exception e) {
+                    log.error("处理视频封面失败,视频URL:{},错误:{},将保存视频但不包含封面", videoUrl, e.getMessage(), e);
+                    // 处理失败时,继续执行下面的逻辑,保存视频但不包含封面
                 }
+            }
+            
+            // 组合成JSON对象格式(即使没有封面也保存)
+            JSONObject videoJson = new JSONObject();
+            videoJson.put("video", videoUrl);
+            if (StringUtils.isNotBlank(coverUrl)) {
+                videoJson.put("cover", coverUrl);
+            }
+            
+            storeVideo.setImgUrl(JSON.toJSONString(videoJson));
+            
+            videoList.add(storeVideo);
+            videoIndex++;
+        }
+
+        // 批量保存或更新
+        if (videoList.isEmpty()) {
+            log.warn("没有有效的视频数据需要保存");
+            return false;
+        }
+
+        boolean saveResult;
+        // 分离新增和更新的视频
+        List<StoreVideo> insertList = new java.util.ArrayList<>();
+        List<StoreVideo> updateList = new java.util.ArrayList<>();
+        
+        for (StoreVideo video : videoList) {
+            if (video.getId() != null && video.getId() > 0) {
+                updateList.add(video);
+            } else {
+                insertList.add(video);
+            }
+        }
+        
+        // 执行新增和更新
+        try {
+            if (!insertList.isEmpty()) {
+                if (insertList.size() == 1) {
+                    saveResult = super.save(insertList.get(0));
+                } else {
+                    saveResult = super.saveBatch(insertList);
+                }
+                log.info("新增视频数量:{}", insertList.size());
+            } else {
+                saveResult = true; // 如果没有新增,默认成功
+            }
+            
+            if (!updateList.isEmpty()) {
+                boolean updateResult;
+                if (updateList.size() == 1) {
+                    updateResult = super.updateById(updateList.get(0));
+                } else {
+                    updateResult = super.updateBatchById(updateList);
+                }
+                saveResult = saveResult && updateResult;
+                log.info("更新视频数量:{}", updateList.size());
+            }
+        } catch (Exception e) {
+            log.error("保存或更新视频失败,错误:{}", e.getMessage(), e);
+            return false;
+        }
+
+        // 保存成功后,更新相册的imgCount
+        if (saveResult && dto.getBusinessId() != null && dto.getBusinessId() > 0) {
+            try {
+                updateAlbumImgCount(dto.getBusinessId());
             } catch (Exception e) {
-                log.error("StoreVideoServiceImpl.save 处理视频封面失败, imgUrl: {}", entity.getImgUrl(), e);
-                // 如果处理失败,记录错误日志但不影响保存操作
+                log.error("更新相册imgCount失败,相册ID:{},错误:{}", dto.getBusinessId(), e.getMessage(), e);
+                // 不影响保存结果,只记录错误日志
             }
         }
 
-        // 调用父类的save方法保存
-        return super.save(entity);
+        return saveResult;
+    }
+
+    /**
+     * 更新相册的imgCount(统计该相册下的视频数量)
+     *
+     * @param albumId 相册ID
+     */
+    private void updateAlbumImgCount(Integer albumId) {
+        try {
+            // 统计该相册下的视频数量
+            LambdaQueryWrapper<StoreVideo> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(StoreVideo::getBusinessId, albumId)
+                    .eq(StoreVideo::getDeleteFlag, CommonConstant.DELETE_FLAG_UNDELETE);
+            long videoCount = this.count(queryWrapper);
+
+            // 更新相册的imgCount
+            LambdaUpdateWrapper<StoreOfficialAlbum> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(StoreOfficialAlbum::getId, albumId)
+                    .set(StoreOfficialAlbum::getImgCount, (int) videoCount);
+            storeOfficialAlbumMapper.update(null, updateWrapper);
+
+            log.info("更新相册imgCount成功,相册ID:{},视频数量:{}", albumId, videoCount);
+        } catch (Exception e) {
+            log.error("更新相册imgCount失败,相册ID:{},错误:{}", albumId, e.getMessage(), e);
+            throw e;
+        }
     }
 
     /**
-     * 处理视频URL,截取第一帧作为封面并生成JSON数组
+     * 处理视频URL,截取第一帧作为封面并生成JSON对象
+     * 只支持JSON对象格式:{"video": "...", "cover": "..."}
      *
-     * @param imgUrl 视频URL或JSON字符串
-     * @return 处理后的JSON数组字符串
+     * @param imgUrl 视频URL或JSON对象字符串
+     * @return 处理后的JSON对象字符串,格式:{"video": "...", "cover": "..."}
      */
     private String processVideoAndCover(String imgUrl) {
         log.info("StoreVideoServiceImpl.processVideoAndCover imgUrl={}", imgUrl);
@@ -98,20 +276,18 @@ public class StoreVideoServiceImpl extends ServiceImpl<StoreVideoMapper, StoreVi
         }
 
         String videoUrl = null;
+        String coverUrl = null;
 
-        // 判断imgUrl是否为JSON格式
+        // 判断imgUrl是否为JSON对象格式
         try {
-            JSONArray jsonArray = JSONArray.parseArray(imgUrl);
-            // 如果已经是JSON数组格式,检查是否包含video和cover
-            if (jsonArray != null && !jsonArray.isEmpty()) {
-                JSONObject firstItem = jsonArray.getJSONObject(0);
-                if (firstItem != null && firstItem.containsKey("video")) {
-                    videoUrl = firstItem.getString("video");
-                    // 如果已经有cover,则直接返回
-                    if (firstItem.containsKey("cover") && StringUtils.isNotBlank(firstItem.getString("cover"))) {
-                        log.info("StoreVideoServiceImpl.processVideoAndCover 已有封面,无需重新生成");
-                        return imgUrl;
-                    }
+            JSONObject jsonObject = JSON.parseObject(imgUrl);
+            if (jsonObject != null && jsonObject.containsKey("video")) {
+                videoUrl = jsonObject.getString("video");
+                coverUrl = jsonObject.getString("cover");
+                // 如果已经有cover,则直接返回
+                if (StringUtils.isNotBlank(coverUrl)) {
+                    log.info("StoreVideoServiceImpl.processVideoAndCover 已有封面,无需重新生成");
+                    return imgUrl;
                 }
             }
         } catch (Exception e) {
@@ -159,21 +335,19 @@ public class StoreVideoServiceImpl extends ServiceImpl<StoreVideoMapper, StoreVi
             // 上传封面到OSS
             String coverFileName = generateCoverFileName(videoUrl);
             String coverOssPath = "video/" + coverFileName + ".jpg";
-            String coverUrl = aliOSSUtil.uploadFile(coverFile, coverOssPath);
+            coverUrl = aliOSSUtil.uploadFile(coverFile, coverOssPath);
             if (StringUtils.isBlank(coverUrl)) {
                 log.error("StoreVideoServiceImpl.processVideoAndCover 上传封面失败: {}", coverOssPath);
                 return imgUrl;
             }
 
-            // 构建JSON数组
-            JSONArray resultArray = new JSONArray();
-            JSONObject videoObject = new JSONObject();
-            videoObject.put("video", videoUrl);
-            videoObject.put("cover", coverUrl);
-            resultArray.add(videoObject);
+            // 构建JSON对象(格式:{"video": "...", "cover": "..."})
+            JSONObject resultObject = new JSONObject();
+            resultObject.put("video", videoUrl);
+            resultObject.put("cover", coverUrl);
 
             log.info("StoreVideoServiceImpl.processVideoAndCover 处理成功, videoUrl: {}, coverUrl: {}", videoUrl, coverUrl);
-            return JSON.toJSONString(resultArray);
+            return JSON.toJSONString(resultObject);
 
         } catch (Exception e) {
             log.error("StoreVideoServiceImpl.processVideoAndCover 处理异常", e);
@@ -387,5 +561,41 @@ public class StoreVideoServiceImpl extends ServiceImpl<StoreVideoMapper, StoreVi
                 .orderByAsc(StoreVideo::getImgSort);
         return this.list(lambdaQueryWrapper);
     }
+
+    /**
+     * 尝试根据视频URL匹配封面URL
+     * 从/file/uploadMore接口获取的封面URL格式:视频URL的扩展名从.mp4等替换为.jpg
+     *
+     * @param videoUrl 视频URL
+     * @return 封面URL,如果匹配不到则返回null
+     */
+    private String tryMatchCoverUrl(String videoUrl) {
+        if (StringUtils.isBlank(videoUrl)) {
+            return null;
+        }
+        
+        try {
+            // 检查是否为视频URL
+            if (!isVideoUrl(videoUrl)) {
+                return null;
+            }
+            
+            // 提取视频文件名(不含扩展名)
+            int lastDotIndex = videoUrl.lastIndexOf('.');
+            int lastSlashIndex = Math.max(videoUrl.lastIndexOf('/'), videoUrl.lastIndexOf('\\'));
+            
+            if (lastDotIndex > lastSlashIndex && lastDotIndex > 0) {
+                // 构建封面URL:将扩展名替换为.jpg
+                String coverUrl = videoUrl.substring(0, lastDotIndex) + ".jpg";
+                log.debug("尝试匹配封面URL,视频URL:{},封面URL:{}", videoUrl, coverUrl);
+                return coverUrl;
+            }
+        } catch (Exception e) {
+            log.debug("匹配封面URL失败,视频URL:{},错误:{}", videoUrl, e.getMessage());
+        }
+        
+        return null;
+    }
+
 }
 

+ 27 - 1
alien-store/src/main/java/shop/alien/store/service/impl/SubAccountStoreServiceImpl.java

@@ -1,9 +1,15 @@
 package shop.alien.store.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import shop.alien.entity.store.StorePlatformRole;
+import shop.alien.entity.store.StorePlatformUserRole;
+import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.SubAccountStoreListVo;
+import shop.alien.mapper.StorePlatformUserRoleMapper;
+import shop.alien.mapper.StoreUserMapper;
 import shop.alien.mapper.SubAccountStoreMapper;
 import shop.alien.store.service.SubAccountStoreService;
 
@@ -23,14 +29,34 @@ public class SubAccountStoreServiceImpl implements SubAccountStoreService {
 
     private final SubAccountStoreMapper subAccountStoreMapper;
 
+    private final StorePlatformUserRoleMapper storePlatformUserRoleMapper;
+
+    private final StoreUserMapper storeUserMapper;
+
     @Override
     public List<SubAccountStoreListVo> getSubAccountStoreList(Integer userId) {
         log.info("SubAccountStoreServiceImpl.getSubAccountStoreList?userId={}", userId);
+        List<SubAccountStoreListVo> storeList = new ArrayList<>();
         if (userId == null) {
             log.warn("用户ID为空");
             return new ArrayList<>();
         }
-        List<SubAccountStoreListVo> storeList = subAccountStoreMapper.selectSubAccountStoreListByUserId(userId);
+        List<StorePlatformUserRole> list = new ArrayList<>();
+        StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>()
+                .eq(StoreUser :: getId, userId));
+        if(storeUser!=null){
+            if(storeUser.getAccountType() != 1){
+                list = storePlatformUserRoleMapper.selectList(new LambdaQueryWrapper<StorePlatformUserRole>()
+                        .eq(StorePlatformUserRole :: getUserId, userId));
+            }
+        }
+        if(list.size()>0 || storeUser.getStoreId() != null){
+            //查出主子账号所有门店信息回显
+            storeList = subAccountStoreMapper.selectSubAccountStoreListByUserIdTwo(userId);
+        }else{
+            //主账号没有门店 子账号有门店 查出主账号 账号信息和子账号门店信息组合回显
+            storeList = subAccountStoreMapper.selectSubAccountStoreListByUserId(userId);
+        }
         log.info("查询子账号关联门店列表完成 - 用户ID: {}, 门店数量: {}", userId, storeList != null ? storeList.size() : 0);
         return storeList;
     }

+ 17 - 0
alien-store/src/main/java/shop/alien/store/service/impl/TrackEventServiceImpl.java

@@ -1003,4 +1003,21 @@ public class TrackEventServiceImpl extends ServiceImpl<StoreTrackEventMapper, St
         }
     }
 
+    @Override
+    public Long getStoreViewCount(Integer storeId) {
+        try {
+            if (storeId == null || storeId <= 0) {
+                log.warn("店铺ID无效: storeId={}", storeId);
+                return 0L;
+            }
+            
+            StoreTrackEventMapper mapper = this.getBaseMapper();
+            Long viewCount = mapper.countStoreViewCount(storeId);
+            return viewCount != null ? viewCount : 0L;
+        } catch (Exception e) {
+            log.error("统计店铺浏览量失败: storeId={}", storeId, e);
+            return 0L;
+        }
+    }
+
 }

+ 4 - 3
alien-store/src/main/java/shop/alien/store/util/ai/AiFeedbackAssignUtils.java

@@ -40,8 +40,8 @@ public class AiFeedbackAssignUtils {
     private String assignStaffUrl;
 
     // 语音识别接口地址(从配置中读取,如果没有配置则使用默认值)
-    @Value("${feign.alienAI.url:}")
-    private String aiServiceBaseUrl;
+    // @Value("${feign.alienAI.url}")
+    // private String aiServiceBaseUrl;
 
     /**登录的语音识别文件登录
      * 登录 AI 服务,获取 token
@@ -119,7 +119,8 @@ public class AiFeedbackAssignUtils {
             HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
 
             // 构建完整的URL
-            String url = aiServiceBaseUrl + "/asr/upload";
+            // String url = aiServiceBaseUrl + "/asr/upload";
+            String url = "http://124.93.18.180:8891/asr/upload";
             log.info("调用AI语音识别接口,URL: {}", url);
 
             // 使用 RestTemplate 发送请求

+ 219 - 0
alien-store/src/main/java/shop/alien/store/util/ai/AiFeedbackEmailUtil.java

@@ -0,0 +1,219 @@
+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.HashMap;
+import java.util.Map;
+
+/**
+ * 邮件分发工具类
+ * 调用AI邮件发送接口发送反馈邮件
+ */
+@Slf4j
+@Component
+@RefreshScope
+@RequiredArgsConstructor
+public class AiFeedbackEmailUtil {
+
+    private final RestTemplate restTemplate;
+
+    /**
+     * 邮件发送接口地址
+     */
+    @Value("${feedback-email-url:http://124.93.18.180:9000/ai/smart-customer/api/v1/feedback_email/send}")
+    private String feedbackEmailUrl;
+
+    /**
+     * 反馈类型枚举
+     */
+    public enum FeedbackType {
+        BUG("0", "bug反馈"),
+        OPTIMIZATION("1", "优化反馈"),
+        NEW_FEATURE("2", "新增功能反馈");
+
+        private final String code;
+        private final String desc;
+
+        FeedbackType(String code, String desc) {
+            this.code = code;
+            this.desc = desc;
+        }
+
+        public String getCode() {
+            return code;
+        }
+
+        public String getDesc() {
+            return desc;
+        }
+    }
+
+    /**
+     * 邮件发送结果类
+     */
+    public static class EmailSendResult {
+        private boolean success;
+        private String message;
+        private Long ticketId;
+
+        public EmailSendResult(boolean success, String message, Long ticketId) {
+            this.success = success;
+            this.message = message;
+            this.ticketId = ticketId;
+        }
+
+        public boolean isSuccess() {
+            return success;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+
+        public Long getTicketId() {
+            return ticketId;
+        }
+    }
+
+    /**
+     * 发送反馈邮件
+     *
+     * @param feedbackType 问题反馈类型(0-bug反馈,1-优化反馈,2-新增功能反馈)
+     * @param feedbackContent 反馈内容(必填)
+     * @param userId 用户ID(可选)
+     * @param feedbackSource 反馈来源(可选,0,1)
+     * @param contactWay 联系方式(可选)
+     * @return 邮件发送结果
+     */
+    public EmailSendResult sendFeedbackEmail(String feedbackType, String feedbackContent, 
+                                             Integer userId, String feedbackSource, String contactWay) {
+        log.info("开始发送反馈邮件:feedbackType={}, feedbackContent={}, userId={}, feedbackSource={}, contactWay={}",
+                feedbackType, feedbackContent, userId, feedbackSource, contactWay);
+
+        // 参数校验
+        if (!StringUtils.hasText(feedbackContent)) {
+            log.error("反馈内容不能为空");
+            return new EmailSendResult(false, "反馈内容不能为空", null);
+        }
+
+        try {
+            // 调用邮件发送接口
+            return callFeedbackEmailApi(feedbackType, feedbackContent, userId, feedbackSource, contactWay);
+        } catch (Exception e) {
+            log.error("发送反馈邮件异常", e);
+            return new EmailSendResult(false, "发送邮件异常:" + e.getMessage(), null);
+        }
+    }
+
+    /**
+     * 调用邮件发送接口
+     *
+     * @param feedbackType 问题反馈类型
+     * @param feedbackContent 反馈内容
+     * @param userId 用户ID
+     * @param feedbackSource 反馈来源
+     * @param contactWay 联系方式
+     * @return 邮件发送结果
+     */
+    private EmailSendResult callFeedbackEmailApi(String feedbackType, String feedbackContent,
+                                                 Integer userId, String feedbackSource, String contactWay) {
+        try {
+            // 构建请求体
+            Map<String, Object> requestBody = new HashMap<>();
+            if (StringUtils.hasText(feedbackType)) {
+                requestBody.put("feedback_type", feedbackType);
+            }
+            requestBody.put("feedback_content", feedbackContent);
+            if (userId != null) {
+                requestBody.put("user_id", userId);
+            }
+            if (StringUtils.hasText(feedbackSource)) {
+                requestBody.put("feedback_source", feedbackSource);
+            }
+            if (StringUtils.hasText(contactWay)) {
+                requestBody.put("contact_way", contactWay);
+            }
+
+            // 设置请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
+
+            log.info("调用邮件发送接口:url={}, requestBody={}", feedbackEmailUrl, requestBody);
+
+            // 发送请求
+            ResponseEntity<String> response = restTemplate.postForEntity(feedbackEmailUrl, 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 parseEmailSendResult(jsonResponse);
+                } else {
+                    log.error("邮件发送接口返回空响应");
+                    return new EmailSendResult(false, "邮件发送接口返回空响应", null);
+                }
+            } else {
+                log.error("邮件发送接口调用失败,状态码:{}", response.getStatusCode());
+                return new EmailSendResult(false, "邮件发送接口调用失败,状态码:" + response.getStatusCode(), null);
+            }
+
+        } catch (org.springframework.web.client.HttpClientErrorException e) {
+            log.error("调用邮件发送接口失败,HTTP错误: {}, 响应体: {}", 
+                    e.getStatusCode(), e.getResponseBodyAsString(), e);
+            return new EmailSendResult(false, "调用邮件发送接口失败: " + e.getStatusCode() + 
+                    ", 响应: " + e.getResponseBodyAsString(), null);
+        } catch (Exception e) {
+            log.error("调用邮件发送接口异常", e);
+            return new EmailSendResult(false, "调用邮件发送接口异常:" + e.getMessage(), null);
+        }
+    }
+
+    /**
+     * 解析邮件发送结果
+     *
+     * @param jsonResponse API响应JSON
+     * @return 邮件发送结果
+     */
+    private EmailSendResult parseEmailSendResult(JSONObject jsonResponse) {
+        try {
+            // API返回格式:
+            // {
+            //     "success": true,
+            //     "message": "邮件通知已发送成功",
+            //     "ticket_id": 1769655115282
+            // }
+
+            Boolean success = jsonResponse.getBoolean("success");
+            String message = jsonResponse.getString("message");
+            Long ticketId = jsonResponse.getLong("ticket_id");
+
+            if (Boolean.TRUE.equals(success)) {
+                log.info("邮件发送成功:ticketId={}, message={}", ticketId, message);
+                return new EmailSendResult(true, message, ticketId);
+            } else {
+                log.warn("邮件发送失败:message={}", message);
+                return new EmailSendResult(false, message != null ? message : "邮件发送失败", ticketId);
+            }
+
+        } catch (Exception e) {
+            log.error("解析邮件发送结果异常", e);
+            return new EmailSendResult(false, "解析邮件发送结果异常:" + e.getMessage(), null);
+        }
+    }
+}
+

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

@@ -0,0 +1,71 @@
+package shop.alien.store.util.ai;
+
+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.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * AI 举报审核(举报评论,举报动态)工具类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+
+@Slf4j
+@Component
+@RefreshScope
+@RequiredArgsConstructor
+public class AiReportReviewUtil {
+
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+    private final RestTemplate restTemplate;
+
+    @Value("${third-party-ai.report-review.base-url:http://124.93.18.180:9000/ai/auto-review/api/v1/merchant_dynamic_violation_audit_task/submit}")
+    private String aiReportReviewUrl;
+
+    @Async("taskExecutor")
+    public void reviewReport(Integer reportId, String reportType) {
+
+        if(!StringUtils.hasText(reportId.toString())){
+            throw new IllegalArgumentException("reportId 不能为空");
+        }
+
+        if(!StringUtils.hasText(reportType)){
+            throw new IllegalArgumentException("reportType 不能为空");
+        }
+
+
+        // 1. 登录 AI 服务,获取 token
+        String accessToken = aiAuthTokenUtil.getAccessToken();
+        if(!StringUtils.hasText(accessToken)){
+            throw new IllegalArgumentException("accessToken 不能为空");
+        }
+
+        // 构建请求体
+        Map<String, Object> requestBody = new HashMap<>();
+        requestBody.put("id", reportId);
+        requestBody.put("type", reportType);
+        // 构建请求头,添加Authorization
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        headers.set("Authorization", "Bearer " + accessToken);
+        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
+        log.info("调用ai举报接口URL:{},requestBody: {}", aiReportReviewUrl, requestBody);
+        ResponseEntity<String> response = restTemplate.postForEntity(aiReportReviewUrl, request, String.class);
+        if(response.getStatusCode().isError()){
+            log.error("调用ai举报接口失败,URL:{},requestBody: {},response: {}", aiReportReviewUrl, requestBody, response);
+        }
+    }
+}