浏览代码

Merge branch 'sit' into uat

# Conflicts:
#	alien-entity/src/main/java/shop/alien/mapper/StoreCommentAppealMapper.java
#	alien-second/src/main/java/shop/alien/second/util/AiTaskUtils.java
#	alien-store-platform/pom.xml
#	alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java
#	alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java
#	alien-store/src/main/java/shop/alien/store/service/LifeUserStoreService.java
lutong 17 小时之前
父节点
当前提交
7454a3dc09
共有 65 个文件被更改,包括 4638 次插入96 次删除
  1. 74 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeFeedback.java
  2. 49 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeFeedbackReply.java
  3. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeImg.java
  4. 18 3
      alien-entity/src/main/java/shop/alien/entity/store/LifeLog.java
  5. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/StoreCommentAppeal.java
  6. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/StoreImg.java
  7. 48 2
      alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java
  8. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/FeedbackReplyDto.java
  9. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackAssignDto.java
  10. 39 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackDto.java
  11. 35 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackQueryDto.java
  12. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackReplyWebDto.java
  13. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackStatusDto.java
  14. 12 3
      alien-entity/src/main/java/shop/alien/entity/store/dto/StoreInfoDto.java
  15. 33 0
      alien-entity/src/main/java/shop/alien/entity/store/dto/UserReplyDto.java
  16. 29 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackAttachmentVo.java
  17. 41 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackLogVo.java
  18. 45 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackReplyVo.java
  19. 23 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackTypeVo.java
  20. 78 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackDetailVo.java
  21. 62 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackListVo.java
  22. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackVo.java
  23. 26 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/LifeStaffListVo.java
  24. 1 1
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java
  25. 9 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDTO.java
  26. 80 0
      alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackMapper.java
  27. 23 0
      alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackReplyMapper.java
  28. 51 0
      alien-entity/src/main/java/shop/alien/mapper/LifeImgMapper.java
  29. 24 0
      alien-entity/src/main/java/shop/alien/mapper/LifeLogMapper.java
  30. 28 0
      alien-entity/src/main/java/shop/alien/mapper/StoreCommentAppealMapper.java
  31. 207 0
      alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml
  32. 32 0
      alien-entity/src/main/resources/mapper/LifeFeedbackReplyMapper.xml
  33. 90 0
      alien-entity/src/main/resources/mapper/LifeImgMapper.xml
  34. 31 30
      alien-job/src/main/java/shop/alien/job/second/AiCheckXxlJob.java
  35. 6 2
      alien-job/src/main/java/shop/alien/job/second/AiUserViolationJob.java
  36. 7 7
      alien-job/src/main/java/shop/alien/job/store/BadReviewAppealJob.java
  37. 7 1
      alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsAuditServiceImpl.java
  38. 153 0
      alien-second/src/main/java/shop/alien/second/util/AiTaskUtils.java
  39. 4 3
      alien-second/src/main/java/shop/alien/second/util/AiUserViolationUtils.java
  40. 57 0
      alien-second/src/main/java/shop/alien/second/util/JsonUtils.java
  41. 351 0
      alien-store-platform/pom.xml
  42. 160 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java
  43. 59 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/feign/AlienAIFeign.java
  44. 434 0
      alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java
  45. 6 6
      alien-store/src/main/java/shop/alien/store/config/WebSocketProcess.java
  46. 189 0
      alien-store/src/main/java/shop/alien/store/controller/AiAuditController.java
  47. 63 0
      alien-store/src/main/java/shop/alien/store/controller/AiUploadController.java
  48. 3 3
      alien-store/src/main/java/shop/alien/store/controller/AliController.java
  49. 3 3
      alien-store/src/main/java/shop/alien/store/controller/LifeDiscountCouponStoreFriendController.java
  50. 117 0
      alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java
  51. 5 2
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  52. 35 0
      alien-store/src/main/java/shop/alien/store/feign/AlienAIFeign.java
  53. 1 1
      alien-store/src/main/java/shop/alien/store/service/LifeDiscountCouponStoreFriendService.java
  54. 20 0
      alien-store/src/main/java/shop/alien/store/service/LifeFeedbackReplyService.java
  55. 78 0
      alien-store/src/main/java/shop/alien/store/service/LifeFeedbackService.java
  56. 47 0
      alien-store/src/main/java/shop/alien/store/service/LifeImgService.java
  57. 15 0
      alien-store/src/main/java/shop/alien/store/service/LifeUserStoreService.java
  58. 7 1
      alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponStoreFriendServiceImpl.java
  59. 28 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackReplyServiceImpl.java
  60. 705 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java
  61. 56 0
      alien-store/src/main/java/shop/alien/store/service/impl/LifeImgServiceImpl.java
  62. 10 1
      alien-store/src/main/java/shop/alien/store/service/impl/StoreCommentAppealServiceImpl.java
  63. 302 25
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  64. 275 0
      alien-store/src/main/java/shop/alien/store/util/ai/AiFeedbackAssignUtils.java
  65. 6 0
      pom.xml

+ 74 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeFeedback.java

@@ -0,0 +1,74 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+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.io.Serializable;
+import java.util.Date;
+
+/**
+ * 意见反馈表
+ */
+@Data
+@JsonInclude
+@TableName("life_feedback")
+@ApiModel(value = "LifeFeedback对象", description = "意见反馈")
+public class LifeFeedback implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "用户ID")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    @TableField("feedback_source")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    @TableField("feedback_way")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    @TableField("feedback_type")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈内容")
+    @TableField("content")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式(手机号或邮箱)")
+    @TableField("contact_way")
+    private String contactWay;
+
+    @ApiModelProperty(value = "反馈时间")
+    @TableField("feedback_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决")
+    @TableField("handle_status")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "跟进人员ID(关联life_sys表的id)")
+    @TableField("staff_id")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "update_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+}
+

+ 49 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeFeedbackReply.java

@@ -0,0 +1,49 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+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.io.Serializable;
+import java.util.Date;
+
+/**
+ * 反馈回复表
+ */
+@Data
+@JsonInclude
+@TableName("life_feedback_reply")
+@ApiModel(value = "LifeFeedbackReply对象", description = "反馈回复")
+public class LifeFeedbackReply implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "回复ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "关联的反馈ID")
+    @TableField("feedback_id")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "回复类型:0-平台回复,1-用户回复")
+    @TableField("reply_type")
+    private Integer replyType;
+
+    @ApiModelProperty(value = "回复内容")
+    @TableField("reply_content")
+    private String replyContent;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "更新时间")
+    @TableField(value = "update_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+}
+

+ 66 - 0
alien-entity/src/main/java/shop/alien/entity/store/LifeImg.java

@@ -0,0 +1,66 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.*;
+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.io.Serializable;
+import java.util.Date;
+
+/**
+ * 反馈附件表(图片和视频)
+ */
+@Data
+@JsonInclude
+@TableName("life_img")
+@ApiModel(value = "LifeImg对象", description = "反馈附件(图片和视频)")
+public class LifeImg implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "反馈ID(关联life_feedback)")
+    @TableField("feedback_id")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "文件URL(图片或视频)")
+    @TableField("img_url")
+    private String imgUrl;
+
+    @ApiModelProperty(value = "缩略图URL(视频的封面图)")
+    @TableField("thumbnail_url")
+    private String thumbnailUrl;
+
+    @ApiModelProperty(value = "文件类型:1-图片,2-视频")
+    @TableField("file_type")
+    private Integer fileType;
+
+    @ApiModelProperty(value = "上传时间")
+    @TableField("upload_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date uploadTime;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "update_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}
+

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

@@ -3,24 +3,39 @@ package shop.alien.entity.store;
 import com.baomidou.mybatisplus.annotation.*;
 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.io.Serializable;
 import java.util.Date;
 
 /**
- * 日志
+ * 意见反馈日志
  */
 @Data
 @JsonInclude
 @TableName("life_log")
-public class LifeLog {
+@ApiModel(value = "LifeLog对象", description = "意见反馈日志")
+public class LifeLog implements Serializable {
+    private static final long serialVersionUID = 1L;
 
+    @ApiModelProperty(value = "主键ID")
     @TableId(value = "id", type = IdType.AUTO)
-    private String id;
+    private Integer id;
 
+    @ApiModelProperty(value = "意见反馈主表ID")
+    @TableField("feedback_id")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "日志内容")
+    @TableField("context")
     private String context;
 
+    @ApiModelProperty(value = "操作类型:0-创建反馈工单,1-分配跟踪人员,2-回复用户,3-问题解决状态")
+    @TableField("type")
+    private String type;
+
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic

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

@@ -43,7 +43,7 @@ public class StoreCommentAppeal extends Model<StoreCommentAppeal> {
     @TableField("img_id")
     private String imgId;
 
-    @ApiModelProperty(value = "申诉状态: 0:待处理, 1:已驳回, 2:已同意")
+    @ApiModelProperty(value = "申诉状态: 0:待处理, 1:已驳回, 2:已同意, 3:处理中")
     @TableField("appeal_status")
     private Integer appealStatus;
 

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

@@ -30,7 +30,7 @@ public class StoreImg extends Model<StoreImg> {
     @TableField("store_id")
     private Integer storeId;
 
-    @ApiModelProperty(value = "图片类型, 0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉,10:商家头像,11:店铺轮播图,12:联名卡图片,13:动态折扣, 14:营业执照,15:合同照片,17:打卡广场小人图片 18: 二手商品发布图片 19:二手商品与用户举报图片,20头图单图模式,21头图多图模式 , 22续签合同, 23 二手商品记录图片类型, 24 食品经营许可证审核前类型 25.食品经营许可证审核后类型 26.运营活动活动标题图 27.运营活动活动详情图 28.运动设施 29.洗浴设施及服务 30.酒水 31.娱乐经营许可证 32.娱乐经营许可证审核前")
+    @ApiModelProperty(value = "图片类型, 0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉,10:商家头像,11:店铺轮播图,12:联名卡图片,13:动态折扣, 14:营业执照,15:合同照片,17:打卡广场小人图片 18: 二手商品发布图片 19:二手商品与用户举报图片,20头图单图模式,21头图多图模式 , 22续签合同, 23 二手商品记录图片类型, 24 食品经营许可证审核前类型 25.食品经营许可证审核后类型 26.运营活动活动标题图 27.运营活动活动详情图 28.运动设施 29.洗浴设施及服务 30.酒水 31.娱乐经营许可证 32.娱乐经营许可证审核前, 33:身份证正面, 34:身份证反面")
     @TableField("img_type")
     private Integer imgType;
 

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

@@ -221,12 +221,12 @@ public class StoreInfo {
     @TableField("food_licence_reason")
     private String foodLicenceReason;
 
-    @ApiModelProperty(value = "经营许可证到期时间")
+    @ApiModelProperty(value = "食品经营许可证到期时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @TableField("food_licence_expiration_time")
     private Date  foodLicenceExpirationTime;
 
-    @ApiModelProperty(value = "变更经营许可证提交时间")
+    @ApiModelProperty(value = "变更食品经营许可证提交时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @TableField("update_food_licence_time")
     private Date  updateFoodLicenceTime;
@@ -265,4 +265,50 @@ public class StoreInfo {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @TableField("update_entertainment_licence_time")
     private Date updateEntertainmentLicenceTime;
+
+    @ApiModelProperty(value = "续签合同到期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("renew_contract_expiration_time")
+    private Date renewContractExpirationTime;
+
+    @ApiModelProperty(value = "营业执照状态 字典 foodLicenceStatus")
+    @TableField("business_license_status")
+    private Integer businessLicenseStatus;
+
+    @ApiModelProperty(value = "营业执照失败原因")
+    @TableField("business_license_reason")
+    private String businessLicenseReason;
+
+    @ApiModelProperty(value = "营业执照到期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("business_license_expiration_time")
+    private Date businessLicenseExpirationTime;
+
+    @ApiModelProperty(value = "变更营业执照提交时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("update_business_license_time")
+    private Date updateBusinessLicenseTime;
+
+    @ApiModelProperty(value = "身份证状态 字典 foodLicenceStatus")
+    @TableField("id_card_status")
+    private Integer idCardStatus;
+
+    @ApiModelProperty(value = "身份证审核失败原因")
+    @TableField("id_card_reason")
+    private String idCardReason;
+
+    @ApiModelProperty(value = "身份证到期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("id_card_expiration_time")
+    private Date idCardExpirationTime;
+
+    @ApiModelProperty(value = "变更身份证提交时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("update_id_card_time")
+    private Date updateIdCardTime;
+
+    @ApiModelProperty(value = "审核时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("review_date")
+    private Date reviewDate;
 }

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/FeedbackReplyDto.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 平台回复反馈DTO
+ */
+@Data
+@ApiModel(value = "FeedbackReplyDto对象", description = "平台回复反馈DTO")
+public class FeedbackReplyDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "原始反馈ID")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "平台工作人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "回复内容")
+    private String content;
+}
+

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackAssignDto.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台分配跟踪人员DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackAssignDto对象", description = "中台分配跟踪人员DTO")
+public class LifeFeedbackAssignDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID", required = true)
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "跟踪人员ID(关联life_sys表的id)", required = true)
+    private Integer staffId;
+
+    @ApiModelProperty(value = "操作人员ID")
+    private Integer operatorId;
+}
+

+ 39 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackDto.java

@@ -0,0 +1,39 @@
+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
+ */
+@Data
+@ApiModel(value = "LifeFeedbackDto对象", description = "意见反馈提交DTO")
+public class LifeFeedbackDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈内容")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式(手机号或邮箱)")
+    private String contactWay;
+
+    @ApiModelProperty(value = "文件URL列表(图片和视频,系统会自动识别类型。视频会自动匹配封面图)")
+    private List<String> fileUrlList;
+}
+

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

@@ -0,0 +1,35 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台意见反馈查询DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackQueryDto对象", description = "中台意见反馈查询DTO")
+public class LifeFeedbackQueryDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "页码", required = true)
+    private Integer page = 1;
+
+    @ApiModelProperty(value = "每页数量", required = true)
+    private Integer size = 10;
+}
+

+ 29 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackReplyWebDto.java

@@ -0,0 +1,29 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台回复用户DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackReplyWebDto对象", description = "中台回复用户DTO")
+public class LifeFeedbackReplyWebDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID", required = true)
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "回复内容", required = true)
+    private String content;
+
+    @ApiModelProperty(value = "操作人员ID")
+    private Integer operatorId;
+
+    @ApiModelProperty(value = "用户回复内容")
+    private String userReply;
+}
+

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackStatusDto.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台更新反馈处理状态DTO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackStatusDto对象", description = "中台更新反馈处理状态DTO")
+public class LifeFeedbackStatusDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID", required = true)
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决", required = true)
+    private Integer handleStatus;
+
+    // @ApiModelProperty(value = "操作人员ID")
+    // private Integer operatorId;
+}
+

+ 12 - 3
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreInfoDto.java

@@ -213,8 +213,17 @@ public class StoreInfoDto {
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private Date entertainmentLicenceExpirationTime;
 
-//    @ApiModelProperty(value = "分类id(词典表 键为 business_classify)(多个ID用逗号拼接)")
-//    @JsonDeserialize(using = StringToListDeserializer.class)
-//    private List<String> businessClassify;
+    @ApiModelProperty(value = "营业执照图片URL")
+    private String businessLicenseUrl;
 
+    @ApiModelProperty(value = "营业执照状态 字典 foodLicenceStatus")
+    private Integer businessLicenseStatus;
+
+    @ApiModelProperty(value = "营业执照失败原因")
+    private String businessLicenseReason;
+
+    @ApiModelProperty(value = "营业执照到期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private Date businessLicenseExpirationTime;
 }

+ 33 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/UserReplyDto.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
+ */
+@Data
+@ApiModel(value = "UserReplyDto对象", description = "用户回复DTO")
+public class UserReplyDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    private Integer userId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端", required = true)
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "原始反馈ID(反馈详情页的反馈ID)", required = true)
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "回复内容", required = true)
+    private String content;
+
+    @ApiModelProperty(value = "文件URL列表(图片和视频,系统会自动识别类型。视频会自动匹配封面图)")
+    private List<String> fileUrlList;
+}
+

+ 29 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackAttachmentVo.java

@@ -0,0 +1,29 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 反馈附件VO
+ */
+@Data
+@ApiModel(value = "FeedbackAttachmentVo对象", description = "反馈附件VO")
+public class FeedbackAttachmentVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "附件ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "文件类型:1-图片,2-视频")
+    private Integer fileType;
+
+    @ApiModelProperty(value = "文件URL")
+    private String fileUrl;
+
+    @ApiModelProperty(value = "缩略图URL(视频的封面图)")
+    private String thumbnailUrl;
+}
+

+ 41 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackLogVo.java

@@ -0,0 +1,41 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 反馈操作日志VO
+ */
+@Data
+@ApiModel(value = "FeedbackLogVo对象", description = "反馈操作日志VO")
+public class FeedbackLogVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "日志ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "操作类型:0-问题解决状态,1-分配跟踪人员,3-回复用户")
+    private Integer type;
+
+    @ApiModelProperty(value = "操作类型名称")
+    private String typeName;
+
+    @ApiModelProperty(value = "日志内容")
+    private String context;
+
+    @ApiModelProperty(value = "子内容(用于回复用户时显示用户回复)")
+    private String subContext;
+
+    @ApiModelProperty(value = "操作时间")
+    @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "操作人姓名")
+    private String operatorName;
+}
+

+ 45 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackReplyVo.java

@@ -0,0 +1,45 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 反馈回复VO
+ */
+@Data
+@ApiModel(value = "FeedbackReplyVo对象", description = "反馈回复VO")
+public class FeedbackReplyVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "回复ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "反馈ID")
+    private Integer feedbackId;
+
+    @ApiModelProperty(value = "回复类型:0-平台回复,1-用户回复")
+    private Integer replyType;
+
+    @ApiModelProperty(value = "回复类型名称")
+    private String replyTypeName;
+
+    @ApiModelProperty(value = "回复内容")
+    private String replyContent;
+
+    @ApiModelProperty(value = "创建时间")
+    @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "图片列表")
+    private List<String> imgUrlList;
+
+    @ApiModelProperty(value = "视频列表")
+    private List<String> videoUrlList;
+}
+

+ 23 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/FeedbackTypeVo.java

@@ -0,0 +1,23 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 反馈类型VO
+ */
+@Data
+@ApiModel(value = "FeedbackTypeVo对象", description = "反馈类型VO")
+public class FeedbackTypeVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈类型值:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer value;
+
+    @ApiModelProperty(value = "反馈类型名称")
+    private String label;
+}
+

+ 78 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackDetailVo.java

@@ -0,0 +1,78 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 中台意见反馈详情VO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackDetailVo对象", description = "中台意见反馈详情VO")
+public class LifeFeedbackDetailVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "账号(手机号)")
+    private String phone;
+
+    @ApiModelProperty(value = "跟踪人员姓名+联系方式")
+    private String staffInfo;
+
+    @ApiModelProperty(value = "跟踪人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈来源名称")
+    private String feedbackSourceName;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈方式名称")
+    private String feedbackWayName;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈类型名称")
+    private String feedbackTypeName;
+
+    @ApiModelProperty(value = "反馈时间")
+    @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "问题描述")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式")
+    private String contactWay;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "处理状态名称")
+    private String handleStatusName;
+
+    @ApiModelProperty(value = "附件列表")
+    private List<FeedbackAttachmentVo> attachments;
+
+    @ApiModelProperty(value = "操作日志列表")
+    private List<FeedbackLogVo> logs;
+
+    @ApiModelProperty(value = "回复列表(平台回复和用户回复)")
+    private List<FeedbackReplyVo> replies;
+}
+

+ 62 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackListVo.java

@@ -0,0 +1,62 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 中台意见反馈列表VO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackListVo对象", description = "中台意见反馈列表VO")
+public class LifeFeedbackListVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "账号(手机号)")
+    private String phone;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈类型名称")
+    private String feedbackTypeName;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈方式名称")
+    private String feedbackWayName;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈来源名称")
+    private String feedbackSourceName;
+
+    @ApiModelProperty(value = "反馈时间")
+    @JsonFormat(pattern = "yyyy/MM/dd HH:mm", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "跟踪人员姓名+部门")
+    private String staffInfo;
+
+    @ApiModelProperty(value = "跟踪人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "处理状态名称")
+    private String handleStatusName;
+}
+

+ 66 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeFeedbackVo.java

@@ -0,0 +1,66 @@
+package shop.alien.entity.store.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 意见反馈展示VO
+ */
+@Data
+@ApiModel(value = "LifeFeedbackVo对象", description = "意见反馈展示VO")
+public class LifeFeedbackVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "反馈ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "反馈来源:0-用户端,1-商家端")
+    private Integer feedbackSource;
+
+    @ApiModelProperty(value = "反馈方式:0-用户反馈,1-AI识别")
+    private Integer feedbackWay;
+
+    @ApiModelProperty(value = "反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈")
+    private Integer feedbackType;
+
+    @ApiModelProperty(value = "反馈类型名称")
+    private String feedbackTypeName;
+
+    @ApiModelProperty(value = "反馈内容")
+    private String content;
+
+    @ApiModelProperty(value = "联系方式")
+    private String contactWay;
+
+    @ApiModelProperty(value = "反馈时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date feedbackTime;
+
+    @ApiModelProperty(value = "处理状态:0-处理中,1-已解决")
+    private Integer handleStatus;
+
+    @ApiModelProperty(value = "跟进工作人员ID")
+    private Integer staffId;
+
+    @ApiModelProperty(value = "跟进工作人员姓名")
+    private String staffName;
+
+    @ApiModelProperty(value = "附件图片列表")
+    private List<String> imgUrlList;
+
+    @ApiModelProperty(value = "附件视频列表")
+    private List<String> videoUrlList;
+
+    @ApiModelProperty(value = "平台反馈建议列表")
+    private List<LifeFeedbackVo> platformReplies;
+}
+

+ 26 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeStaffListVo.java

@@ -0,0 +1,26 @@
+package shop.alien.entity.store.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中台跟踪人员列表VO
+ */
+@Data
+@ApiModel(value = "LifeStaffListVo对象", description = "中台跟踪人员列表VO")
+public class LifeStaffListVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "人员ID")
+    private Integer id;
+
+    @ApiModelProperty(value = "人员信息(姓名)")
+    private String staffInfo;
+
+    @ApiModelProperty(value = "姓名")
+    private String realName;
+}
+

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

@@ -67,7 +67,7 @@ public class StoreOperationalActivity {
     @TableField("coupon_quantity")
     private Integer couponQuantity;
 
-    @ApiModelProperty(value = "状态:1-待审核, 2-未开始, 3-审核拒绝, 4-已售罄, 5-进行中, 6-已下架, 7-已结束")
+    @ApiModelProperty(value = "状态:1-待审核, 2-未开始, 3-审核拒绝, 4-已售罄, 5-进行中, 6-已下架, 7-已结束, 8-审核成功")
     @TableField("status")
     private Integer status;
 

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

@@ -2,6 +2,7 @@ package shop.alien.entity.storePlatform.vo;
 
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.JsonNode;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -73,5 +74,13 @@ public class StoreOperationalActivityDTO {
     @ApiModelProperty(value = "页数")
     private Integer pageSize;
 
+    @ApiModelProperty(value = "上传图片的方式: 1-用户本地上传,2-使用用户输入的描述AI生成图片")
+    private Integer uploadImgType;
+
+    @ApiModelProperty(value = "用户输入的AI描述")
+    private JsonNode imgDescribe;
+
+    @ApiModelProperty(value = "AI审核的输入参数")
+    private JsonNode auditParam;
 }
 

+ 80 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeFeedbackMapper.java

@@ -0,0 +1,80 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.LifeFeedback;
+import shop.alien.entity.store.vo.LifeFeedbackDetailVo;
+import shop.alien.entity.store.vo.LifeFeedbackListVo;
+import shop.alien.entity.store.vo.LifeFeedbackVo;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 意见反馈 Mapper 接口
+ */
+@Mapper
+public interface LifeFeedbackMapper extends BaseMapper<LifeFeedback> {
+
+    /**
+     * 查询用户反馈列表(带工作人员名称)
+     * @param page 分页对象
+     * @param userId 用户ID
+     * @param feedbackSource 反馈来源
+     * @param feedbackWay 反馈方式
+     * @param handleStatus 处理状态
+     * @return 反馈列表
+     */
+    IPage<LifeFeedbackVo> selectFeedbackListWithStaff(
+            Page<LifeFeedbackVo> page,
+            @Param("userId") Integer userId,
+            @Param("feedbackSource") Integer feedbackSource,
+            @Param("feedbackWay") Integer feedbackWay,
+            @Param("handleStatus") Integer handleStatus
+    );
+
+    /**
+     * 查询反馈详情(带工作人员名称)
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    LifeFeedbackVo selectFeedbackDetail(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 统计待处理反馈数量
+     * @param feedbackSource 反馈来源
+     * @return 待处理数量
+     */
+    Integer countPendingFeedback(@Param("feedbackSource") Integer feedbackSource);
+
+
+    // ==================== Web中台接口 ====================
+
+    /**
+     * 中台-查询意见反馈列表
+     * @param page 分页对象
+     * @param feedbackType 反馈类型
+     * @param handleStatus 处理状态
+     * @param feedbackSource 反馈来源
+     * @param feedbackWay 反馈方式
+     * @return 反馈列表
+     */
+    IPage<LifeFeedbackListVo> selectWebFeedbackList(
+            Page<LifeFeedbackListVo> page,
+            @Param("feedbackType") Integer feedbackType,
+            @Param("handleStatus") Integer handleStatus,
+            @Param("feedbackSource") Integer feedbackSource,
+            @Param("feedbackWay") Integer feedbackWay
+    );
+
+    /**
+     * 中台-查询反馈详情
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    LifeFeedbackDetailVo selectWebFeedbackDetail(@Param("feedbackId") Integer feedbackId);
+}
+

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

@@ -0,0 +1,23 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.LifeFeedbackReply;
+
+import java.util.List;
+
+/**
+ * 反馈回复 Mapper 接口
+ */
+@Mapper
+public interface LifeFeedbackReplyMapper extends BaseMapper<LifeFeedbackReply> {
+
+    /**
+     * 根据反馈ID查询回复列表
+     * @param feedbackId 反馈ID
+     * @return 回复列表
+     */
+    List<LifeFeedbackReply> selectByFeedbackId(@Param("feedbackId") Integer feedbackId);
+}
+

+ 51 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeImgMapper.java

@@ -0,0 +1,51 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import shop.alien.entity.store.LifeImg;
+
+import java.util.List;
+
+/**
+ * 反馈图片 Mapper 接口
+ */
+@Mapper
+public interface LifeImgMapper extends BaseMapper<LifeImg> {
+
+    /**
+     * 根据反馈ID查询图片列表
+     * @param feedbackId 反馈ID
+     * @return 图片列表
+     */
+    List<LifeImg> selectByFeedbackId(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 批量插入图片
+     * @param list 图片列表
+     * @return 插入数量
+     */
+    Integer batchInsert(@Param("list") List<LifeImg> list);
+
+    /**
+     * 根据反馈ID删除图片(逻辑删除)
+     * @param feedbackId 反馈ID
+     * @return 删除数量
+     */
+    Integer deleteByFeedbackId(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 查询反馈的图片URL列表
+     * @param feedbackId 反馈ID
+     * @return 图片URL列表
+     */
+    List<String> selectImgUrlsByFeedbackId(@Param("feedbackId") Integer feedbackId);
+
+    /**
+     * 查询反馈的视频URL列表
+     * @param feedbackId 反馈ID
+     * @return 视频URL列表
+     */
+    List<String> selectVideoUrlsByFeedbackId(@Param("feedbackId") Integer feedbackId);
+}
+

+ 24 - 0
alien-entity/src/main/java/shop/alien/mapper/LifeLogMapper.java

@@ -2,11 +2,35 @@ package shop.alien.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 import shop.alien.entity.store.LifeLog;
+import shop.alien.entity.store.vo.FeedbackLogVo;
+
+import java.util.List;
 
 /**
  * 日志
  */
 @Mapper
 public interface LifeLogMapper extends BaseMapper<LifeLog> {
+
+    /**
+     * 根据反馈ID查询操作日志列表
+     * @param feedbackId 反馈ID
+     * @return 日志列表
+     */
+    @Select("SELECT l.id, l.type, l.context, l.created_time AS createdTime, " +
+            "CASE l.type " +
+            "   WHEN 0 THEN '问题解决状态' " +
+            "   WHEN 1 THEN '分配跟踪人员' " +
+            "   WHEN 2 THEN '创建反馈工单' " +
+            "   WHEN 3 THEN '回复用户' " +
+            "END AS typeName, " +
+            "s.user_name AS operatorName " +
+            "FROM life_log l " +
+            "LEFT JOIN life_sys s ON l.created_user_id = s.id " +
+            "WHERE l.feedback_id = #{feedbackId} AND l.delete_flag = 0 " +
+            "ORDER BY l.created_time DESC")
+    List<FeedbackLogVo> selectLogsByFeedbackId(@Param("feedbackId") Integer feedbackId);
 }

+ 28 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreCommentAppealMapper.java

@@ -6,10 +6,12 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.Constants;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
 import shop.alien.entity.store.StoreCommentAppeal;
 import shop.alien.entity.store.vo.StoreCommentAppealVo;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 评论申诉表 Mapper 接口
@@ -63,4 +65,30 @@ public interface StoreCommentAppealMapper extends BaseMapper<StoreCommentAppeal>
             "left join store_user e on a.store_id = e.store_id and e.delete_flag = 0  ${ew.customSqlSegment}")
     StoreCommentAppealVo getCommentDetail(@Param(Constants.WRAPPER) QueryWrapper<StoreCommentAppealVo> queryWrapper);
 
+
+    /**
+     * 申诉列表
+     *
+     * @return List<Map < String, Object>>
+     */
+    @Select("SELECT sca.id, sca.appeal_reason, sca.appeal_status, sc.comment_content, si.img_url, sci.user_img_url, sc.id comment_id, sc.business_id " +
+            "FROM store_comment_appeal sca " +
+            "LEFT JOIN store_comment sc ON sca.comment_id = sc.id " +
+            "LEFT JOIN store_img si ON sca.img_id = si.id " +
+            "LEFT JOIN (SELECT sc.id id, si.img_url user_img_url FROM store_comment sc LEFT JOIN store_img si ON sc.img_id = si.id AND sc.delete_flag = 0) sci ON sci.id = sca.comment_id " +
+            "WHERE sca.appeal_status = 0 and record_id is null AND sca.delete_flag = 0")
+    List<Map<String, Object>> getAppealList();
+
+
+    @Select("SELECT sca.id, sca.appeal_status, sca.final_result, sca.comment_id, sca.record_id " +
+            "FROM store_comment_appeal sca " +
+            "WHERE sca.appeal_status = 3")
+    List<StoreCommentAppeal> getPendingAppeals();
+
+    @Update("UPDATE store_comment_appeal " +
+            "SET appeal_status = #{appealStatus}, final_result = #{finalResult} " +
+            "WHERE record_id = #{recordId}")
+    void updateByRecordId(@Param("recordId") Integer recordId,
+                          @Param("appealStatus") Integer appealStatus,
+                          @Param("finalResult") String finalResult);
 }

+ 207 - 0
alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml

@@ -0,0 +1,207 @@
+<?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.LifeFeedbackMapper">
+
+    <!-- 通用结果映射 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeFeedback">
+        <id column="id" property="id" />
+        <result column="user_id" property="userId" />
+        <result column="feedback_source" property="feedbackSource" />
+        <result column="feedback_way" property="feedbackWay" />
+        <result column="feedback_type" property="feedbackType" />
+        <result column="content" property="content" />
+        <result column="contact_way" property="contactWay" />
+        <result column="feedback_time" property="feedbackTime" />
+        <result column="staff_id" property="staffId" />
+        <result column="handle_status" property="handleStatus" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+    <!-- 基础字段 -->
+    <sql id="Base_Column_List">
+        id, user_id, feedback_source, feedback_way, feedback_type, 
+        content, contact_way, feedback_time, staff_id, handle_status,
+        create_time, update_time
+    </sql>
+
+    <!-- 查询用户反馈列表(带工作人员名称) -->
+    <!-- 只查询原始反馈,排除用户回复(用户回复的feedback_time晚于原始反馈) -->
+    <select id="selectFeedbackListWithStaff" resultType="shop.alien.entity.store.vo.LifeFeedbackVo">
+        SELECT 
+            f.id,
+            f.user_id AS userId,
+            f.feedback_source AS feedbackSource,
+            f.feedback_way AS feedbackWay,
+            f.feedback_type AS feedbackType,
+            f.content,
+            f.contact_way AS contactWay,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            f.handle_status AS handleStatus,
+            s.user_name AS staffName
+        FROM life_feedback f
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE 1=1
+        <if test="userId != null">
+            AND f.user_id = #{userId}
+        </if>
+        <if test="feedbackSource != null">
+            AND f.feedback_source = #{feedbackSource}
+        </if>
+        <if test="feedbackWay != null">
+            AND f.feedback_way = #{feedbackWay}
+        </if>
+        <if test="handleStatus != null">
+            AND f.handle_status = #{handleStatus}
+        </if>
+        <!-- 只查询原始反馈:feedback_way=0(用户反馈)或feedback_way=1(AI识别) -->
+        <!-- 回复记录已存储在life_feedback_reply表中,life_feedback表中只包含原始反馈 -->
+        AND (f.feedback_way = 0 OR f.feedback_way = 1)
+        ORDER BY f.feedback_time DESC
+    </select>
+
+    <!-- 查询反馈详情(带工作人员名称) -->
+    <select id="selectFeedbackDetail" resultType="shop.alien.entity.store.vo.LifeFeedbackVo">
+        SELECT 
+            f.id,
+            f.user_id AS userId,
+            f.feedback_source AS feedbackSource,
+            f.feedback_way AS feedbackWay,
+            f.feedback_type AS feedbackType,
+            f.content,
+            f.contact_way AS contactWay,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            f.handle_status AS handleStatus,
+            s.user_name AS staffName
+        FROM life_feedback f
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE f.id = #{feedbackId}
+    </select>
+
+    <!-- 统计处理中反馈数量 -->
+    <select id="countPendingFeedback" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM life_feedback
+        WHERE handle_status = 0
+        <if test="feedbackSource != null">
+            AND feedback_source = #{feedbackSource}
+        </if>
+    </select>
+
+    <!-- 查询平台回复列表 -->
+    <select id="selectPlatformReplies" resultType="shop.alien.entity.store.vo.LifeFeedbackVo">
+        SELECT
+            f.id,
+            f.user_id AS userId,
+            f.feedback_source AS feedbackSource,
+            f.feedback_way AS feedbackWay,
+            f.feedback_type AS feedbackType,
+            f.content,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            f.handle_status AS handleStatus,
+            s.user_name AS staffName
+        FROM life_feedback f
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE f.user_id = #{userId}
+        AND f.feedback_source = #{feedbackSource}
+        AND f.feedback_way = 1
+        AND f.feedback_time >= #{startTime}
+        ORDER BY f.feedback_time ASC
+    </select>
+
+    <!-- ==================== Web中台接口 ==================== -->
+
+    <!-- 中台-查询意见反馈列表 -->
+    <select id="selectWebFeedbackList" resultType="shop.alien.entity.store.vo.LifeFeedbackListVo">
+        SELECT
+            f.id,
+            u.nick_name AS nickName,
+            u.phone AS phone,
+            f.feedback_type AS feedbackType,
+            CASE f.feedback_type
+                WHEN 0 THEN 'bug反馈'
+                WHEN 1 THEN '优化反馈'
+                WHEN 2 THEN '新增功能反馈'
+            END AS feedbackTypeName,
+            f.feedback_way AS feedbackWay,
+            CASE f.feedback_way
+                WHEN 0 THEN '用户反馈'
+                WHEN 1 THEN 'AI识别'
+            END AS feedbackWayName,
+            f.feedback_source AS feedbackSource,
+            CASE f.feedback_source
+                WHEN 0 THEN '用户端'
+                WHEN 1 THEN '商家端'
+            END AS feedbackSourceName,
+            f.feedback_time AS feedbackTime,
+            f.staff_id AS staffId,
+            CONCAT(IFNULL(s.user_name, '')) AS staffInfo,
+            f.handle_status AS handleStatus,
+            CASE f.handle_status
+                WHEN 0 THEN '处理中'
+                WHEN 1 THEN '已解决'
+            END AS handleStatusName
+        FROM life_feedback f
+        LEFT JOIN store_user u ON f.user_id = u.id
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE 1=1
+        <if test="feedbackType != null">
+            AND f.feedback_type = #{feedbackType}
+        </if>
+        <if test="handleStatus != null">
+            AND f.handle_status = #{handleStatus}
+        </if>
+        <if test="feedbackSource != null">
+            AND f.feedback_source = #{feedbackSource}
+        </if>
+        <if test="feedbackWay != null">
+            AND f.feedback_way = #{feedbackWay}
+        </if>
+        ORDER BY f.feedback_time DESC
+    </select>
+
+    <!-- 中台-查询反馈详情 -->
+    <select id="selectWebFeedbackDetail" resultType="shop.alien.entity.store.vo.LifeFeedbackDetailVo">
+        SELECT
+            f.id,
+            u.nick_name AS nickName,
+            u.phone AS phone,
+            f.staff_id AS staffId,
+            CONCAT(IFNULL(s.user_name, '')) AS staffInfo,
+            f.feedback_source AS feedbackSource,
+            CASE f.feedback_source
+                WHEN 0 THEN '用户端'
+                WHEN 1 THEN '商家端'
+            END AS feedbackSourceName,
+            f.feedback_way AS feedbackWay,
+            CASE f.feedback_way
+                WHEN 0 THEN '用户反馈'
+                WHEN 1 THEN 'AI识别'
+            END AS feedbackWayName,
+            f.feedback_type AS feedbackType,
+            CASE f.feedback_type
+                WHEN 0 THEN 'bug反馈'
+                WHEN 1 THEN '优化反馈'
+                WHEN 2 THEN '新增功能反馈'
+            END AS feedbackTypeName,
+            f.feedback_time AS feedbackTime,
+            f.content,
+            f.contact_way AS contactWay,
+            f.handle_status AS handleStatus,
+            CASE f.handle_status
+                WHEN 0 THEN '处理中'
+                WHEN 1 THEN '已解决'
+            END AS handleStatusName
+        FROM life_feedback f
+        LEFT JOIN store_user u ON f.user_id = u.id
+        LEFT JOIN life_sys s ON f.staff_id = s.id
+        WHERE f.id = #{feedbackId}
+    </select>
+
+</mapper>
+

+ 32 - 0
alien-entity/src/main/resources/mapper/LifeFeedbackReplyMapper.xml

@@ -0,0 +1,32 @@
+<?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.LifeFeedbackReplyMapper">
+
+    <!-- 通用结果映射 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeFeedbackReply">
+        <id column="id" property="id" />
+        <result column="feedback_id" property="feedbackId" />
+        <result column="reply_type" property="replyType" />
+        <result column="reply_content" property="replyContent" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+    <!-- 基础字段 -->
+    <sql id="Base_Column_List">
+        id, feedback_id, reply_type, reply_content, create_time, update_time
+    </sql>
+
+    <!-- 根据反馈ID查询回复列表 -->
+    <select id="selectByFeedbackId" resultMap="BaseResultMap">
+        SELECT 
+            <include refid="Base_Column_List" />
+        FROM life_feedback_reply
+        WHERE feedback_id = #{feedbackId}
+        ORDER BY create_time ASC
+    </select>
+
+</mapper>
+

+ 90 - 0
alien-entity/src/main/resources/mapper/LifeImgMapper.xml

@@ -0,0 +1,90 @@
+<?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.LifeImgMapper">
+
+    <!-- 通用结果映射 -->
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.LifeImg">
+        <id column="id" property="id" />
+        <result column="feedback_id" property="feedbackId" />
+        <result column="img_url" property="imgUrl" />
+        <result column="thumbnail_url" property="thumbnailUrl" />
+        <result column="file_type" property="fileType" />
+        <result column="upload_time" property="uploadTime" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+        <result column="created_user_id" property="createdUserId" />
+        <result column="updated_user_id" property="updatedUserId" />
+    </resultMap>
+
+    <!-- 基础字段 -->
+    <sql id="Base_Column_List">
+        id, feedback_id, img_url, thumbnail_url, file_type, upload_time,
+        create_time, update_time, created_user_id, updated_user_id
+    </sql>
+
+    <!-- 根据反馈ID查询图片列表 -->
+    <select id="selectByFeedbackId" resultMap="BaseResultMap">
+        SELECT 
+            <include refid="Base_Column_List" />
+        FROM life_img
+        WHERE feedback_id = #{feedbackId}
+    </select>
+
+    <!-- 批量插入附件(图片和视频) -->
+    <insert id="batchInsert" parameterType="java.util.List">
+        INSERT INTO life_img (
+            feedback_id, img_url, thumbnail_url, file_type, upload_time,
+            create_time, update_time, created_user_id, updated_user_id
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+                #{item.feedbackId},
+                #{item.imgUrl},
+                #{item.thumbnailUrl},
+                #{item.fileType},
+                #{item.uploadTime},
+                NOW(),
+                NOW(),
+                #{item.createdUserId},
+                #{item.updatedUserId}
+            )
+        </foreach>
+    </insert>
+
+    <!-- 根据反馈ID删除图片 -->
+    <delete id="deleteByFeedbackId">
+        DELETE FROM life_img
+        WHERE feedback_id = #{feedbackId}
+    </delete>
+
+    <!-- 查询反馈的图片URL列表 -->
+    <select id="selectImgUrlsByFeedbackId" resultType="java.lang.String">
+        SELECT img_url
+        FROM life_img
+        WHERE feedback_id = #{feedbackId}
+        AND file_type = 1
+        ORDER BY upload_time ASC
+    </select>
+
+    <!-- 查询反馈的视频URL列表 -->
+    <select id="selectVideoUrlsByFeedbackId" resultType="java.lang.String">
+        SELECT img_url
+        FROM life_img
+        WHERE feedback_id = #{feedbackId}
+        AND file_type = 2
+        ORDER BY upload_time ASC
+    </select>
+
+    <!-- 查询反馈的视频信息(包含视频URL和缩略图URL) -->
+    <select id="selectVideoInfoByFeedbackId" resultMap="BaseResultMap">
+        SELECT
+            <include refid="Base_Column_List" />
+        FROM life_img
+        WHERE feedback_id = #{feedbackId}
+        AND file_type = 2
+        ORDER BY upload_time ASC
+    </select>
+
+</mapper>

+ 31 - 30
alien-job/src/main/java/shop/alien/job/second/AiCheckXxlJob.java

@@ -30,10 +30,7 @@ import shop.alien.mapper.second.SecondRiskControlRecordMapper;
 import shop.alien.util.common.Constants;
 
 import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -267,6 +264,12 @@ public class AiCheckXxlJob {
                     // 初始化请求体Map
                     Map<String, Object> requestBody = new HashMap<>();
 
+
+
+                    if (violation.getAiTaskId() == null || violation.getAiTaskId() == "") {
+                        continue;
+                    }
+
                     // 设置投诉记录ID
                     requestBody.put("task_id", violation.getAiTaskId());
                     HttpHeaders aiHeaders = new HttpHeaders();
@@ -286,33 +289,31 @@ public class AiCheckXxlJob {
                             if (taskObject.getInteger("code") == 200) {
                                 com.alibaba.fastjson.JSONObject data = taskObject.getJSONObject("data");
                                 if (data != null) {
-                                    boolean isValid = data.getBoolean("is_valid");
-                                    String processingStatus;
-                                    if (isValid) {
-                                         processingStatus = "1";
-                                    } else {
-                                        processingStatus = "2";
+                                    String status = data.getString("status");
+                                    if (!"processing".equals(status)) {
+                                        boolean isValid = data.getBoolean("is_valid");
+                                        violation.setReportResult(data.getString("matched_type"));
+                                        violation.setProcessingStatus(isValid ? "1" : "2");
+                                        violation.setProcessingTime(new Date());
+                                        lifeUserViolationMapper.updateById(violation);
+
+                                        // 发送通知
+                                        LifeNotice lifeMessage = new LifeNotice();
+                                        LifeUser lifeUser = lifeUserMapper.selectById(violation.getReportingUserId());
+                                        lifeMessage.setReceiverId("user_" + lifeUser.getUserPhone());
+                                        String text = "您的举报商品结果为" + (isValid ? "违规" : "未违规");
+                                        com.alibaba.fastjson.JSONObject lifeMessagejson = new com.alibaba.fastjson.JSONObject();
+                                        lifeMessagejson.put("title", "平台已受理");
+                                        lifeMessagejson.put("message", text);
+                                        lifeMessage.setContext(lifeMessagejson.toJSONString());
+
+                                        lifeMessage.setTitle("举报通知");
+                                        lifeMessage.setBusinessId(violation.getId());
+                                        lifeMessage.setSenderId("system");
+                                        lifeMessage.setIsRead(0);
+                                        lifeMessage.setNoticeType(1);
+                                        lifeNoticeMapper.insert(lifeMessage);
                                     }
-                                    violation.setReportResult(data.getString("matched_type"));
-                                    violation.setProcessingStatus(processingStatus);
-                                    lifeUserViolationMapper.updateById(violation);
-
-                                    // 发送通知
-                                    LifeNotice lifeMessage = new LifeNotice();
-                                    LifeUser lifeUser = lifeUserMapper.selectById(violation.getReportingUserId());
-                                    lifeMessage.setReceiverId("user_" + lifeUser.getUserPhone());
-                                    String text = "您的举报用户结果为" + (isValid ? "违规" : "未违规");
-                                    com.alibaba.fastjson.JSONObject lifeMessagejson = new com.alibaba.fastjson.JSONObject();
-                                    lifeMessagejson.put("title", "平台已受理");
-                                    lifeMessagejson.put("message", text);
-                                    lifeMessage.setContext(lifeMessagejson.toJSONString());
-
-                                    lifeMessage.setTitle("举报通知");
-                                    lifeMessage.setBusinessId(violation.getId());
-                                    lifeMessage.setSenderId("system");
-                                    lifeMessage.setIsRead(0);
-                                    lifeMessage.setNoticeType(1);
-                                    lifeNoticeMapper.insert(lifeMessage);
                                 }
                             }
                         }

+ 6 - 2
alien-job/src/main/java/shop/alien/job/second/AiUserViolationJob.java

@@ -24,6 +24,7 @@ import shop.alien.mapper.LifeUserMapper;
 import shop.alien.mapper.LifeUserViolationMapper;
 
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 
 @Slf4j
@@ -44,9 +45,11 @@ public class AiUserViolationJob {
     @Value("${third-party-pass-word.base-url}")
     private String passWord;
 
-    private String loginUrl = "http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login";
+    @Value("${third-party-login.base-url:http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login}")
+    private String loginUrl;
 
-    private String aiUserViolationCheckUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/user_complaint_record/result";
+    @Value("${third-party-aiUserViolationCheckUrl.base-url:http://192.168.2.250:9000/ai/auto-review/api/v1/user_complaint_record/result}")
+    private String aiUserViolationCheckUrl;
 
     @XxlJob("getAiUserViolationResult")
     public R<String> getAiUserViolationResult() {
@@ -149,6 +152,7 @@ public class AiUserViolationJob {
                             aiTask.setProcessingStatus("2");
                             aiTask.setReportResult(dataJsonObj.getString("decision_reason"));
                         }
+                        aiTask.setProcessingTime(new Date());
                     }
 
                     // 发送通知

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

@@ -56,20 +56,20 @@ public class BadReviewAppealJob {
 //    @Value("${third-party-login.base-url}")
 //    private String loginUrl;
 
-    private String loginUrl = "http://192.168.2.78:9000/ai/user-auth-core/api/v1/auth/login";
-
-
     @Value("${third-party-user-name.base-url}")
     private String userName;
 
     @Value("${third-party-pass-word.base-url}")
     private String passWord;
 
-    private String analyzeUrl = "http://192.168.2.78:9000/ai/auto-review/api/v1/analyze";
-
-    private String resultUrl = "http://192.168.2.78:9000/ai/auto-review";
+    @Value("${third-party-login.base-url}")
+    private String loginUrl;
 
+    @Value("${third-party-analyzeUrl.base-url}")
+    private String analyzeUrl;
 
+    @Value("${third-party-resultUrl.base-url}")
+    private String resultUrl;
 
     /**
      * 差评申述置信度分析接口地址
@@ -299,7 +299,7 @@ public class BadReviewAppealJob {
                 //修改评论状态为"处理中"
                 StoreCommentAppeal sCommentAppeal = new StoreCommentAppeal();
                 sCommentAppeal.setId((Integer) storeCommentAppeal.get("id"));
-                sCommentAppeal.setAppealStatus(0);
+                sCommentAppeal.setAppealStatus(3);
                 sCommentAppeal.setRecordId(recordId);
                 storeCommentAppealMapper.updateById(sCommentAppeal);
 

+ 7 - 1
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsAuditServiceImpl.java

@@ -66,6 +66,12 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
     private boolean videoModerationBlockOnFailure;
 
     /**
+     * AI审核结果查询接口地址
+     */
+    @Value("${audit.result-url:http://192.168.2.250:9100/ai/auto-review/api/v1/audit_task/getResult}")
+    private String aiAuditResultUrl;
+
+    /**
      * 视频审核服务,用于处理商品中视频内容的审核
      */
     private final VideoModerationService videoModerationService;
@@ -638,7 +644,7 @@ public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
      * @return success-审核通过, reject-审核拒绝, pending-处理中, fail-处理失败
      */
     private String processAiTaskResult(SecondAiTask task, String accessToken) {
-        String resultUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/audit_task/getResult?task_id=" + task.getTaskId();
+        String resultUrl = aiAuditResultUrl + "?task_id=" + task.getTaskId();
         try {
             org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
             headers.set("Authorization", "Bearer " + accessToken);

+ 153 - 0
alien-second/src/main/java/shop/alien/second/util/AiTaskUtils.java

@@ -0,0 +1,153 @@
+package shop.alien.second.util;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.map.HashedMap;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreCommentAppeal;
+
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+@RefreshScope
+public class AiTaskUtils {
+
+    private final RestTemplate restTemplate;
+
+//    @Value("${third-party-login.base-url:http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login}")
+//    private String loginUrl = "http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login";
+
+    @Value("${third-party-user-name.base-url:UdUser}")
+    private String userName;
+
+    @Value("${third-party-pass-word.base-url:123456}")
+    private String passWord;
+
+
+    @Value("${third-party-login.base-url:http://192.168.2.250:9100/ai/user-auth-core/api/v1/auth/login}")
+    private String loginUrl;
+
+    @Value("${audit.auditTaskUrl:http://192.168.2.250:9100/ai/auto-review/api/v1/audit_task/product}")
+    private String auditTaskUrl;
+//    private String auditTaskUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/audit_task/product";
+
+
+    private String auditTaskResultUrl = "http://192.168.2.250:9000/ai/task-core/api/v1/audit_task/getResult";
+
+    /**
+     * 登录 AI 服务,获取 token
+     *
+     * @return accessToken
+     */
+    public String getAccessToken() {
+        log.info("登录Ai服务获取token...{}", loginUrl);
+        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("username", userName);
+        formData.add("password", passWord);
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
+
+        ResponseEntity<String> response;
+        try {
+            log.info("请求Ai服务登录接口===================>");
+            response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
+        } catch (Exception e) {
+            log.error("请求AI服务登录接口失败", e);
+            return null;
+        }
+
+        if (response != null && response.getStatusCode() == HttpStatus.OK) {
+            String body = response.getBody();
+            log.info("请求Ai服务登录成功 postForEntity.getBody()\t{}", body);
+            if (StringUtils.hasText(body)) {
+                JSONObject jsonObject = JSONObject.parseObject(body);
+                if (jsonObject != null) {
+                    JSONObject dataJson = jsonObject.getJSONObject("data");
+                    if (dataJson != null) {
+                        return dataJson.getString("access_token");
+                    }
+                }
+            }
+            log.warn("AI服务登录响应解析失败 body: {}", body);
+            return null;
+        }
+
+        log.error("请求AI服务 登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+        return null;
+    }
+
+
+    /**
+     * 调用AI服务创建任务
+     *
+     * @return accessToken
+     */
+    public String createTask(String accessToken, String text, List<String> img_urls) {
+        log.info("创建Ai服务任务...{}", auditTaskUrl);
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
+
+        Map<String, Object> analyzeRequest = new HashedMap<>();
+        analyzeRequest.put("text", StringUtils.hasText(text) ? text : "");
+        analyzeRequest.put("img_urls", img_urls);
+
+        HttpEntity<Map<String, Object>> analyzeEntity = new HttpEntity<>(analyzeRequest, analyzeHeaders);
+
+        ResponseEntity<String> analyzeResp = null;
+        try {
+            analyzeResp = restTemplate.postForEntity(auditTaskUrl, analyzeEntity, String.class);
+        } catch (org.springframework.web.client.HttpServerErrorException.ServiceUnavailable e) {
+            log.error("调用提交商品审核任务接口返回503 Service Unavailable错误: {}", e.getResponseBodyAsString());
+            return  null;
+        } catch (Exception e) {
+            log.error("调用提交商品审核任务接口异常", e);
+            return  null;
+        }
+
+        if (analyzeResp != null && analyzeResp.getStatusCodeValue() == 200) {
+            String analyzeBody = analyzeResp.getBody();
+            log.info("提交商品审核任务提交成功, 返回: {}", analyzeBody);
+
+            JSONObject analyzeJson = JSONObject.parseObject(analyzeBody);
+            JSONObject dataJsonObj = analyzeJson.getJSONObject("data");
+
+            if (dataJsonObj == null) {
+                log.error("提交商品审核任务返回数据为空");
+                R.fail("提交商品审核任务返回数据为空");
+                return  null;
+            }
+
+            // 获取record_id用于后续查询
+            String taskId = dataJsonObj.getString("task_id");
+            if (taskId == null) {
+                log.error("提交商品审核任务返回record_id为空");
+                R.fail("提交商品审核任务返回record_id为空");
+                return  null;
+            }
+            return taskId;
+        } else {
+            if (analyzeResp != null) {
+                log.error("调用提交商品审核任务接口失败, http状态: {}", analyzeResp.getStatusCode());
+                R.fail("调用提交商品审核任务接口失败, http状态: " + analyzeResp.getStatusCode());
+                return  null;
+            }
+        }
+        return  null;
+    }
+}

+ 4 - 3
alien-second/src/main/java/shop/alien/second/util/AiUserViolationUtils.java

@@ -33,8 +33,8 @@ public class AiUserViolationUtils {
     private final LifeUserMapper lifeUserMapper;
     private final LifeMessageMapper lifeMessageMapper;
 
-    //    @Value("${third-party-login.base-url:http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login}")
-    private String loginUrl = "http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login";
+    @Value("${third-party-login.base-url:http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login}")
+    private String loginUrl;
 
     @Value("${third-party-user-name.base-url:UdUser}")
     private String userName;
@@ -42,7 +42,8 @@ public class AiUserViolationUtils {
     @Value("${third-party-pass-word.base-url:123456}")
     private String passWord;
 
-    private String userComplaintRecordUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/user_complaint_record/submit";
+    @Value("${third-party-userComplaintRecordUrl.base-url:http://192.168.2.250:9000/ai/auto-review/api/v1/user_complaint_record/submit}")
+    private String userComplaintRecordUrl;
 
 
     /**

+ 57 - 0
alien-second/src/main/java/shop/alien/second/util/JsonUtils.java

@@ -31,4 +31,61 @@ public class JsonUtils {
             throw new RuntimeException("解析JSON失败", e);
         }
     }
+
+
+    /**
+     * 从图片对象中获取URL(支持多种可能的字段名)
+     * 例如:从 banner_image 或 vertical_image 对象中获取 url 或 imgUrl
+     *
+     * @param jsonString JSON字符串
+     * @param imageObjectKey 图片对象的key,如 "banner_image" 或 "vertical_image"
+     * @return 图片URL字符串,如果不存在则返回null
+     */
+    public static String getImageUrlFromObject(String jsonString, String imageObjectKey) {
+        try {
+            JsonNode jsonNode = objectMapper.readTree(jsonString);
+            JsonNode dataNode = jsonNode.get("data");
+            if (dataNode == null) {
+                return null;
+            }
+            
+            JsonNode imageNode = dataNode.get(imageObjectKey);
+            if (imageNode == null || !imageNode.isObject()) {
+                return null;
+            }
+            
+            // 尝试常见的URL字段名
+            String[] possibleUrlFields = {"url", "imgUrl", "imageUrl", "img_url", "image_url"};
+            for (String field : possibleUrlFields) {
+                JsonNode urlNode = imageNode.get(field);
+                if (urlNode != null && urlNode.isTextual()) {
+                    return urlNode.asText();
+                }
+            }
+            
+            return null;
+        } catch (Exception e) {
+            throw new RuntimeException("解析图片URL失败,对象key: " + imageObjectKey, e);
+        }
+    }
+
+    /**
+     * 获取banner图片URL
+     *
+     * @param jsonString JSON字符串
+     * @return banner图片URL
+     */
+    public static String getBannerImageUrl(String jsonString) {
+        return getImageUrlFromObject(jsonString, "banner_image");
+    }
+
+    /**
+     * 获取竖版图片URL
+     *
+     * @param jsonString JSON字符串
+     * @return 竖版图片URL
+     */
+    public static String getVerticalImageUrl(String jsonString) {
+        return getImageUrlFromObject(jsonString, "vertical_image");
+    }
 }

+ 351 - 0
alien-store-platform/pom.xml

@@ -0,0 +1,351 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>shop.alien</groupId>
+        <artifactId>alien-cloud</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <artifactId>alien-store-platform</artifactId>
+    <version>1.0.0</version>
+    <name>alien-store-platform</name>
+    <description>爱丽恩商家平台项目</description>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.core</groupId>
+                    <artifactId>jackson-databind</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.elasticsearch.client</groupId>
+            <artifactId>elasticsearch-rest-high-level-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- mybatis-plus代码生成器 Start -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-generator</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>jsqlparser</artifactId>
+                    <groupId>com.github.jsqlparser</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>mybatis</artifactId>
+                    <groupId>org.mybatis</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>mybatis-spring</artifactId>
+                    <groupId>org.mybatis</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-annotation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-extension</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-core</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <!-- mybatisPlus Freemarker 模版引擎 -->
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+        </dependency>
+        <!-- mybatis-plus代码生成器 End -->
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+
+        <!--Swagger Start-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>swagger-annotations</artifactId>
+                    <groupId>io.swagger</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>swagger-models</artifactId>
+                    <groupId>io.swagger</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-models</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>swagger-bootstrap-ui</artifactId>
+        </dependency>
+        <!--Swagger End-->
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!--token-->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dysmsapi20170525</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.netease.yidun</groupId>
+            <artifactId>yidun-java-sdk</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>shop.alien</groupId>
+            <artifactId>alien-entity</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>shop.alien</groupId>
+            <artifactId>alien-util</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>shop.alien</groupId>
+            <artifactId>alien-config</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+        </dependency>
+
+        <!--允许你使用流(Flux和Mono)来处理异步、非阻塞的数据-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-captcha</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>3.1.2</version> <!-- 使用最新版本 -->
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>prepare-package</phase> <!-- 在package之前执行 -->
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/lib</outputDirectory> <!-- 指定输出目录 -->
+                            <includeScope>runtime</includeScope> <!-- 仅包含运行时依赖 -->
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>shop.alien.storeplatform.AlienStorePlatformApplication</mainClass>
+                    <layout>ZIP</layout>
+                    <includes>
+                        <include>
+                            <groupId>nothing</groupId>
+                            <artifactId>nothing</artifactId>
+                        </include>
+                    </includes>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <profiles>
+        <profile>
+            <id>test</id>
+            <properties>
+                <profiles.active>test</profiles.active>
+                <nacos.server-addr>192.168.2.252:8848</nacos.server-addr>
+                <nacos.namespace>0e1e2d77-56e8-422c-8317-6f71d7285e59</nacos.namespace>
+                <nacos.username>nacos</nacos.username>
+                <nacos.password>ngfriend198092</nacos.password>
+            </properties>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+        </profile>
+    </profiles>
+</project>

+ 160 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java

@@ -0,0 +1,160 @@
+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.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityDTO;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
+import shop.alien.storeplatform.service.OperationalActivityService;
+
+import java.util.List;
+import java.util.Optional;
+
+import static shop.alien.storeplatform.service.impl.OperationalActivityServiceImpl.failureReasonHolder;
+
+/**
+ * 运营活动管理控制器
+ *
+ * @author system
+ * @since 2025-11-26
+ */
+@Slf4j
+@Api(tags = {"商家端-运营活动管理"})
+@ApiSort(10)
+@CrossOrigin
+@RestController
+@RequestMapping("/operationalActivity")
+@RequiredArgsConstructor
+public class OperationalActivityController {
+
+    private final OperationalActivityService activityService;
+
+    @ApiOperation("创建运营活动")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/create")
+    public R<String> createActivity(@RequestBody StoreOperationalActivityDTO dto) {
+        log.info("OperationalActivityController.createActivity: dto={}", dto);
+        try {
+            log.warn("用户输入的图片描述{}",dto.getImgDescribe());
+            int result = activityService.createActivity(dto);
+            if (1 == result) {
+                return R.success("活动创建成功");
+            } else if (2 == result) {
+                try {
+                    return R.fail("活动创建失败,原因:审核未通过"+
+                            Optional.ofNullable(failureReasonHolder.get()));
+                }finally {
+                    failureReasonHolder.remove();
+                }
+            }else {
+                return R.fail("活动创建失败");
+            }
+        } catch (Exception e) {
+            log.error("OperationalActivityController.createActivity ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("更新运营活动")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/update")
+    public R<String> updateActivity(@RequestBody StoreOperationalActivityDTO dto) {
+        log.info("OperationalActivityController.updateActivity: dto={}", dto);
+        try {
+            int result = activityService.updateActivity(dto);
+            if (result > 0) {
+                return R.success("活动更新成功");
+            }
+            return R.fail("活动更新失败");
+        } catch (Exception e) {
+            log.error("OperationalActivityController.updateActivity ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("删除运营活动")
+    @ApiOperationSupport(order = 3)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "活动ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/delete")
+    public R<String> deleteActivity(@RequestParam("id") Integer id) {
+        log.info("OperationalActivityController.deleteActivity: id={}", id);
+        try {
+            int result = activityService.deleteActivity(id);
+            if (result > 0) {
+                return R.success("活动删除成功");
+            }
+            return R.fail("活动删除失败");
+        } catch (Exception e) {
+            log.error("OperationalActivityController.deleteActivity ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据ID获取活动详情")
+    @ApiOperationSupport(order = 4)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "活动ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/queryActivityById")
+    public R<StoreOperationalActivityVO> queryActivityById(@RequestParam("id") Integer id) {
+        log.info("OperationalActivityController.getActivityById: id={}", id);
+        try {
+            StoreOperationalActivityVO result = activityService.queryActivityById(id);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("OperationalActivityController.getActivityById ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("根据商户ID获取活动列表")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "storeId", value = "商户ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @PostMapping("/queryActivityList")
+    public R<IPage<StoreOperationalActivityVO>> queryActivityList(@RequestBody StoreOperationalActivityDTO dto) {
+        log.info("OperationalActivityController.queryActivityList: storeId={}, status={}, activityName={}", dto.getStoreId(), dto.getStatus(), dto.getActivityName());
+        try {
+            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());
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("OperationalActivityController.qeryActivityList ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+
+    @ApiOperation("启用/禁用活动")
+    @ApiOperationSupport(order = 7)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "id", value = "活动ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "status", value = "状态:0-禁用, 1-启用", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/updateStatus")
+    public R<String> updateActivityStatus(
+            @RequestParam("id") Integer id,
+            @RequestParam("status") Integer status) {
+        log.info("OperationalActivityController.updateActivityStatus: id={}, status={}", id, status);
+        try {
+            int result = activityService.updateActivityStatus(id, status);
+            if (result > 0) {
+                return R.success("状态更新成功");
+            }
+            return R.fail("状态更新失败");
+        } catch (Exception e) {
+            log.error("OperationalActivityController.updateActivityStatus ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}
+

+ 59 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/feign/AlienAIFeign.java

@@ -0,0 +1,59 @@
+package shop.alien.storeplatform.feign;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.http.MediaType;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.*;
+
+@FeignClient(url = "${feign.alienAI.url}", name = "alien-AI")
+public interface AlienAIFeign {
+
+    /**
+     * 登录接口 - 获取access_token
+     *
+     * @param username 用户名
+     * @param password 密码
+     * @return JsonNode 响应体,包含access_token
+     */
+    @PostMapping(value = "/ai/user-auth-core/api/v1/auth/login",
+            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode login(@RequestBody MultiValueMap<String, String> formData);
+
+    /**
+     * 使用 JsonNode 灵活调用接口 - 生成促销图片
+     *
+     * @param authorization Bearer token,格式为 "Bearer {access_token}"
+     * @param requestBody JsonNode 请求体,可以灵活构建
+     * @return JsonNode 响应体,可以灵活解析
+     */
+    @PostMapping(value = "/ai/life-manager/api/v1/promotion_image/generate",
+                 consumes = MediaType.APPLICATION_JSON_VALUE,
+                 produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode generatePromotionImage(@RequestHeader("Authorization") String authorization, @RequestBody JsonNode requestBody);
+
+    /**
+     * 使用 JsonNode 灵活调用接口 - 发起AI审核(多模态)
+     *
+     * @param authorization Bearer token,格式为 "Bearer {access_token}"
+     * @param requestBody JsonNode 请求体,可以灵活构建
+     * @return JsonNode 响应体,可以灵活解析
+     */
+    @PostMapping(value = "/ai/auto-review/api/v1/multimodal_audit_task/submit",
+            consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode multiModelAudit(@RequestHeader("Authorization") String authorization, @RequestBody JsonNode requestBody);
+
+
+    /**
+     * 获取 AI审核(多模态) 结果
+     *
+     * @param authorization Bearer token,格式为 "Bearer {access_token}"
+     * @param taskId 任务ID
+     * @return JsonNode 响应体,包含审核结果
+     */
+    @GetMapping(value = "/ai/auto-review/api/v1/multimodal_audit_task/getResult",
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode getMultiModelAuditResult(@RequestHeader("Authorization") String authorization, @RequestParam("task_id") String taskId);
+}

+ 434 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -0,0 +1,434 @@
+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 com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RBucket;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import shop.alien.entity.store.LifeDiscountCoupon;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.storePlatform.StoreOperationalActivity;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityDTO;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
+import shop.alien.mapper.LifeDiscountCouponMapper;
+import shop.alien.mapper.StoreImgMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+import shop.alien.storeplatform.feign.AlienAIFeign;
+import shop.alien.storeplatform.service.OperationalActivityService;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * 运营活动服务实现类
+ * <p>
+ * 提供运营活动的增删改查功能,包括:
+ * 1. 活动的创建、更新、删除
+ * 2. 活动列表查询、分页查询
+ * 3. 活动状态管理
+ * </p>
+ *
+ * @author system
+ * @since 2025-11-26
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class OperationalActivityServiceImpl implements OperationalActivityService {
+
+    private final StoreOperationalActivityMapper activityMapper;
+
+    private final StoreImgMapper imgMapper;
+
+    private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
+
+    private final AlienAIFeign alienAIFeign;
+
+    private final RedissonClient redissonClient;
+
+    @Value("${ai.aiAccount}")
+    private String aiAccount;
+
+    @Value("${ai.aiPassword}")
+    private String aiPassword;
+
+    @Value("${ai.token-timeout:3}")
+    private Integer aiTokenTimeout;
+
+    // AI平台token Redis key
+    private static final String AI_TOKEN_KEY = "ai:platform:token";
+
+    ObjectMapper objectMapper = new ObjectMapper();
+
+    public static final ThreadLocal<String> failureReasonHolder = new ThreadLocal<>();
+
+    // AI参数模板
+    String tpl = "活动名称:%s\n"
+            + "活动时间:%s - %s,格式化时间为yyyy-mm-dd类型\n"
+            + "用户可参与次数:%s\n"
+            + "活动规则:%s\n"
+            + "优惠券发放数量:%s\n"
+            + "图片描述:%s";
+
+    @Override
+    public int createActivity(StoreOperationalActivityDTO dto) {
+        log.info("OperationalActivityServiceImpl.createActivity: dto={}", dto);
+
+        StoreOperationalActivity activity = new StoreOperationalActivity();
+        BeanUtils.copyProperties(dto, activity);
+
+        // 设置默认值
+        if (activity.getParticipationLimit() == null) {
+            activity.setParticipationLimit(0);
+        }
+        if (activity.getStatus() == null) {
+            activity.setStatus(1);
+        }
+        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 audioResponse = alienAIFeign.multiModelAudit(authorization, dto.getAuditParam());
+                    // 如果审核失败,不进行文字生成海报图片,提前短路。
+                    if (audioResponse.has("data")) {
+                        String taskId = audioResponse.get("data").get("task_id").asText();
+                        JsonNode audioResResponse = alienAIFeign.getMultiModelAuditResult(authorization, taskId);
+                        String status=audioResResponse.get("data").get("status").asText();
+                        for (int i = 0; i < 60; i++) { // AI审核接口速度还在优化中 todo
+                            if (status.equals("completed")||status.equals("failed")) {
+                                break;
+                            }
+                            audioResResponse = alienAIFeign.getMultiModelAuditResult(authorization, taskId);
+                            status = audioResResponse.get("data").get("status").asText();
+                            Thread.sleep(1000);
+                        }
+                        String auditRes = audioResResponse.get("data").get("audit_result").asText();
+                        if (!status.equals("failed")&&auditRes != null && auditRes.equals("compliant")) {
+                            activity.setStatus(8);
+                            result = activityMapper.insert(activity);
+                        } else {
+                            String failureReason = audioResResponse.get("data").get("failure_reason").asText();
+                            failureReasonHolder.set(failureReason);
+                            // 审核不成功 不生成任何记录,返回原因
+                            return 2;
+                        }
+                    }
+                    // 使用用户描述和页面输入框其他信息,让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);
+            }
+            dto.getActivityTitleImg().setBusinessId(activity.getId());
+            dto.getActivityTitleImg().setImgType(26);
+            imgMapper.insert(dto.getActivityTitleImg());
+
+            dto.getActivityDetailImg().setBusinessId(activity.getId());
+            dto.getActivityDetailImg().setImgType(27);
+            imgMapper.insert(dto.getActivityDetailImg());
+        return result;
+    }
+
+    /**
+     * 登录AI平台,获取token
+     */
+    private String getToken() {
+        // 1. 先从缓存获取
+        RBucket<String> tokenBucket = redissonClient.getBucket(AI_TOKEN_KEY);
+        String cachedToken = tokenBucket.get();
+        if (cachedToken != null && !cachedToken.isEmpty()) {
+            log.debug("从缓存获取 AI token");
+            return cachedToken;
+        }
+        // 2. 缓存未命中,调用登录接口
+        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("username", aiAccount);
+        formData.add("password", aiPassword);
+        JsonNode loginResponse = alienAIFeign.login(formData);
+        String accessToken = null;
+        if (loginResponse != null && loginResponse.has("data")) {
+            JsonNode data = loginResponse.get("data");
+            if (data.has("access_token")) {
+                accessToken = data.get("access_token").asText();
+            }
+        }
+        // 3. 如果获取成功,存入缓存
+        if (accessToken != null && !accessToken.isEmpty()) {
+            tokenBucket.set(accessToken, aiTokenTimeout, TimeUnit.SECONDS);
+        } else {
+            log.error("获取 AI token 失败");
+        }
+        return accessToken;
+    }
+
+    @Override
+    public int updateActivity(StoreOperationalActivityDTO dto) {
+        log.info("OperationalActivityServiceImpl.updateActivity: dto={}", dto);
+
+        if (dto.getId() == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+
+        StoreOperationalActivity activity = new StoreOperationalActivity();
+        BeanUtils.copyProperties(dto, activity);
+        Integer result = activityMapper.updateById(activity);
+
+        // 添加
+        if (result > 0) {
+            // 删除原本的图片
+            LambdaUpdateWrapper<StoreImg> wrapper = new LambdaUpdateWrapper<>();
+            wrapper.eq(StoreImg::getBusinessId, dto.getId())
+                    .eq(StoreImg::getStoreId, dto.getStoreId())
+                    .set(StoreImg::getDeleteFlag, 1);
+            imgMapper.update(null, wrapper);
+
+
+            // 插入新图片
+            StoreImg activityTitleImg = new StoreImg();
+            activityTitleImg.setStoreId(dto.getStoreId());
+            activityTitleImg.setImgType(26);
+            activityTitleImg.setImgSort(dto.getActivityTitleImg().getImgSort());
+            activityTitleImg.setImgUrl(dto.getActivityTitleImg().getImgUrl());
+            activityTitleImg.setBusinessId(dto.getId());
+            imgMapper.insert(activityTitleImg);
+
+            // 插入新图片
+            StoreImg activityDetailImg = new StoreImg();
+            activityDetailImg.setStoreId(dto.getStoreId());
+            activityDetailImg.setImgType(27);
+            activityDetailImg.setImgSort(dto.getActivityDetailImg().getImgSort());
+            activityDetailImg.setImgUrl(dto.getActivityDetailImg().getImgUrl());
+            activityDetailImg.setBusinessId(dto.getId());
+            imgMapper.insert(activityDetailImg);
+        }
+        return result;
+    }
+
+    @Override
+    public int deleteActivity(Integer id) {
+        log.info("OperationalActivityServiceImpl.deleteActivity: id={}", id);
+
+        if (id == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+
+        // 逻辑删除
+        return activityMapper.deleteById(id);
+    }
+
+    @Override
+    public StoreOperationalActivityVO queryActivityById(Integer id) {
+        log.info("OperationalActivityServiceImpl.getActivityById: id={}", id);
+
+        if (id == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+
+        StoreOperationalActivity activity = activityMapper.selectById(id);
+
+        if (activity == null) {
+            return null;
+        }
+
+        // 创建实体类
+        StoreOperationalActivityVO vo = new StoreOperationalActivityVO();
+        BeanUtils.copyProperties(activity, vo);
+
+        if (activity.getStatus() == 1) {
+            vo.setStatusName("待审核");
+        } else if (activity.getStatus() == 2) {
+            vo.setStatusName("未开始");
+        } else if (activity.getStatus() == 3) {
+            vo.setStatusName("审核拒绝");
+        } else if (activity.getStatus() == 4) {
+            vo.setStatusName("已售罄");
+        } else if (activity.getStatus() == 5) {
+            vo.setStatusName("进行中");
+        } else if (activity.getStatus() == 6) {
+            vo.setStatusName("已下架");
+        } else if (activity.getStatus() == 7) {
+            vo.setStatusName("已结束");
+        }
+
+        // 设置优惠券名称(判空处理)
+        if (activity.getCouponId() != null) {
+            LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
+            if (coupon != null) {
+                vo.setCouponName(coupon.getName());
+            }
+        }
+
+        StoreImg activityTitleImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
+                .eq(StoreImg::getStoreId, vo.getStoreId())
+                .eq(StoreImg::getImgType, 26)
+                .eq(StoreImg::getDeleteFlag, 0)
+                .eq(StoreImg::getBusinessId, activity.getId()));
+        if (activityTitleImg != null) {
+            vo.setActivityTitleImgUrl(activityTitleImg.getImgUrl());
+        }
+
+
+        StoreImg activityDetailImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
+                .eq(StoreImg::getStoreId, vo.getStoreId())
+                .eq(StoreImg::getImgType, 27)
+                .eq(StoreImg::getDeleteFlag, 0)
+                .eq(StoreImg::getBusinessId, activity.getId()));
+        if (activityDetailImg != null) {
+            vo.setActivityDetailImgUrl(activityDetailImg.getImgUrl());
+        }
+
+        return vo;
+    }
+
+
+    @Override
+    public IPage<StoreOperationalActivityVO> queryActivityList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize) {
+        log.info("OperationalActivityServiceImpl.queryActivityList: storeId={}, status={}, activityName={}, pageNum={}, pageSize={}",
+                storeId, status, activityName, pageNum, pageSize);
+
+        LambdaQueryWrapper<StoreOperationalActivity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(storeId != null, StoreOperationalActivity::getStoreId, storeId);
+        wrapper.like(activityName != null && activityName != "", StoreOperationalActivity::getActivityName, activityName);
+        wrapper.eq(status != null, StoreOperationalActivity::getStatus, status);
+
+        IPage<StoreOperationalActivity> list = activityMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
+
+        // 将list复制到vo
+        List<StoreOperationalActivityVO> voRecords = new ArrayList<>();
+
+        for (StoreOperationalActivity activity : list.getRecords()) {
+            // 创建实体类
+            StoreOperationalActivityVO vo = new StoreOperationalActivityVO();
+            BeanUtils.copyProperties(activity, vo);
+
+            if (activity.getStatus() == 1) {
+                vo.setStatusName("待审核");
+            } else if (activity.getStatus() == 2) {
+                vo.setStatusName("未开始");
+            } else if (activity.getStatus() == 3) {
+                vo.setStatusName("审核拒绝");
+            } else if (activity.getStatus() == 4) {
+                vo.setStatusName("已售罄");
+            } else if (activity.getStatus() == 5) {
+                vo.setStatusName("进行中");
+            } else if (activity.getStatus() == 6) {
+                vo.setStatusName("已下架");
+            } else if (activity.getStatus() == 7) {
+                vo.setStatusName("已结束");
+            }
+
+            vo.setCouponName(lifeDiscountCouponMapper.selectById(activity.getCouponId()).getName());
+
+            voRecords.add(vo);
+        }
+
+        // 创建分页结果对象
+        Page<StoreOperationalActivityVO> voList = new Page<>(list.getCurrent(), list.getSize(), list.getTotal());
+        voList.setRecords(voRecords);
+
+        return voList;
+    }
+
+    public static void main(String[] args) {
+        // 时间格式化
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+        Date now = new Date();
+        System.out.println("当前时间: " + sdf.format(now));
+
+        // 获取当天零点零分零秒时间
+        Calendar todayStart = Calendar.getInstance();
+        todayStart.setTime(now);
+        todayStart.set(Calendar.HOUR_OF_DAY, 0);
+        todayStart.set(Calendar.MINUTE, 0);
+        todayStart.set(Calendar.SECOND, 0);
+        todayStart.set(Calendar.MILLISECOND, 0);
+        Date todayZero = todayStart.getTime();
+        System.out.println("当天零点时间: " + sdf.format(todayZero));
+
+        // now + 1天(零点)
+        Calendar calendarPlus = Calendar.getInstance();
+        calendarPlus.setTime(now);
+        calendarPlus.add(Calendar.DAY_OF_MONTH, 1);
+        calendarPlus.set(Calendar.HOUR_OF_DAY, 0);
+        calendarPlus.set(Calendar.MINUTE, 0);
+        calendarPlus.set(Calendar.SECOND, 0);
+        calendarPlus.set(Calendar.MILLISECOND, 0);
+        Date nowPlusOneDay = calendarPlus.getTime();
+        System.out.println("当前时间+1天(零点): " + sdf.format(nowPlusOneDay));
+
+    }
+
+    @Override
+    public int updateActivityStatus(Integer id, Integer status) {
+        log.info("OperationalActivityServiceImpl.updateActivityStatus: id={}, status={}", id, status);
+
+        if (id == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+
+        StoreOperationalActivity activity = new StoreOperationalActivity();
+        activity.setId(id);
+        activity.setStatus(status);
+
+        return activityMapper.updateById(activity);
+    }
+}
+

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

@@ -81,12 +81,12 @@ public class WebSocketProcess implements ApplicationContextAware {
             }
 
             // 检查消息合规性
-            if (!checkCompliance(webSocketVo)) {
-                webSocketVo.setType("7");
-                webSocketVo.setText("发送内容存在违规行为");
-                sendMessage(webSocketVo.getSenderId(), JSONObject.from(webSocketVo).toJSONString());
-                return;
-            }
+//            if (!checkCompliance(webSocketVo)) {
+//                webSocketVo.setType("7");
+//                webSocketVo.setText("发送内容存在违规行为");
+//                sendMessage(webSocketVo.getSenderId(), JSONObject.from(webSocketVo).toJSONString());
+//                return;
+//            }
 
             log.info("webSocketVo----------------{}", JSONObject.from(webSocketVo).toJSONString());
             log.info("concurrentHashMap----------{}", concurrentHashMap.keySet());

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

@@ -0,0 +1,189 @@
+package shop.alien.store.controller;
+
+import com.alibaba.fastjson2.JSONObject;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import io.swagger.annotations.ApiSort;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+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 java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Api(tags = {"ai审核"})
+@ApiSort(9)
+@CrossOrigin
+@RestController
+@RequestMapping("/aiAudit")
+@RequiredArgsConstructor
+public class AiAuditController {
+
+    private final RestTemplate restTemplate;
+
+    @Value("${third-party-user-name.base-url}")
+    private String userName;
+
+    @Value("${third-party-user-name-admin.base-url}")
+    private String userNameAdmin;
+
+    @Value("${third-party-pass-word.base-url}")
+    private String passWord;
+
+    @Value("${third-party-login.base-url:http://192.168.2.250:9100/ai/user-auth-core/api/v1/auth/login}")
+    private String loginUrl;
+
+    @Value("${third-party-text-check.base-url:http://192.168.2.250:9100/ai/auto-review/api/v1/trade_relevance/check}")
+    private String aiTextCheckUrl;
+
+    @Value("${third-party-content_compliance-check.base-url:http://192.168.2.250:9100/ai/auto-review/api/v1/content_compliance/check}")
+    private String aiContentCheckUrl;
+
+    @ApiOperation("ai文本审核")
+    @ApiOperationSupport(order = 1)
+    @GetMapping("/getAiAuditTextCheckResult")
+    public R<JSONObject> getAiAuditTextCheckResult(String text) {
+        String accessToken = fetchAiServiceToken();
+        if (!StringUtils.hasText(accessToken)) {
+            return R.fail("调用AI文字审核文本接口 登录接口失败");
+        }
+        return getAiAuditTextCheck(accessToken, text);
+    }
+
+    @ApiOperation("ai文本审核")
+    @ApiOperationSupport(order = 2)
+    @GetMapping("/getAiAuditContentCheckResult")
+    public R<JSONObject> getAiAuditContentCheckResult(String text) {
+        String accessToken = fetchAiServiceAdminToken();
+        if (!StringUtils.hasText(accessToken)) {
+            return R.fail("调用AI文字审核内容接口 登录接口失败");
+        }
+        return getAiAuditContentCheck(accessToken, text);
+    }
+
+    private R<JSONObject> getAiAuditTextCheck(String accessToken, String text) {
+        JSONObject data = new JSONObject();
+        data.put("is_trade_related", true);
+
+        // 初始化请求体Map
+        Map<String, Object> requestBody = new HashMap<>();
+        requestBody.put("text", text);
+        HttpHeaders aiHeaders = new HttpHeaders();
+        aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+        aiHeaders.set("Authorization", "Bearer " + accessToken);
+
+        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, aiHeaders);
+
+        try {
+            ResponseEntity<String> response = restTemplate.postForEntity(aiTextCheckUrl, request, String.class);
+            if (response.getStatusCodeValue() != 200) {
+                throw new RuntimeException("AI文字审核接口调用失败 http状态:" + response.getStatusCode());
+            }
+            if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(response.getBody())) {
+                JSONObject taskObject = JSONObject.parseObject(response.getBody());
+                if (taskObject.getInteger("code") == 200) {
+                    data = taskObject.getJSONObject("data");
+                }
+            }
+        } catch (Exception e) {
+            log.error("调用AI文字审核接口 接口异常------", e);
+        }
+
+        return R.data(data);
+    }
+
+    private R<JSONObject> getAiAuditContentCheck(String accessToken, String text) {
+        JSONObject data = new JSONObject();
+        data.put("is_compliant", true);
+
+        // 初始化请求体Map
+        Map<String, Object> requestBody = new HashMap<>();
+        requestBody.put("text", text);
+        HttpHeaders aiHeaders = new HttpHeaders();
+        aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+//        aiHeaders.set("Authorization", "Bearer " + accessToken);
+        aiHeaders.set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1cHN0b3JlQGFkbWluLmNvbSIsImlkIjo2LCJ0aW1lIjoxNzYyOTI1NDAzLjY1MTY5MjZ9.07lz8Ox2cGC28UCmqcKCt5R6Rfwtgs-Eiu0ttgWRxws");
+
+        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, aiHeaders);
+
+        try {
+            ResponseEntity<String> response = restTemplate.postForEntity(aiContentCheckUrl, request, String.class);
+            if (response.getStatusCodeValue() != 200) {
+                throw new RuntimeException("AI文字审核文本接口调用失败 http状态:" + response.getStatusCode());
+            }
+            if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty(response.getBody())) {
+                JSONObject taskObject = JSONObject.parseObject(response.getBody());
+                if (taskObject.getInteger("code") == 200) {
+                    data = taskObject.getJSONObject("data");
+                }
+            }
+        } catch (Exception e) {
+            log.error("调用AI文本审核接口 接口异常------", e);
+        }
+
+        return R.data(data);
+    }
+
+    private String fetchAiServiceToken() {
+        log.info("登录Ai服务获取token...{}", loginUrl);
+        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("username", userName);
+        formData.add("password", passWord);
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
+
+        try {
+            ResponseEntity<String> response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
+            if (response != null && response.getStatusCodeValue() == 200 && response.getBody() != null) {
+                JSONObject jsonObject = JSONObject.parseObject(response.getBody());
+                JSONObject dataJson = jsonObject.getJSONObject("data");
+                return dataJson != null ? dataJson.getString("access_token") : null;
+            }
+            log.error("请求差评申诉辅助系统 登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+        } catch (Exception e) {
+            log.error("调用差评申诉辅助系统登录接口异常", e);
+        }
+        return null;
+    }
+
+    private String fetchAiServiceAdminToken() {
+        log.info("登录Ai服务获取token...{}", loginUrl);
+        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("username", userNameAdmin);
+        formData.add("password", passWord);
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
+
+        try {
+            ResponseEntity<String> response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
+            if (response != null && response.getStatusCodeValue() == 200 && response.getBody() != null) {
+                JSONObject jsonObject = JSONObject.parseObject(response.getBody());
+                JSONObject dataJson = jsonObject.getJSONObject("data");
+                return dataJson != null ? dataJson.getString("access_token") : null;
+            }
+            log.error("请求差评申诉辅助系统 登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+        } catch (Exception e) {
+            log.error("调用差评申诉辅助系统登录接口异常", e);
+        }
+        return null;
+    }
+}

+ 63 - 0
alien-store/src/main/java/shop/alien/store/controller/AiUploadController.java

@@ -0,0 +1,63 @@
+package shop.alien.store.controller;
+
+import com.alibaba.fastjson2.JSONObject;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import io.swagger.annotations.ApiSort;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.PostMapping;
+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.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.store.util.ai.AiFeedbackAssignUtils;
+
+@Slf4j
+@Api(tags = {"ai语音识别"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/aiUp")
+@RequiredArgsConstructor
+public class AiUploadController {
+    private final AiFeedbackAssignUtils aiFeedbackAssignUtils;
+    @ApiOperation("AI语音识别")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/GetAIUpload")
+    public R<JSONObject> getAiUpload(
+            @RequestParam("file") MultipartFile file,
+            @RequestParam(value = "language", required = false) String language,
+            @RequestParam(value = "use_itn", required = false) String use_itn,
+            @RequestParam(value = "merge_vad", required = false) String merge_vad) {
+        // 检查文件是否为空
+        if (file == null || file.isEmpty()) {
+            return R.fail("音频文件不能为空");
+        }
+        
+        String accessToken = aiFeedbackAssignUtils.getAccessToken();
+        if (!StringUtils.hasText(accessToken)) {
+          return  R.fail("调用AI语音识别接口 登录接口失败");
+        }
+        
+        try {
+            String speechRecognitionResult = aiFeedbackAssignUtils.getSpeechRecognition1(file, accessToken, language, use_itn, merge_vad);
+            if (speechRecognitionResult == null) {
+                return R.fail("语音识别失败");
+            }
+            // 将返回的JSON字符串解析为JSONObject
+            JSONObject result = JSONObject.parseObject(speechRecognitionResult);
+            return R.data(result);
+        } catch (RuntimeException e) {
+            log.error("语音识别失败", e);
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("语音识别异常", e);
+            return R.fail("语音识别失败: " + e.getMessage());
+        }
+    }
+}

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

@@ -107,9 +107,9 @@ public class AliController {
                         .size();
             }
         }
-        if (size > 0) {
-            return R.fail("该身份证已实名认证过");
-        }
+//        if (size > 0) {
+//            return R.fail("该身份证已实名认证过");
+//        }
         if (aliPayConfig.getIdInfo(name, idCard)) {
             return R.success("身份验证成功");
         }

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

@@ -165,9 +165,9 @@ public class LifeDiscountCouponStoreFriendController {
     @ApiImplicitParams({@ApiImplicitParam(name = "storeId", value = "当前登录店铺id", dataType = "String", paramType = "query", required = true)
     })
     @GetMapping("/getRuleList")
-    private R<List<LifeDiscountCouponFriendRuleVo>> getRuleList(@RequestParam(value = "storeId") String storeId) {
-        log.info("LifeDiscountCouponStoreFriendController.getRuleList?storeId={}", storeId);
-        return R.data(lifeDiscountCouponStoreFriendService.getRuleList(storeId));
+    private R<List<LifeDiscountCouponFriendRuleVo>> getRuleList(@RequestParam(value = "storeId") String storeId, String acName, String status) {
+        log.info("LifeDiscountCouponStoreFriendController.getRuleList?storeId={},name={},status={}", storeId, acName, status);
+        return R.data(lifeDiscountCouponStoreFriendService.getRuleList(storeId, acName, status));
     }
 
     @ApiOperation("查询赠券记录")

+ 117 - 0
alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java

@@ -0,0 +1,117 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.dto.LifeFeedbackDto;
+import shop.alien.entity.store.dto.UserReplyDto;
+import shop.alien.entity.store.dto.*;
+import shop.alien.entity.store.vo.LifeFeedbackDetailVo;
+import shop.alien.entity.store.vo.LifeFeedbackListVo;
+import shop.alien.entity.store.vo.LifeFeedbackVo;
+import shop.alien.store.service.LifeFeedbackService;
+
+/**
+ * 意见反馈 Controller
+ */
+@Api(tags = {"意见反馈模块"})
+@Slf4j
+@CrossOrigin
+@RestController
+@RequestMapping("/feedback")
+@RequiredArgsConstructor
+public class LifeFeedbackController {
+
+    private final LifeFeedbackService lifeFeedbackService;
+
+    @ApiOperation(value = "提交反馈", httpMethod = "POST")
+    @PostMapping("/submit")
+    public R<String> submitFeedback(@RequestBody LifeFeedbackDto dto) {
+        log.info("LifeFeedbackController.submitFeedback, dto={}", dto);
+        return lifeFeedbackService.submitFeedback(dto);
+    }
+
+    @ApiOperation(value = "查询用户历史反馈列表", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "feedbackSource", value = "反馈来源:0-用户端,1-商家端", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "int", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "int", paramType = "query", required = true)
+    })
+    @GetMapping("/list")
+    public R<IPage<LifeFeedbackVo>> getFeedbackList(
+            @RequestParam("userId") Integer userId,
+            @RequestParam("feedbackSource") Integer feedbackSource,
+            @RequestParam(value = "page", defaultValue = "1") int page,
+            @RequestParam(value = "size", defaultValue = "10") int size) {
+        log.info("LifeFeedbackController.getFeedbackList, userId={}, feedbackSource={}, page={}, size={}", 
+                userId, feedbackSource, page, size);
+        IPage<LifeFeedbackVo> result = lifeFeedbackService.getFeedbackList(userId, feedbackSource, page, size);
+        return R.data(result);
+    }
+
+    @ApiOperation(value = "查询反馈详情", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "feedbackId", value = "反馈ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/detail")
+    public R<LifeFeedbackVo> getFeedbackDetail(@RequestParam("feedbackId") Integer feedbackId) {
+        log.info("LifeFeedbackController.getFeedbackDetail, feedbackId={}", feedbackId);
+        return lifeFeedbackService.getFeedbackDetail(feedbackId);
+    }
+
+    @ApiOperation(value = "用户回复", httpMethod = "POST")
+    @PostMapping("/userReply")
+    public R<String> userReply(@RequestBody UserReplyDto dto) {
+        log.info("LifeFeedbackController.userReply, dto={}", dto);
+        return lifeFeedbackService.userReply(dto);
+    }
+
+    // ==================== 中台接口 ====================
+
+    @ApiOperation(value = "中台-查询意见反馈列表", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "feedbackType", value = "反馈类型:0-优化建议,1-问题", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "handleStatus", value = "处理状态:0-处理中,1-已解决,2-未分配,3-无需解决", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "feedbackSource", value = "反馈来源:0-用户端,1-商家端", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "feedbackWay", value = "反馈方式:0-用户反馈,1-AI识别", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "page", value = "页码", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/platform/list")
+    public R<IPage<LifeFeedbackListVo>> getWebFeedbackList(LifeFeedbackQueryDto queryDto) {
+        log.info("LifeFeedbackController.getWebFeedbackList, queryDto={}", queryDto);
+        return lifeFeedbackService.getWebFeedbackList(queryDto);
+    }
+
+    @ApiOperation(value = "中台-查询反馈详情", httpMethod = "GET")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "feedbackId", value = "反馈ID", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/platform/detail")
+    public R<LifeFeedbackDetailVo> getWebFeedbackDetail(@RequestParam("feedbackId") Integer feedbackId) {
+        log.info("LifeFeedbackController.getWebFeedbackDetail, feedbackId={}", feedbackId);
+        return lifeFeedbackService.getWebFeedbackDetail(feedbackId);
+    }
+
+    @ApiOperation(value = "中台-回复用户", httpMethod = "POST")
+    @PostMapping("/platform/reply")
+    public R<String> webReplyUser(@RequestBody LifeFeedbackReplyWebDto replyDto) {
+        log.info("LifeFeedbackController.webReplyUser, replyDto={}", replyDto);
+        return lifeFeedbackService.webReplyUser(replyDto);
+    }
+
+    @ApiOperation(value = "中台-更新处理状态", httpMethod = "POST")
+    @PostMapping("/platform/updateStatus")
+    public R<String> updateWebFeedbackStatus(@RequestBody LifeFeedbackStatusDto statusDto) {
+        log.info("LifeFeedbackController.updateWebFeedbackStatus, statusDto={}", statusDto);
+        return lifeFeedbackService.updateWebFeedbackStatus(statusDto);
+    }
+}
+

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

@@ -11,7 +11,6 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartRequest;
 import shop.alien.entity.result.R;
-import shop.alien.entity.second.SecondGoodsCategory;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.StoreInfoDto;
 import shop.alien.entity.store.vo.*;
@@ -787,7 +786,11 @@ public class StoreInfoController {
     })
     public R<List<StoreCommentSummaryInterest>> getStoreInterestInfo(int storeId) {
         log.info("StoreInfoController.getStoreInterestInfo?storeId={}", storeId);
-        List<StoreCommentSummaryInterest> list = storeCommentSummaryInterestMapper.selectList(new LambdaQueryWrapper<StoreCommentSummaryInterest>().eq(StoreCommentSummaryInterest :: getStoreId, storeId));
+        List<StoreCommentSummaryInterest> list = storeCommentSummaryInterestMapper.selectList(
+                new LambdaQueryWrapper<StoreCommentSummaryInterest>()
+                        .eq(StoreCommentSummaryInterest::getStoreId, storeId)
+                        .last("ORDER BY RAND() LIMIT 1") // 随机排序并只取第一条
+        );
         return R.data(list);
     }
 

+ 35 - 0
alien-store/src/main/java/shop/alien/store/feign/AlienAIFeign.java

@@ -0,0 +1,35 @@
+package shop.alien.store.feign;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.http.MediaType;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.*;
+
+@FeignClient(url = "${feign.alienAI.url}", name = "alien-AI")
+public interface AlienAIFeign {
+
+    /**
+     * 登录接口 - 获取access_token
+     *
+     *
+     *
+     * @return JsonNode 响应体,包含access_token
+     */
+    @PostMapping(value = "/ai/user-auth-core/api/v1/auth/login",
+            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode login(@RequestBody MultiValueMap<String, String> formData);
+
+    /**
+     * 使用 JsonNode 灵活调用接口
+     *
+     * @param authorization Bearer token,格式为 "Bearer {access_token}"
+     * @return JsonNode 响应体,可以灵活解析
+     */
+    @PostMapping(value = "/asr/upload",
+            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode getSpeechRecognition (@RequestHeader("Authorization") String authorization, @RequestBody MultiValueMap<String, Object> formData);
+}
+

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

@@ -55,7 +55,7 @@ public interface LifeDiscountCouponStoreFriendService extends IService<LifeDisco
 
     List<LifeDiscountCouponFriendRuleDetailVo> getReceivedFriendCouponList(String storeId,String friendStoreUserId);
 
-    List<LifeDiscountCouponFriendRuleVo> getRuleList(String storeId);
+    List<LifeDiscountCouponFriendRuleVo> getRuleList(String storeId, String acName, String status);
 
     LifeDiscountCouponFriendRuleVo getRuleById(String id);
 

+ 20 - 0
alien-store/src/main/java/shop/alien/store/service/LifeFeedbackReplyService.java

@@ -0,0 +1,20 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.LifeFeedbackReply;
+
+import java.util.List;
+
+/**
+ * 反馈回复 Service
+ */
+public interface LifeFeedbackReplyService extends IService<LifeFeedbackReply> {
+
+    /**
+     * 根据反馈ID查询回复列表
+     * @param feedbackId 反馈ID
+     * @return 回复列表
+     */
+    List<LifeFeedbackReply> getByFeedbackId(Integer feedbackId);
+}
+

+ 78 - 0
alien-store/src/main/java/shop/alien/store/service/LifeFeedbackService.java

@@ -0,0 +1,78 @@
+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.LifeFeedback;
+import shop.alien.entity.store.dto.*;
+import shop.alien.entity.store.vo.LifeFeedbackDetailVo;
+import shop.alien.entity.store.vo.LifeFeedbackListVo;
+import shop.alien.entity.store.vo.LifeFeedbackVo;
+
+/**
+ * 意见反馈 Service
+ */
+public interface LifeFeedbackService extends IService<LifeFeedback> {
+
+    /**
+     * 提交反馈
+     * @param dto 反馈信息
+     * @return 反馈结果
+     */
+    R<String> submitFeedback(LifeFeedbackDto dto);
+
+    /**
+     * 查询用户历史反馈列表
+     * @param userId 用户ID
+     * @param feedbackSource 反馈来源
+     * @param page 页码
+     * @param size 每页数量
+     * @return 反馈列表
+     */
+    IPage<LifeFeedbackVo> getFeedbackList(Integer userId, Integer feedbackSource, int page, int size);
+
+    /**
+     * 查询反馈详情
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    R<LifeFeedbackVo> getFeedbackDetail(Integer feedbackId);
+
+    /**
+     * 用户回复
+     * @param dto 用户回复信息
+     * @return 回复结果
+     */
+    R<String> userReply(UserReplyDto dto);
+
+    // ==================== 中台接口 ====================
+
+    /**
+     * 中台-查询意见反馈列表
+     * @param queryDto 查询条件
+     * @return 反馈列表
+     */
+    R<IPage<LifeFeedbackListVo>> getWebFeedbackList(LifeFeedbackQueryDto queryDto);
+
+    /**
+     * 中台-查询反馈详情
+     * @param feedbackId 反馈ID
+     * @return 反馈详情
+     */
+    R<LifeFeedbackDetailVo> getWebFeedbackDetail(Integer feedbackId);
+
+    /**
+     * 中台-回复用户
+     * @param replyDto 回复信息
+     * @return 回复结果
+     */
+    R<String> webReplyUser(LifeFeedbackReplyWebDto replyDto);
+
+    /**
+     * 中台-更新反馈处理状态
+     * @param statusDto 状态信息
+     * @return 更新结果
+     */
+    R<String> updateWebFeedbackStatus(LifeFeedbackStatusDto statusDto);
+}
+

+ 47 - 0
alien-store/src/main/java/shop/alien/store/service/LifeImgService.java

@@ -0,0 +1,47 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.LifeImg;
+
+import java.util.List;
+
+/**
+ * 反馈图片 Service
+ */
+public interface LifeImgService extends IService<LifeImg> {
+
+    /**
+     * 根据反馈ID查询图片列表
+     * @param feedbackId 反馈ID
+     * @return 图片列表
+     */
+    List<LifeImg> getByFeedbackId(Integer feedbackId);
+
+    /**
+     * 批量保存图片
+     * @param imgList 图片列表
+     * @return 是否成功
+     */
+    boolean batchSave(List<LifeImg> imgList);
+
+    /**
+     * 根据反馈ID删除图片
+     * @param feedbackId 反馈ID
+     * @return 是否成功
+     */
+    boolean removeByFeedbackId(Integer feedbackId);
+
+    /**
+     * 查询反馈的图片URL列表
+     * @param feedbackId 反馈ID
+     * @return 图片URL列表
+     */
+    List<String> getImgUrlsByFeedbackId(Integer feedbackId);
+
+    /**
+     * 查询反馈的视频URL列表
+     * @param feedbackId 反馈ID
+     * @return 视频URL列表
+     */
+    List<String> getVideoUrlsByFeedbackId(Integer feedbackId);
+}

+ 15 - 0
alien-store/src/main/java/shop/alien/store/service/LifeUserStoreService.java

@@ -110,6 +110,8 @@ public class LifeUserStoreService {
             Map<String, List<Map<String, Object>>> avgPriceMap = lifeUserOrderMapper.allStoreAvgPrice().stream().collect(Collectors.groupingBy(o -> o.get("store_id").toString()));
             // 获取所有店铺的打卡次数
             List<Map<Integer, Integer>> storeClockInCountList = storeClockInService.getStoreClockInCount();
+            // 获取所有店铺打卡次数(有图片并且设置为可见的)
+            List<Map<Integer, Integer>> storeClockInCountMapList = storeClockInService.getStoreClockInWithCanLookCount();
             // 遍历所有门店信息,构造返回结果
             for (StoreInfoVo store : storeInfoVoList) {
 
@@ -209,10 +211,23 @@ public class LifeUserStoreService {
                         storeMap.put("storeClockInCount", b.get("count"));
                     }
                 });
+
+                storeClockInCountMapList.forEach(b -> {
+                    Integer storeId = b.get("storeId");
+                    if (Objects.equals(storeId, store.getId())) {
+                        storeMap.put("storeClockInCountWithCanLook", b.get("count"));
+                    }
+                });
+
                 // 如果未找到打卡次数,则设置为0
                 if (null == storeMap.get("storeClockInCount")) {
                     storeMap.put("storeClockInCount", 0);
                 }
+                // 如果未找到打卡次数(有图片并且设置为可见的),则设置为0
+                if (null == storeMap.get("storeClockInCountWithCanLook")) {
+                    storeMap.put("storeClockInCountWithCanLook", 0);
+                    continue;
+                }
                 // 将当前门店的信息添加到返回结果列表中
                 returnMaps.add(storeMap);
             }

+ 7 - 1
alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponStoreFriendServiceImpl.java

@@ -454,11 +454,17 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
     }
 
     @Override
-    public List<LifeDiscountCouponFriendRuleVo> getRuleList(String storeId) {
+    public List<LifeDiscountCouponFriendRuleVo> getRuleList(String storeId, String acName, String status) {
         List<LifeDiscountCouponFriendRuleVo> ruleList = lifeDiscountCouponFriendRuleDetailMapper.getRuleList(storeId);
         if (ObjectUtils.isNotEmpty(ruleList)) {
             ruleList.forEach(i -> i.setStatus(i.getEndDate().after(new Date()) ? "0" : "1"));
         }
+        if (StringUtils.isNotEmpty(acName)) {
+            ruleList = ruleList.stream().filter(i -> i.getAcName().contains(acName)).collect(Collectors.toList());
+        }
+        if (StringUtils.isNotEmpty(status)) {
+            ruleList = ruleList.stream().filter(i -> i.getStatus().equals(status)).collect(Collectors.toList());
+        }
         return ruleList;
     }
 

+ 28 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackReplyServiceImpl.java

@@ -0,0 +1,28 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.LifeFeedbackReply;
+import shop.alien.mapper.LifeFeedbackReplyMapper;
+import shop.alien.store.service.LifeFeedbackReplyService;
+
+import java.util.List;
+
+/**
+ * 反馈回复 Service实现类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class LifeFeedbackReplyServiceImpl extends ServiceImpl<LifeFeedbackReplyMapper, LifeFeedbackReply> implements LifeFeedbackReplyService {
+
+    private final LifeFeedbackReplyMapper lifeFeedbackReplyMapper;
+
+    @Override
+    public List<LifeFeedbackReply> getByFeedbackId(Integer feedbackId) {
+        return lifeFeedbackReplyMapper.selectByFeedbackId(feedbackId);
+    }
+}
+

+ 705 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java

@@ -0,0 +1,705 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.LifeFeedback;
+import shop.alien.entity.store.LifeFeedbackReply;
+import shop.alien.entity.store.LifeImg;
+import shop.alien.entity.store.LifeLog;
+import shop.alien.entity.store.dto.*;
+import shop.alien.entity.store.vo.*;
+import shop.alien.entity.store.vo.FeedbackReplyVo;
+import shop.alien.mapper.LifeFeedbackMapper;
+import shop.alien.mapper.LifeLogMapper;
+import shop.alien.mapper.LifeNoticeMapper;
+import shop.alien.mapper.StoreUserMapper;
+import shop.alien.entity.store.LifeNotice;
+import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.store.vo.WebSocketVo;
+import shop.alien.store.config.WebSocketProcess;
+import shop.alien.store.service.LifeFeedbackService;
+import shop.alien.store.service.LifeFeedbackReplyService;
+import shop.alien.store.service.LifeImgService;
+import com.alibaba.fastjson2.JSONObject;
+
+import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * 意见反馈 Service实现类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
+public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, LifeFeedback> implements LifeFeedbackService {
+
+    private final LifeFeedbackMapper lifeFeedbackMapper;
+    private final LifeImgService lifeImgService;
+    private final LifeLogMapper lifeLogMapper;
+    private final LifeFeedbackReplyService lifeFeedbackReplyService;
+    private final LifeNoticeMapper lifeNoticeMapper;
+    private final StoreUserMapper storeUserMapper;
+    private final WebSocketProcess webSocketProcess;
+
+    @Override
+    public R<String> submitFeedback(LifeFeedbackDto dto) {
+        try {
+            // 1. 参数校验
+            if (dto.getUserId() == null) {
+                return R.fail("用户ID不能为空");
+            }
+            if (dto.getFeedbackSource() == null) {
+                return R.fail("反馈来源不能为空");
+            }
+            if (dto.getFeedbackType() == null) {
+                return R.fail("反馈类型不能为空");
+            }
+            if (dto.getContent() == null || dto.getContent().trim().isEmpty()) {
+                return R.fail("反馈内容不能为空");
+            }
+
+            // 2. 创建反馈记录(使用MyBatis Plus的save方法)
+            LifeFeedback feedback = new LifeFeedback();
+            BeanUtils.copyProperties(dto, feedback);
+            // 如果feedbackWay为空,默认为用户主动反馈(0)
+            if (feedback.getFeedbackWay() == null) {
+                feedback.setFeedbackWay(0);
+            }
+            feedback.setFeedbackTime(new Date());
+            feedback.setHandleStatus(0); // 处理中
+            feedback.setCreateTime(new Date());
+
+            boolean saveResult = this.save(feedback);
+            if (!saveResult) {
+                return R.fail("提交反馈失败");
+            }
+
+            // 3. 保存附件(图片和视频)
+            List<LifeImg> fileList = new ArrayList<>();
+            // 收集所有视频的截图URL,避免重复保存为普通图片
+            List<String> videoThumbnailUrls = new ArrayList<>();
+
+            if (!CollectionUtils.isEmpty(dto.getFileUrlList())) {
+                // 先处理视频,找到所有视频及其封面图
+                List<String> videoUrls = new ArrayList<>();
+                List<String> imageUrls = new ArrayList<>();
+
+                // 分类:区分视频和图片
+                for (String fileUrl : dto.getFileUrlList()) {
+                    if (isVideoUrl(fileUrl)) {
+                        videoUrls.add(fileUrl);
+                    } else if (isImageUrl(fileUrl)) {
+                        imageUrls.add(fileUrl);
+                    }
+                }
+
+                // 处理视频:自动匹配封面图
+                for (String videoUrl : videoUrls) {
+                    LifeImg video = new LifeImg();
+                    video.setFeedbackId(feedback.getId());
+                    video.setImgUrl(videoUrl);
+                    video.setFileType(2); // 2-视频
+                    video.setUploadTime(new Date());
+
+                    // 从fileUrlList中查找对应的封面图URL(通过文件名匹配)
+                    // 视频URL格式: .../video/xxx123456.mp4
+                    // 封面图URL格式: .../video/xxx123456.jpg 或 .../image/xxx123456.jpg
+                    String videoFileName = videoUrl.substring(videoUrl.lastIndexOf('/') + 1);
+                    String videoNameWithoutExt = videoFileName.substring(0, videoFileName.lastIndexOf('.'));
+
+                    // 在图片列表中查找匹配的封面图
+                    for (String imgUrl : imageUrls) {
+                        String imgFileName = imgUrl.substring(imgUrl.lastIndexOf('/') + 1);
+                        if (imgFileName.contains(".")) {
+                            String imgNameWithoutExt = imgFileName.substring(0, imgFileName.lastIndexOf('.'));
+                            // 如果文件名(不含扩展名)相同,且是图片格式,则认为是该视频的封面图
+                            if (videoNameWithoutExt.equals(imgNameWithoutExt) && isImageUrl(imgUrl)) {
+                                video.setThumbnailUrl(imgUrl);
+                                videoThumbnailUrls.add(imgUrl); // 记录已使用的封面图URL
+                                break;
+                            }
+                        }
+                    }
+
+                    fileList.add(video);
+                }
+
+                // 处理图片(排除已作为视频封面的URL)
+                for (String imgUrl : imageUrls) {
+                    // 如果该URL已被用作视频封面,则跳过,不重复保存
+                    if (!videoThumbnailUrls.contains(imgUrl)) {
+                        LifeImg img = new LifeImg();
+                        img.setFeedbackId(feedback.getId());
+                        img.setImgUrl(imgUrl);
+                        img.setFileType(1); // 1-图片
+                        img.setUploadTime(new Date());
+                        fileList.add(img);
+                    }
+                }
+            }
+
+            if (!fileList.isEmpty()) {
+                lifeImgService.batchSave(fileList);
+            }
+
+            // 4. 记录日志(只记录详细内容)
+            saveLog(feedback.getId(), feedback.getContent(), "0");
+
+            return R.success("提交成功");
+        } catch (Exception e) {
+            log.error("提交反馈失败", e);
+            return R.fail("提交反馈失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public IPage<LifeFeedbackVo> getFeedbackList(Integer userId, Integer feedbackSource, int page, int size) {
+        try {
+            // 使用自定义SQL查询(已包含工作人员名称)
+            // 查询用户反馈(feedbackWay=0)和AI识别(feedbackWay=1)的记录
+            Page<LifeFeedbackVo> pageParam = new Page<>(page, size);
+            IPage<LifeFeedbackVo> voPage = lifeFeedbackMapper.selectFeedbackListWithStaff(
+                    pageParam, userId, feedbackSource, null, null
+            );
+
+            // 为每条记录查询附件(图片和视频)并设置反馈类型名称
+            voPage.getRecords().forEach(vo -> {
+                List<String> imgUrls = lifeImgService.getImgUrlsByFeedbackId(vo.getId());
+                vo.setImgUrlList(imgUrls);
+                List<String> videoUrls = lifeImgService.getVideoUrlsByFeedbackId(vo.getId());
+                vo.setVideoUrlList(videoUrls);
+                // 设置反馈类型名称
+                vo.setFeedbackTypeName(getFeedbackTypeName(vo.getFeedbackType()));
+            });
+
+            return voPage;
+        } catch (Exception e) {
+            log.error("查询反馈列表失败", e);
+            return new Page<>(page, size);
+        }
+    }
+
+    @Override
+    public R<LifeFeedbackVo> getFeedbackDetail(Integer feedbackId) {
+        try {
+            // 1. 使用自定义SQL查询反馈详情(已包含工作人员名称)
+            LifeFeedbackVo vo = lifeFeedbackMapper.selectFeedbackDetail(feedbackId);
+            if (vo == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 2. 查询附件(图片和视频)
+            // 查询所有附件,然后过滤出原始反馈的附件(排除回复附件)
+            List<LifeImg> allImgs = lifeImgService.getByFeedbackId(feedbackId);
+            List<String> imgUrls = new ArrayList<>();
+            List<String> videoUrls = new ArrayList<>();
+            Date feedbackTime = vo.getFeedbackTime();
+            if (feedbackTime != null) {
+                long feedbackTimeMs = feedbackTime.getTime();
+                for (LifeImg img : allImgs) {
+                    if (img.getUploadTime() != null) {
+                        long imgTimeMs = img.getUploadTime().getTime();
+                        // 判断附件是否属于原始反馈(时间差在5分钟内,且早于最早的回复)
+                        // 简化处理:如果是反馈后5分钟内的附件,认为是原始反馈的附件
+                        List<LifeFeedbackReply> replyList = lifeFeedbackReplyService.getByFeedbackId(feedbackId);
+                        boolean isOriginalFeedback = true;
+                        if (!replyList.isEmpty()) {
+                            Date firstReplyTime = replyList.get(0).getCreateTime();
+                            // 如果附件时间在最早回复时间之后,则不属于原始反馈
+                            if (img.getUploadTime().after(firstReplyTime)) {
+                                isOriginalFeedback = false;
+                            } else {
+                                long timeDiff = Math.abs(imgTimeMs - feedbackTimeMs);
+                                if (timeDiff > 5 * 60 * 1000) { // 超过5分钟
+                                    isOriginalFeedback = false;
+                                }
+                            }
+                        } else {
+                            long timeDiff = Math.abs(imgTimeMs - feedbackTimeMs);
+                            if (timeDiff > 5 * 60 * 1000) { // 超过5分钟
+                                isOriginalFeedback = false;
+                            }
+                        }
+
+                        if (isOriginalFeedback) {
+                            if (img.getFileType() == 1) {
+                                imgUrls.add(img.getImgUrl());
+                            } else if (img.getFileType() == 2) {
+                                videoUrls.add(img.getImgUrl());
+                            }
+                        }
+                    }
+                }
+            } else {
+                // 如果没有反馈时间,使用简单方式:只查询图片和视频
+                for (LifeImg img : allImgs) {
+                    if (img.getFileType() == 1) {
+                        imgUrls.add(img.getImgUrl());
+                    } else if (img.getFileType() == 2) {
+                        videoUrls.add(img.getImgUrl());
+                    }
+                }
+            }
+            vo.setImgUrlList(imgUrls);
+            vo.setVideoUrlList(videoUrls);
+            // 设置反馈类型名称
+            vo.setFeedbackTypeName(getFeedbackTypeName(vo.getFeedbackType()));
+
+            // 3. 查询回复列表(从life_feedback_reply表)
+            List<LifeFeedbackReply> replyList = lifeFeedbackReplyService.getByFeedbackId(feedbackId);
+            // 转换为VO格式(使用之前查询的allImgs)
+            List<LifeFeedbackVo> replyVoList = new ArrayList<>();
+            for (LifeFeedbackReply reply : replyList) {
+                LifeFeedbackVo replyVo = new LifeFeedbackVo();
+                replyVo.setId(reply.getId());
+                replyVo.setContent(reply.getReplyContent());
+                replyVo.setFeedbackTime(reply.getCreateTime());
+                // reply_type: 0-平台回复, 1-我的回复
+                replyVo.setStaffId(reply.getReplyType() == 0 ? 1 : null); // 平台回复有staffId,用户回复为null
+                // 查询回复的附件(通过时间判断:上传时间在回复创建时间前后5分钟内)
+                List<String> replyImgUrls = new ArrayList<>();
+                List<String> replyVideoUrls = new ArrayList<>();
+                Date replyTime = reply.getCreateTime();
+                long replyTimeMs = replyTime.getTime();
+                for (LifeImg img : allImgs) {
+                    if (img.getUploadTime() != null) {
+                        long imgTimeMs = img.getUploadTime().getTime();
+                        // 判断附件是否属于该回复(时间差在5分钟内)
+                        long timeDiff = Math.abs(imgTimeMs - replyTimeMs);
+                        if (timeDiff <= 5 * 60 * 1000) { // 5分钟
+                            if (img.getFileType() == 1) {
+                                replyImgUrls.add(img.getImgUrl());
+                            } else if (img.getFileType() == 2) {
+                                replyVideoUrls.add(img.getImgUrl());
+                            }
+                        }
+                    }
+                }
+                replyVo.setImgUrlList(replyImgUrls);
+                replyVo.setVideoUrlList(replyVideoUrls);
+                replyVoList.add(replyVo);
+            }
+
+            // 4. 按时间升序排序回复
+            replyVoList.sort((a, b) -> a.getFeedbackTime().compareTo(b.getFeedbackTime()));
+            vo.setPlatformReplies(replyVoList);
+
+            return R.data(vo);
+        } catch (Exception e) {
+            log.error("查询反馈详情失败", e);
+            return R.fail("查询反馈详情失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<String> userReply(UserReplyDto dto) {
+        try {
+            // 1. 参数校验
+            if (dto.getUserId() == null) {
+                return R.fail("用户ID不能为空");
+            }
+            if (dto.getFeedbackSource() == null) {
+                return R.fail("反馈来源不能为空");
+            }
+            if (dto.getFeedbackId() == null) {
+                return R.fail("反馈ID不能为空");
+            }
+            if (dto.getContent() == null || dto.getContent().trim().isEmpty()) {
+                return R.fail("回复内容不能为空");
+            }
+
+            // 2. 查询原始反馈(用于验证反馈是否存在)
+            LifeFeedback originalFeedback = lifeFeedbackMapper.selectById(dto.getFeedbackId());
+            if (originalFeedback == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 3. 创建用户回复记录(保存到life_feedback_reply表)
+            LifeFeedbackReply userReply = new LifeFeedbackReply();
+            userReply.setFeedbackId(dto.getFeedbackId());
+            userReply.setReplyType(1); // 1-我的回复(用户回复)
+            userReply.setReplyContent(dto.getContent());
+            userReply.setCreateTime(new Date());
+            userReply.setUpdateTime(new Date());
+
+            boolean saveResult = lifeFeedbackReplyService.save(userReply);
+            if (!saveResult) {
+                return R.fail("回复失败");
+            }
+
+            // 4. 记录日志(只记录内容)
+            saveLog(dto.getFeedbackId(), dto.getContent(), "2");
+
+            return R.success("回复成功");
+        } catch (Exception e) {
+            log.error("用户回复失败", e);
+            return R.fail("用户回复失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 保存操作日志
+     * @param feedbackId 反馈ID
+     * @param context 日志内容
+     * @param type 操作类型:0-创建反馈工单,1-分配跟踪人员,2-回复用户,3-问题解决状态
+     */
+    private void saveLog(Integer feedbackId, String context, String type) {
+        try {
+            LifeLog lifeLog = new LifeLog();
+            lifeLog.setFeedbackId(feedbackId);
+            lifeLog.setContext(context);
+            lifeLog.setType(type);
+            lifeLog.setCreatedTime(new Date());
+            lifeLogMapper.insert(lifeLog);
+        } catch (Exception e) {
+            log.error("保存日志失败", e);
+        }
+    }
+
+    /**
+     * 获取反馈类型名称
+     * @param feedbackType 反馈类型:0-bug反馈,1-优化反馈,2-新增功能反馈
+     * @return 反馈类型名称
+     */
+    private String getFeedbackTypeName(Integer feedbackType) {
+        if (feedbackType == null) {
+            return "";
+        }
+        switch (feedbackType) {
+            case 0:
+                return "bug反馈";
+            case 1:
+                return "优化反馈";
+            case 2:
+                return "新增功能反馈";
+            default:
+                return "";
+        }
+    }
+
+    /**
+     * 判断URL是否为视频
+     * @param url 文件URL
+     * @return true-视频,false-非视频
+     */
+    private boolean isVideoUrl(String url) {
+        if (url == null || url.isEmpty()) {
+            return false;
+        }
+        String lowerUrl = url.toLowerCase();
+        return lowerUrl.endsWith(".mp4") ||
+               lowerUrl.endsWith(".avi") ||
+               lowerUrl.endsWith(".flv") ||
+               lowerUrl.endsWith(".mkv") ||
+               lowerUrl.endsWith(".rmvb") ||
+               lowerUrl.endsWith(".wmv") ||
+               lowerUrl.endsWith(".3gp") ||
+               lowerUrl.endsWith(".mov");
+    }
+
+    /**
+     * 判断URL是否为图片
+     * @param url 文件URL
+     * @return true-图片,false-非图片
+     */
+    private boolean isImageUrl(String url) {
+        if (url == null || url.isEmpty()) {
+            return false;
+        }
+        String lowerUrl = url.toLowerCase();
+        return lowerUrl.endsWith(".jpg") ||
+               lowerUrl.endsWith(".jpeg") ||
+               lowerUrl.endsWith(".png") ||
+               lowerUrl.endsWith(".bmp") ||
+               lowerUrl.endsWith(".webp") ||
+               lowerUrl.endsWith(".gif") ||
+               lowerUrl.endsWith(".svg");
+    }
+
+
+    // ==================== 中台接口实现 ====================
+
+    @Override
+    public R<IPage<LifeFeedbackListVo>> getWebFeedbackList(LifeFeedbackQueryDto queryDto) {
+        try {
+            Page<LifeFeedbackListVo> pageParam = new Page<>(queryDto.getPage(), queryDto.getSize());
+            IPage<LifeFeedbackListVo> result = lifeFeedbackMapper.selectWebFeedbackList(
+                    pageParam,
+                    queryDto.getFeedbackType(),
+                    queryDto.getHandleStatus(),
+                    queryDto.getFeedbackSource(),
+                    queryDto.getFeedbackWay()
+            );
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("中台-查询意见反馈列表失败", e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    public R<LifeFeedbackDetailVo> getWebFeedbackDetail(Integer feedbackId) {
+        try {
+            if (feedbackId == null) {
+                return R.fail("反馈ID不能为空");
+            }
+
+            // 1. 查询反馈详情
+            LifeFeedbackDetailVo detail = lifeFeedbackMapper.selectWebFeedbackDetail(feedbackId);
+            if (detail == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 2. 查询附件列表(图片/视频)
+            List<FeedbackAttachmentVo> attachments = new ArrayList<>();
+            List<LifeImg> imgList = lifeImgService.getByFeedbackId(feedbackId);
+            if (!CollectionUtils.isEmpty(imgList)) {
+                for (LifeImg img : imgList) {
+                    FeedbackAttachmentVo attachment = new FeedbackAttachmentVo();
+                    attachment.setId(img.getId());
+                    attachment.setFileType(img.getFileType() != null ? img.getFileType() : 1);
+                    attachment.setFileUrl(img.getImgUrl());
+                    attachment.setThumbnailUrl(img.getThumbnailUrl());
+                    attachments.add(attachment);
+                }
+            }
+            detail.setAttachments(attachments);
+
+            // 3. 查询回复列表(平台回复和用户回复)
+            List<LifeFeedbackReply> replyList = lifeFeedbackReplyService.getByFeedbackId(feedbackId);
+            List<FeedbackReplyVo> replyVoList = new ArrayList<>();
+            for (LifeFeedbackReply reply : replyList) {
+                FeedbackReplyVo replyVo = new FeedbackReplyVo();
+                replyVo.setId(reply.getId());
+                replyVo.setFeedbackId(reply.getFeedbackId());
+                replyVo.setReplyType(reply.getReplyType());
+                replyVo.setReplyTypeName(reply.getReplyType() == 0 ? "平台回复" : "用户回复");
+                replyVo.setReplyContent(reply.getReplyContent());
+                replyVo.setCreateTime(reply.getCreateTime());
+                
+                // 查询回复的附件(通过时间判断:上传时间在回复创建时间前后5分钟内)
+                List<String> replyImgUrls = new ArrayList<>();
+                List<String> replyVideoUrls = new ArrayList<>();
+                Date replyTime = reply.getCreateTime();
+                if (replyTime != null && !CollectionUtils.isEmpty(imgList)) {
+                    long replyTimeMs = replyTime.getTime();
+                    for (LifeImg img : imgList) {
+                        if (img.getUploadTime() != null) {
+                            long imgTimeMs = img.getUploadTime().getTime();
+                            // 判断附件是否属于该回复(时间差在5分钟内)
+                            long timeDiff = Math.abs(imgTimeMs - replyTimeMs);
+                            if (timeDiff <= 5 * 60 * 1000) { // 5分钟
+                                if (img.getFileType() == 1) {
+                                    replyImgUrls.add(img.getImgUrl());
+                                } else if (img.getFileType() == 2) {
+                                    replyVideoUrls.add(img.getImgUrl());
+                                }
+                            }
+                        }
+                    }
+                }
+                replyVo.setImgUrlList(replyImgUrls);
+                replyVo.setVideoUrlList(replyVideoUrls);
+                replyVoList.add(replyVo);
+            }
+            
+            // 按时间升序排序回复
+            replyVoList.sort((a, b) -> a.getCreateTime().compareTo(b.getCreateTime()));
+            detail.setReplies(replyVoList);
+
+            return R.data(detail);
+        } catch (Exception e) {
+            log.error("中台-查询反馈详情失败", e);
+            return R.fail("查询失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> webReplyUser(LifeFeedbackReplyWebDto replyDto) {
+        try {
+            // 1. 参数校验
+            if (replyDto.getFeedbackId() == null) {
+                return R.fail("反馈ID不能为空");
+            }
+            if (replyDto.getContent() == null || replyDto.getContent().trim().isEmpty()) {
+                return R.fail("回复内容不能为空");
+            }
+
+            // 2. 查询原始反馈
+            LifeFeedback feedback = lifeFeedbackMapper.selectById(replyDto.getFeedbackId());
+            if (feedback == null) {
+                return R.fail("反馈记录不存在");
+            }
+
+            // 3. 保存平台回复到life_feedback_reply表
+            LifeFeedbackReply platformReply = new LifeFeedbackReply();
+            platformReply.setFeedbackId(replyDto.getFeedbackId());
+            platformReply.setReplyType(0); // 0-平台回复
+            platformReply.setReplyContent(replyDto.getContent());
+            platformReply.setCreateTime(new Date());
+            platformReply.setUpdateTime(new Date());
+            
+            boolean saveResult = lifeFeedbackReplyService.save(platformReply);
+            if (!saveResult) {
+                return R.fail("保存回复失败");
+            }
+
+            // 4. 记录回复日志(类型3-回复用户)
+            String logContent = replyDto.getContent();
+            if (replyDto.getUserReply() != null && !replyDto.getUserReply().trim().isEmpty()) {
+                logContent = replyDto.getContent() + "||用户回复:" + replyDto.getUserReply();
+            }
+            saveFeedbackLog(replyDto.getFeedbackId(), 3, logContent);
+
+            // 5. 发送通知给用户
+            sendFeedbackReplyNotice(feedback, replyDto.getContent());
+
+            return R.success("回复成功");
+        } catch (Exception e) {
+            log.error("中台-回复用户失败", e);
+            return R.fail("回复失败:" + e.getMessage());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> updateWebFeedbackStatus(LifeFeedbackStatusDto statusDto) {
+        try {
+            // 1. 参数校验
+            if (statusDto.getFeedbackId() == null) {
+                return R.fail("反馈ID不能为空");
+            }
+
+            // 2. 更新状态为已解决
+            LifeFeedback updateFeedback = new LifeFeedback();
+            updateFeedback.setId(statusDto.getFeedbackId());
+            updateFeedback.setHandleStatus(1); // 已解决
+            updateFeedback.setUpdateTime(new Date());
+
+            boolean result = this.updateById(updateFeedback);
+            if (!result) {
+                return R.fail("更新失败");
+            }
+
+            // 3. 记录日志(类型0-问题解决状态)
+            String logContent = "问题已解决";
+            saveFeedbackLog(statusDto.getFeedbackId(), 0, logContent);
+
+            return R.success("更新成功");
+        } catch (Exception e) {
+            log.error("中台-更新反馈状态失败", e);
+            return R.fail("更新失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 保存反馈操作日志
+     * @param feedbackId 反馈ID
+     * @param type 操作类型:0-问题解决状态,1-分配跟踪人员,2-创建反馈工单,3-回复用户
+     * @param context 日志内容
+     */
+    private void saveFeedbackLog(Integer feedbackId, Integer type, String context) {
+        try {
+            LifeLog lifeLog = new LifeLog();
+            lifeLog.setFeedbackId(feedbackId);
+            lifeLog.setType(String.valueOf(type));
+            lifeLog.setContext(context);
+            lifeLog.setCreatedTime(new Date());
+            lifeLog.setDeleteFlag(0);
+            lifeLogMapper.insert(lifeLog);
+        } catch (Exception e) {
+            log.error("保存反馈日志失败", e);
+        }
+    }
+
+    /**
+     * 发送平台回复通知给用户
+     * @param feedback 反馈记录
+     * @param replyContent 回复内容
+     */
+    private void sendFeedbackReplyNotice(LifeFeedback feedback, String replyContent) {
+        try {
+            String receiverId = null;
+            
+            // 根据反馈来源判断是用户端还是商家端
+            if (feedback.getFeedbackSource() == null) {
+                log.warn("反馈来源为空,无法发送通知,feedbackId={}", feedback.getId());
+                return;
+            }
+            
+            // userId对应store_user表的id,统一从store_user表查询
+            if (feedback.getUserId() == null) {
+                log.warn("用户ID为空,无法发送通知,feedbackId={}", feedback.getId());
+                return;
+            }
+            
+            StoreUser storeUser = storeUserMapper.selectById(feedback.getUserId());
+            if (storeUser == null || storeUser.getPhone() == null || storeUser.getPhone().trim().isEmpty()) {
+                log.warn("未找到商户用户信息或手机号为空,无法发送通知,userId={}", feedback.getUserId());
+                return;
+            }
+            
+            // 根据feedbackSource设置不同的接收者ID格式
+            if (feedback.getFeedbackSource() == 0) {
+                // 用户端 - 使用user_手机号格式
+                receiverId = "user_" + storeUser.getPhone();
+            } else if (feedback.getFeedbackSource() == 1) {
+                // 商家端 - 使用store_手机号格式
+                receiverId = "store_" + storeUser.getPhone();
+            } else {
+                log.warn("未知的反馈来源,feedbackSource={}, feedbackId={}", feedback.getFeedbackSource(), feedback.getId());
+                return;
+            }
+            
+            // 构建通知消息
+            JSONObject messageJson = new JSONObject();
+            messageJson.put("feedbackId", feedback.getId()); // 添加反馈ID用于区分
+            messageJson.put("message", "平台已回复您的意见反馈:" + replyContent);
+
+            // 创建通知记录
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setReceiverId(receiverId);
+            lifeNotice.setContext(messageJson.toJSONString());
+            lifeNotice.setTitle("意见反馈回复通知");
+            lifeNotice.setSenderId("system");
+            lifeNotice.setIsRead(0);
+            lifeNotice.setNoticeType(1); // 1-系统通知
+            lifeNotice.setBusinessId(feedback.getId());
+            
+            // 保存通知
+            lifeNoticeMapper.insert(lifeNotice);
+            
+            // 通过WebSocket发送实时通知
+            WebSocketVo webSocketVo = new WebSocketVo();
+            webSocketVo.setSenderId("system");
+            webSocketVo.setReceiverId(receiverId);
+            webSocketVo.setCategory("notice");
+            webSocketVo.setNoticeType("1");
+            webSocketVo.setIsRead(0);
+            webSocketVo.setText(JSONObject.toJSONString(lifeNotice));
+            
+            try {
+                webSocketProcess.sendMessage(receiverId, JSONObject.toJSONString(webSocketVo));
+                log.info("平台回复通知发送成功,feedbackId={}, receiverId={}", feedback.getId(), receiverId);
+            } catch (Exception e) {
+                log.error("发送WebSocket通知失败,feedbackId={}, receiverId={}, error={}", 
+                        feedback.getId(), receiverId, e.getMessage());
+            }
+            
+        } catch (Exception e) {
+            log.error("发送平台回复通知异常,feedbackId={}, error={}", feedback.getId(), e.getMessage(), e);
+        }
+    }
+}
+

+ 56 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeImgServiceImpl.java

@@ -0,0 +1,56 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.LifeImg;
+import shop.alien.mapper.LifeImgMapper;
+import shop.alien.store.service.LifeImgService;
+
+import java.util.List;
+
+/**
+ * 反馈图片 Service实现类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class LifeImgServiceImpl extends ServiceImpl<LifeImgMapper, LifeImg> implements LifeImgService {
+
+    private final LifeImgMapper lifeImgMapper;
+
+    @Override
+    public List<LifeImg> getByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.selectByFeedbackId(feedbackId);
+    }
+
+    @Override
+    public boolean batchSave(List<LifeImg> imgList) {
+        if (imgList == null || imgList.isEmpty()) {
+            return false;
+        }
+        // 确保每个附件都有上传时间
+        imgList.forEach(item -> {
+            if (item.getUploadTime() == null) {
+                item.setUploadTime(new java.util.Date());
+            }
+        });
+        return lifeImgMapper.batchInsert(imgList) > 0;
+    }
+
+    @Override
+    public boolean removeByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.deleteByFeedbackId(feedbackId) > 0;
+    }
+
+    @Override
+    public List<String> getImgUrlsByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.selectImgUrlsByFeedbackId(feedbackId);
+    }
+
+    @Override
+    public List<String> getVideoUrlsByFeedbackId(Integer feedbackId) {
+        return lifeImgMapper.selectVideoUrlsByFeedbackId(feedbackId);
+    }
+}

+ 10 - 1
alien-store/src/main/java/shop/alien/store/service/impl/StoreCommentAppealServiceImpl.java

@@ -94,9 +94,18 @@ public class StoreCommentAppealServiceImpl extends ServiceImpl<StoreCommentAppea
     public IPage<StoreCommentAppealVo> getAppealHistory(Integer pageNum, Integer pageSize, Integer storeId, String appealStatus) {
         QueryWrapper<StoreCommentAppealVo> wrapper = new QueryWrapper<>();
         wrapper.eq("a.store_id", storeId)
-                .eq(!appealStatus.isEmpty() && !"null".equals(appealStatus), "a.appeal_status", appealStatus)
+//                .eq(!appealStatus.isEmpty() && !"null".equals(appealStatus), "a.appeal_status", appealStatus)
                 .eq("a.delete_flag", 0)
                 .orderByDesc("a.created_time");
+
+        if (StringUtils.isNotEmpty(appealStatus) && !"null".equals(appealStatus)) {
+            if ("0".equals(appealStatus)) {
+                wrapper.in("a.appeal_status", 0, 3);
+            } else {
+                wrapper.eq("a.appeal_status", appealStatus);
+            }
+        }
+
         IPage<StoreCommentAppealVo> storeCommentAppealPage = storeCommentAppealMapper.getStoreCommentAppealPage(new Page<>(pageNum, pageSize), wrapper);
         storeCommentAppealPage.getRecords().forEach(item -> {
             String[] split = item.getImgId().split(",");

+ 302 - 25
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -13,7 +13,6 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.geo.Point;
 import org.springframework.http.*;
@@ -37,7 +36,6 @@ import shop.alien.entity.storePlatform.StoreOperationalActivity;
 import shop.alien.mapper.*;
 import shop.alien.mapper.storePlantform.StoreLicenseHistoryMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
-import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
 import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.config.GaoDeMapUtil;
 import shop.alien.store.config.WebSocketProcess;
@@ -46,7 +44,6 @@ import shop.alien.store.util.CommonConstant;
 import shop.alien.store.util.FileUploadUtil;
 import shop.alien.store.util.GroupConstant;
 import shop.alien.store.util.ai.AiAuthTokenUtil;
-import shop.alien.store.util.ali.AliApi;
 import shop.alien.util.ali.AliOSSUtil;
 import shop.alien.util.common.DistanceUtil;
 import shop.alien.util.common.constant.CouponStatusEnum;
@@ -914,10 +911,13 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 storeInfo.setFoodLicenceStatus(2);
                 storeInfo.setUpdateFoodLicenceTime(new Date());
             }
-        } else if (storeInfoDto.getFoodLicenceExpirationTime() != null) {
-            // 没有食品经营许可证URL,但有传入到期时间时直接使用
-            storeInfo.setFoodLicenceExpirationTime(storeInfoDto.getFoodLicenceExpirationTime());
-            log.info("无食品经营许可证URL,使用DTO中的到期时间:{}", storeInfoDto.getFoodLicenceExpirationTime());
+        } else {
+            // 没有食品经营许可证URL,初始化状态为"未提交"(字典值0)
+            storeInfo.setFoodLicenceStatus(0);
+            if (storeInfoDto.getFoodLicenceExpirationTime() != null) {
+                storeInfo.setFoodLicenceExpirationTime(storeInfoDto.getFoodLicenceExpirationTime());
+                log.info("无食品经营许可证URL,使用DTO中的到期时间:{}", storeInfoDto.getFoodLicenceExpirationTime());
+            }
         }
 
         // 处理娱乐经营许可证OCR数据(复用营业执照OCR类型,数据库中ocr_type存的是BUSINESS_LICENSE)
@@ -965,13 +965,78 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 storeInfo.setEntertainmentLicenceStatus(2);
                 storeInfo.setUpdateEntertainmentLicenceTime(new Date());
             }
-        } else if (storeInfoDto.getEntertainmentLicenceExpirationTime() != null) {
-            // 没有娱乐经营许可证URL,但有传入到期时间时直接使用
-            storeInfo.setEntertainmentLicenceExpirationTime(storeInfoDto.getEntertainmentLicenceExpirationTime());
-            log.info("无娱乐经营许可证URL,使用DTO中的到期时间:{}", storeInfoDto.getEntertainmentLicenceExpirationTime());
+        } else {
+            // 没有娱乐经营许可证URL,初始化状态为"未提交"(字典值0)
+            storeInfo.setEntertainmentLicenceStatus(0);
+            if (storeInfoDto.getEntertainmentLicenceExpirationTime() != null) {
+                storeInfo.setEntertainmentLicenceExpirationTime(storeInfoDto.getEntertainmentLicenceExpirationTime());
+                log.info("无娱乐经营许可证URL,使用DTO中的到期时间:{}", storeInfoDto.getEntertainmentLicenceExpirationTime());
+            }
         }
 
-        // 计算并设置到期时间为三个过期时间的最小值
+        // 处理营业执照OCR数据(复用营业执照OCR类型,数据库中ocr_type存的是BUSINESS_LICENSE)
+        // 营业执照使用 businessLicenseAddress 列表中的第一个URL查询OCR
+        String businessLicenseOcrUrl = null;
+        if (!CollectionUtils.isEmpty(storeInfoDto.getBusinessLicenseAddress())) {
+            businessLicenseOcrUrl = storeInfoDto.getBusinessLicenseAddress().get(0);
+        } else if (StringUtils.isNotEmpty(storeInfoDto.getBusinessLicenseUrl())) {
+            businessLicenseOcrUrl = storeInfoDto.getBusinessLicenseUrl();
+        }
+
+        if (StringUtils.isNotEmpty(businessLicenseOcrUrl)) {
+            // 查询营业执照OCR识别记录
+            OcrImageUpload businessLicenseOcr = ocrImageUploadMapper.selectOne(
+                    new LambdaQueryWrapper<OcrImageUpload>()
+                            .eq(OcrImageUpload::getImageUrl, businessLicenseOcrUrl)
+                            .eq(OcrImageUpload::getOcrType, OcrTypeEnum.BUSINESS_LICENSE.getCode())
+                            .orderByDesc(OcrImageUpload::getCreateTime)
+                            .last("limit 1")
+            );
+            if (businessLicenseOcr != null && StringUtils.isNotEmpty(businessLicenseOcr.getOcrResult())) {
+                try {
+                    com.alibaba.fastjson2.JSONObject ocrResult = com.alibaba.fastjson2.JSONObject.parseObject(businessLicenseOcr.getOcrResult());
+                    // 营业执照OCR字段:validToDate(优先,格式"20241217")、validPeriod(格式"2020年09月04日至2022年09月03日")
+                    Date expirationTime = parseBusinessLicenseExpirationDate(ocrResult);
+                    if (expirationTime != null) {
+                        storeInfo.setBusinessLicenseExpirationTime(expirationTime);
+                    } else if (storeInfoDto.getBusinessLicenseExpirationTime() != null) {
+                        // OCR解析结果为空时,使用DTO中传入的值
+                        storeInfo.setBusinessLicenseExpirationTime(storeInfoDto.getBusinessLicenseExpirationTime());
+                        log.info("使用DTO中的营业执照到期时间:{}", storeInfoDto.getBusinessLicenseExpirationTime());
+                    }
+                    // 设置营业执照状态为"待审核"(字典值2)
+                    storeInfo.setBusinessLicenseStatus(2);
+                    storeInfo.setUpdateBusinessLicenseTime(new Date());
+                    log.info("营业执照OCR数据解析成功,到期时间:{}", storeInfo.getBusinessLicenseExpirationTime());
+                } catch (Exception e) {
+                    log.error("解析营业执照OCR数据失败", e);
+                    // 解析失败时使用DTO中传入的值
+                    if (storeInfoDto.getBusinessLicenseExpirationTime() != null) {
+                        storeInfo.setBusinessLicenseExpirationTime(storeInfoDto.getBusinessLicenseExpirationTime());
+                        log.info("OCR解析失败,使用DTO中的营业执照到期时间:{}", storeInfoDto.getBusinessLicenseExpirationTime());
+                    }
+                    storeInfo.setBusinessLicenseStatus(2);
+                    storeInfo.setUpdateBusinessLicenseTime(new Date());
+                }
+            } else {
+                // 没有OCR记录时,使用DTO中传入的值
+                if (storeInfoDto.getBusinessLicenseExpirationTime() != null) {
+                    storeInfo.setBusinessLicenseExpirationTime(storeInfoDto.getBusinessLicenseExpirationTime());
+                    log.info("无OCR记录,使用DTO中的营业执照到期时间:{}", storeInfoDto.getBusinessLicenseExpirationTime());
+                }
+                storeInfo.setBusinessLicenseStatus(2);
+                storeInfo.setUpdateBusinessLicenseTime(new Date());
+            }
+        } else {
+            // 没有营业执照URL,初始化状态为"未提交"(字典值0)
+            storeInfo.setBusinessLicenseStatus(0);
+            if (storeInfoDto.getBusinessLicenseExpirationTime() != null) {
+                storeInfo.setBusinessLicenseExpirationTime(storeInfoDto.getBusinessLicenseExpirationTime());
+                log.info("无营业执照URL,使用DTO中的到期时间:{}", storeInfoDto.getBusinessLicenseExpirationTime());
+            }
+        }
+
+        // 计算并设置到期时间为五个过期时间的最小值(包含身份证过期时间)
         List<Date> expirationTimeList = new ArrayList<>();
         // 收集所有非空的过期时间
         if (storeInfoDto.getExpirationTime() != null) {
@@ -983,11 +1048,15 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         if (storeInfo.getEntertainmentLicenceExpirationTime() != null) {
             expirationTimeList.add(storeInfo.getEntertainmentLicenceExpirationTime());
         }
+        if (storeInfo.getBusinessLicenseExpirationTime() != null) {
+            expirationTimeList.add(storeInfo.getBusinessLicenseExpirationTime());
+        }
+        // 注意:身份证过期时间在saveIdCardImages方法中设置,这里先不包含,后续会在saveIdCardImages后更新
         // 取最小值设置为门店到期时间
         if (!expirationTimeList.isEmpty()) {
             Date minExpirationTime = Collections.min(expirationTimeList);
             storeInfo.setExpirationTime(minExpirationTime);
-            log.info("设置门店到期时间为三个过期时间的最小值:{}", minExpirationTime);
+            log.info("设置门店到期时间为过期时间的最小值:{}", minExpirationTime);
         }
 
         storeInfoMapper.insert(storeInfo);
@@ -1054,6 +1123,23 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             storeImgMapper.insert(storeImg);
         }
 
+        //存入身份证正反面图片
+        saveIdCardImages(storeInfo.getId(), storeUser.getId().toString(), storeInfo);
+
+        // 更新门店到期时间,包含身份证过期时间
+        if (storeInfo.getIdCardExpirationTime() != null) {
+            List<Date> allExpirationTimeList = new ArrayList<>();
+            if (storeInfo.getExpirationTime() != null) {
+                allExpirationTimeList.add(storeInfo.getExpirationTime());
+            }
+            allExpirationTimeList.add(storeInfo.getIdCardExpirationTime());
+            Date minExpirationTime = Collections.min(allExpirationTimeList);
+            storeInfo.setExpirationTime(minExpirationTime);
+            // 更新数据库中的过期时间
+            storeInfoMapper.updateById(storeInfo);
+            log.info("更新门店到期时间,包含身份证过期时间,最小值:{}", minExpirationTime);
+        }
+
         //初始化标签数据
         LambdaQueryWrapper<TagStoreRelation> tagStoreRelationLambdaQueryWrapper = new LambdaQueryWrapper<>();
         tagStoreRelationLambdaQueryWrapper.eq(TagStoreRelation::getStoreId, storeInfo.getId());
@@ -1336,8 +1422,12 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
 
     @Override
     public List<StoreDictionaryVo> getBusinessSectionTypes(String parentId) {
-        StoreDictionary businessSection = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getTypeName, "business_section").eq(StoreDictionary::getDictId, parentId));
-        List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getParentId, businessSection.getId()));
+        StoreDictionary businessSection = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>()
+                .eq(StoreDictionary::getTypeName, "business_section")
+                .eq(StoreDictionary::getDictId, parentId)
+                .isNull(StoreDictionary::getParentId));
+        List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>()
+                .eq(StoreDictionary::getParentId, businessSection.getId()));
         List<StoreDictionaryVo> voList = new ArrayList<>();
         for (StoreDictionary storeDictionary : storeDictionaries) {
             StoreDictionaryVo vo = new StoreDictionaryVo();
@@ -1950,7 +2040,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             List<String> storeIds = Arrays.asList(storeId.split(","));
             if (StringUtils.isNotEmpty(startTime) && StringUtils.isNotEmpty(endTime)) {
                 List<StoreBusinessInfo> storeBusinessInfos = storeBusinessInfoMapper.selectList(new LambdaQueryWrapper<StoreBusinessInfo>().in(StoreBusinessInfo::getStoreId, storeIds));
-                DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendValue(ChronoField.HOUR_OF_DAY, 1, 2, java.time.format.SignStyle.NOT_NEGATIVE).appendLiteral(':').appendValue(ChronoField.MINUTE_OF_HOUR, 2).toFormatter();
+                DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendValue(ChronoField.HOUR_OF_DAY, 1, 2, SignStyle.NOT_NEGATIVE).appendLiteral(':').appendValue(ChronoField.MINUTE_OF_HOUR, 2).toFormatter();
                 List<StoreBusinessInfo> list = storeBusinessInfos.stream().filter(item -> {
                     // 商家开门时间
                     LocalTime timeStart = LocalTime.parse(item.getEndTime(), formatter);
@@ -2030,7 +2120,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             List<String> storeIds = Arrays.asList(storeId.split(","));
             if (StringUtils.isNotEmpty(startTime) && StringUtils.isNotEmpty(endTime)) {
                 List<StoreBusinessInfo> storeBusinessInfos = storeBusinessInfoMapper.selectList(new LambdaQueryWrapper<StoreBusinessInfo>().in(StoreBusinessInfo::getStoreId, storeIds));
-                DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendValue(ChronoField.HOUR_OF_DAY, 1, 2, java.time.format.SignStyle.NOT_NEGATIVE).appendLiteral(':').appendValue(ChronoField.MINUTE_OF_HOUR, 2).toFormatter();
+                DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendValue(ChronoField.HOUR_OF_DAY, 1, 2, SignStyle.NOT_NEGATIVE).appendLiteral(':').appendValue(ChronoField.MINUTE_OF_HOUR, 2).toFormatter();
                 List<StoreBusinessInfo> list = storeBusinessInfos.stream().filter(item -> {
                     // 商家开门时间
                     LocalTime timeStart = LocalTime.parse(item.getEndTime(), formatter);
@@ -2973,17 +3063,17 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 .eq(StoreOperationalActivity::getDeleteFlag, 0)
                 .eq(StoreOperationalActivity::getStatus, 5);
         List<StoreOperationalActivity> activities = storeOperationalActivityMapper.selectList(activityWrapper);
-        
+
         // 如果没有活动,返回空列表
         if (CollectionUtils.isEmpty(activities)) {
             return new ArrayList<>();
         }
-        
+
         // 获取活动ID列表
         List<Integer> activityIds = activities.stream()
                 .map(StoreOperationalActivity::getId)
                 .collect(Collectors.toList());
-        
+
         // 查询与活动关联的图片
         LambdaQueryWrapper<StoreImg> queryWrapper = new LambdaQueryWrapper<StoreImg>()
                 .eq(StoreImg::getStoreId, Integer.parseInt(storeId))
@@ -3110,7 +3200,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             // KTV=3、洗浴汗蒸=4、按摩足浴=5,酒吧的数值未知(由调用方传入)
             queryWrapper.eq("a.business_section", businessType);
         } else {
-            // 如果没有指定businessType,则查询所有种类型的店铺
+            // 如果没有指定businessType,则查询所有种类型的店铺
             // 需要查询字典表获取所有四种类型的dictId
             List<String> storeTypeNames = Arrays.asList("丽人美发", "运动健身");
             List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(
@@ -3146,7 +3236,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         // 过滤永久关门的店铺
         queryWrapper.ne("a.business_status", 99);
         // 过滤过期的经营许可证
-        queryWrapper.ge("a.entertainment_licence_expiration_time", new Date());
+//        queryWrapper.ge("a.entertainment_licence_expiration_time", new Date());
         // 添加商家名称模糊查询
         if (StringUtils.isNotEmpty(storeName)) {
             queryWrapper.like("a.store_name", storeName);
@@ -3218,7 +3308,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             LambdaQueryWrapper<LifeUserOrder> orderWrapper = new LambdaQueryWrapper<>();
             orderWrapper.in(LifeUserOrder::getStoreId, storeIds)
                     .and(w -> w.and(w1 -> w1.eq(LifeUserOrder::getStatus, 7)
-                                    .ge(LifeUserOrder::getFinishTime, sevenDaysAgoStr))
+                            .ge(LifeUserOrder::getFinishTime, sevenDaysAgoStr))
                             .or(w2 -> w2.eq(LifeUserOrder::getStatus, 1)
                                     .ge(LifeUserOrder::getPayTime, sevenDaysAgoStr)))
                     .eq(LifeUserOrder::getDeleteFlag, 0);
@@ -3349,7 +3439,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                     store.setDistance(storeDistance);
                     // 使用反射或扩展字段存储finalScore,这里我们使用一个临时字段
                     // 由于StoreInfoVo没有finalScore字段,我们使用distance字段临时存储,排序后再恢复
-                    return new Object[]{store, finalScore};
+                    return new Object[] { store, finalScore };
                 })
                 .filter(item -> {
                     // 距离优先模式:过滤掉超出范围的
@@ -3946,7 +4036,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         // 过滤永久关门的店铺
         queryWrapper.ne("a.business_status", 99);
         // 过滤过期的经营许可证
-        queryWrapper.ge("a.entertainment_licence_expiration_time", new Date());
+//        queryWrapper.ge("a.entertainment_licence_expiration_time", new Date());
         // 添加一个模糊查询,根据店铺名称进行查询
         if (StringUtils.isNotEmpty(storeName)) {
             queryWrapper.like("a.store_name", storeName);
@@ -4649,6 +4739,42 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         return null;
     }
 
+
+    /**
+     * 解析营业执照OCR结果中的到期时间(复用营业执照OCR类型)
+     * 营业执照OCR返回字段:validToDate(格式"20241217"或空)、validPeriod(可能为空)
+     * 注意:营业执照的validToDate可能为空,表示长期有效
+     *
+     * @param ocrResult OCR识别结果JSON对象
+     * @return 到期日期,如果解析失败或为长期有效则返回null
+     */
+    private Date parseBusinessLicenseExpirationDate(com.alibaba.fastjson2.JSONObject ocrResult) {
+        if (ocrResult == null) {
+            return null;
+        }
+
+        // 优先使用validToDate字段(格式为yyyyMMdd,如"20241217",可能为空表示长期有效)
+        String validToDate = ocrResult.getString("validToDate");
+        if (StringUtils.isNotEmpty(validToDate)) {
+            Date date = parseDateString(validToDate);
+            if (date != null) {
+                return date;
+            }
+        }
+
+        // 其次使用validPeriod字段(格式为"2020年09月04日至2022年09月03日",可能为空)
+        String validPeriod = ocrResult.getString("validPeriod");
+        if (StringUtils.isNotEmpty(validPeriod)) {
+            Date date = parseValidPeriodEndDate(validPeriod);
+            if (date != null) {
+                return date;
+            }
+        }
+
+        log.info("营业执照OCR结果中未找到有效的到期时间,validToDate={},validPeriod={},可能为长期有效", validToDate, validPeriod);
+        return null;
+    }
+
     /**
      * 解析日期字符串
      * 支持的格式:yyyyMMdd、yyyy-MM-dd、yyyy/MM/dd、yyyy年MM月dd日
@@ -4722,6 +4848,157 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         return parseDateString(endDateStr);
     }
 
+    /**
+     * 保存身份证正反面图片到store_img表,并解析OCR结果设置过期时间和状态
+     *
+     * @param storeId   门店ID
+     * @param storeUserId 店铺用户ID
+     * @param storeInfo 门店信息对象(用于设置身份证状态和过期时间)
+     */
+    private void saveIdCardImages(Integer storeId, String storeUserId, StoreInfo storeInfo) {
+        try {
+            // 查询身份证OCR识别记录(根据storeUserId和ocrType=ID_CARD)
+            List<OcrImageUpload> idCardOcrList = ocrImageUploadMapper.selectList(
+                    new LambdaQueryWrapper<OcrImageUpload>()
+                            .eq(OcrImageUpload::getStoreUserId, storeUserId)
+                            .eq(OcrImageUpload::getOcrType, OcrTypeEnum.ID_CARD.getCode())
+                            .orderByDesc(OcrImageUpload::getCreateTime)
+            );
+
+            if (CollectionUtils.isEmpty(idCardOcrList)) {
+                log.info("未找到身份证OCR记录,storeUserId={}", storeUserId);
+                // 没有OCR记录时,初始化状态为"未提交"(字典值0)
+                storeInfo.setIdCardStatus(0);
+                return;
+            }
+
+            String idCardFrontUrl = null;
+            String idCardBackUrl = null;
+            Date idCardExpirationTime = null;
+
+            // 遍历OCR记录,区分正面和反面
+            for (OcrImageUpload ocrRecord : idCardOcrList) {
+                if (StringUtils.isEmpty(ocrRecord.getOcrResult())) {
+                    continue;
+                }
+
+                try {
+                    // 解析OCR结果JSON
+                    com.alibaba.fastjson2.JSONObject ocrResultJson = com.alibaba.fastjson2.JSONObject.parseObject(ocrRecord.getOcrResult());
+
+                    // 检查是正面还是反面
+                    if (ocrResultJson.containsKey("face")) {
+                        // 身份证正面
+                        idCardFrontUrl = ocrRecord.getImageUrl();
+                        log.info("找到身份证正面图片,URL={}", idCardFrontUrl);
+                    } else if (ocrResultJson.containsKey("back")) {
+                        // 身份证反面
+                        idCardBackUrl = ocrRecord.getImageUrl();
+                        log.info("找到身份证反面图片,URL={}", idCardBackUrl);
+
+                        // 从反面OCR结果中提取有效期限
+                        com.alibaba.fastjson2.JSONObject backData = ocrResultJson.getJSONObject("back");
+                        if (backData != null) {
+                            com.alibaba.fastjson2.JSONObject data = backData.getJSONObject("data");
+                            if (data != null) {
+                                String validPeriod = data.getString("validPeriod");
+                                if (StringUtils.isNotEmpty(validPeriod)) {
+                                    idCardExpirationTime = parseIdCardExpirationDate(validPeriod);
+                                    if (idCardExpirationTime != null) {
+                                        log.info("解析身份证有效期限成功,过期时间:{}", idCardExpirationTime);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("解析身份证OCR结果失败,ocrRecordId={}", ocrRecord.getId(), e);
+                }
+            }
+
+            // 保存身份证正面图片到store_img表(img_type=33)
+            if (StringUtils.isNotEmpty(idCardFrontUrl)) {
+                StoreImg frontImg = new StoreImg();
+                frontImg.setStoreId(storeId);
+                frontImg.setImgType(33);
+                frontImg.setImgSort(1);
+                frontImg.setImgDescription("身份证正面");
+                frontImg.setImgUrl(idCardFrontUrl);
+                storeImgMapper.insert(frontImg);
+                log.info("保存身份证正面图片成功,storeId={}, imgUrl={}", storeId, idCardFrontUrl);
+            }
+
+            // 保存身份证反面图片到store_img表(img_type=34)
+            if (StringUtils.isNotEmpty(idCardBackUrl)) {
+                StoreImg backImg = new StoreImg();
+                backImg.setStoreId(storeId);
+                backImg.setImgType(34);
+                backImg.setImgSort(2);
+                backImg.setImgDescription("身份证反面");
+                backImg.setImgUrl(idCardBackUrl);
+                storeImgMapper.insert(backImg);
+                log.info("保存身份证反面图片成功,storeId={}, imgUrl={}", storeId, idCardBackUrl);
+            }
+
+            // 设置身份证状态和过期时间
+            if (StringUtils.isNotEmpty(idCardFrontUrl) || StringUtils.isNotEmpty(idCardBackUrl)) {
+                // 有身份证图片时,设置状态为"待审核"(字典值2,使用foodLicenceStatus字典)
+                storeInfo.setIdCardStatus(2);
+                storeInfo.setUpdateIdCardTime(new Date());
+                if (idCardExpirationTime != null) {
+                    storeInfo.setIdCardExpirationTime(idCardExpirationTime);
+                }
+                log.info("设置身份证状态为待审核,过期时间:{}", idCardExpirationTime);
+            } else {
+                // 没有身份证图片时,初始化状态为"未提交"(字典值0)
+                storeInfo.setIdCardStatus(0);
+            }
+
+        } catch (Exception e) {
+            log.error("保存身份证图片失败,storeId={}, storeUserId={}", storeId, storeUserId, e);
+            // 发生异常时,初始化状态为"未提交"(字典值0)
+            storeInfo.setIdCardStatus(0);
+        }
+    }
+
+    /**
+     * 解析身份证有效期限
+     * 身份证OCR返回格式:validPeriod="2023.05.29-2033.05.29"
+     *
+     * @param validPeriod 有效期限字符串,格式:"2023.05.29-2033.05.29"
+     * @return 到期日期,如果解析失败则返回null
+     */
+    private Date parseIdCardExpirationDate(String validPeriod) {
+        if (StringUtils.isEmpty(validPeriod)) {
+            return null;
+        }
+
+        // 处理"长期"或"永久"情况
+        if (validPeriod.contains("长期") || validPeriod.contains("永久")) {
+            return null;
+        }
+
+        try {
+            // 身份证格式:2023.05.29-2033.05.29,提取结束日期
+            if (validPeriod.contains("-")) {
+                String[] parts = validPeriod.split("-");
+                if (parts.length >= 2) {
+                    String endDateStr = parts[1].trim();
+                    // 将 "2023.05.29" 格式转换为 "2023-05-29"
+                    endDateStr = endDateStr.replace(".", "-");
+                    return parseDateString(endDateStr);
+                }
+            }
+
+            // 如果格式不匹配,尝试直接解析
+            String normalizedDateStr = validPeriod.replace(".", "-");
+            return parseDateString(normalizedDateStr);
+        } catch (Exception e) {
+            log.warn("解析身份证有效期限失败,validPeriod={}", validPeriod, e);
+            return null;
+        }
+    }
+
     @Override
     public StoreInfoVo getClientStoreDetail(String storeId, String userId, String jingdu, String weidu) {
         StoreInfoVo result = new StoreInfoVo();

+ 275 - 0
alien-store/src/main/java/shop/alien/store/util/ai/AiFeedbackAssignUtils.java

@@ -0,0 +1,275 @@
+package shop.alien.store.util.ai;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.store.LifeFeedback;
+import shop.alien.store.feign.AlienAIFeign;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AiFeedbackAssignUtils {
+
+    private final RestTemplate restTemplate;
+
+    @Value("${ai.service.login-url}")
+    private String loginUrl;
+
+    @Value("${ai.aiAccount}")
+    private String userName;
+
+    @Value("${ai.aiPassword}")
+    private String passWord;
+
+    @Value("${ai.service.assign-staff-url}")
+    private String assignStaffUrl;
+
+    private  final AlienAIFeign alienAIFeign;
+
+    // 语音识别接口地址(从配置中读取,如果没有配置则使用默认值)
+    @Value("${feign.alienAI.url}")
+    private String aiServiceBaseUrl;
+
+    /**登录的语音识别文件登录
+     * 登录 AI 服务,获取 token
+     *
+     * @return accessToken
+     */
+    public String getSpeechRecognition1(MultipartFile file,String accessToken,String language,String use_itn,String merge_vad) {
+        log.info("调用AI语音识别接口...");
+        try {
+            // 使用 RestTemplate 直接发送 multipart/form-data 请求
+            MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
+            
+            // 添加音频文件
+            if (file != null && !file.isEmpty()) {
+                try {
+                    formData.add("file", new ByteArrayResource(file.getBytes()) {
+                        @Override
+                        public String getFilename() {
+                            String originalFilename = file.getOriginalFilename();
+                            return originalFilename != null ? originalFilename : "audio_file";
+                        }
+                    });
+                } catch (IOException e) {
+                    log.error("读取音频文件失败", e);
+                    throw new RuntimeException("读取音频文件失败", e);
+                }
+            } else {
+                log.warn("音频文件为空,无法进行语音识别");
+                throw new RuntimeException("音频文件不能为空");
+            }
+            
+            // 添加识别语言参数(默认 auto)
+            if (StringUtils.hasText(language)) {
+                formData.add("language", language);
+            } else {
+                formData.add("language", "auto");
+            }
+            
+            // 添加是否开启逆文本标准化参数(默认 true)
+            // 支持传入 "true"/"false" 字符串或 boolean 值
+            if (StringUtils.hasText(use_itn)) {
+                // 确保传递的是有效的 boolean 字符串值
+                String useItnValue = use_itn.trim().toLowerCase();
+                if ("true".equals(useItnValue) || "false".equals(useItnValue)) {
+                    formData.add("use_itn", useItnValue);
+                } else {
+                    formData.add("use_itn", "true"); // 默认值
+                }
+            } else {
+                formData.add("use_itn", "true"); // 默认值
+            }
+            
+            // 添加是否合并VAD参数(默认 true)
+            // 支持传入 "true"/"false" 字符串或 boolean 值
+            if (StringUtils.hasText(merge_vad)) {
+                // 确保传递的是有效的 boolean 字符串值
+                String mergeVadValue = merge_vad.trim().toLowerCase();
+                if ("true".equals(mergeVadValue) || "false".equals(mergeVadValue)) {
+                    formData.add("merge_vad", mergeVadValue);
+                } else {
+                    formData.add("merge_vad", "true"); // 默认值
+                }
+            } else {
+                formData.add("merge_vad", "true"); // 默认值
+            }
+
+            // 设置请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+            // 添加 Authorization header(如果需要)
+            if (StringUtils.hasText(accessToken)) {
+                headers.set("Authorization", "Bearer " + accessToken);
+            }
+
+            HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
+
+            // 构建完整的URL
+            String url = aiServiceBaseUrl + "/asr/upload";
+            log.info("调用AI语音识别接口,URL: {}", url);
+
+            // 使用 RestTemplate 发送请求
+            ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
+            
+            // 处理响应
+            if (response != null && response.getStatusCode() == HttpStatus.OK) {
+                String responseBody = response.getBody();
+                log.info("AI语音识别接口响应:{}", responseBody);
+                return responseBody;
+            } else {
+                log.error("AI语音识别接口调用失败,HTTP状态码: {}", 
+                        response != null ? response.getStatusCode() : "null");
+                throw new RuntimeException("AI语音识别接口调用失败,HTTP状态码: " + 
+                        (response != null ? response.getStatusCode() : "null"));
+            }
+        } catch (org.springframework.web.client.HttpClientErrorException e) {
+            log.error("调用AI语音识别接口失败,HTTP错误: {}, 响应体: {}", 
+                    e.getStatusCode(), e.getResponseBodyAsString(), e);
+            throw new RuntimeException("调用AI语音识别接口失败: " + e.getStatusCode() + 
+                    ", 响应: " + e.getResponseBodyAsString(), e);
+        } catch (Exception e) {
+            log.error("调用AI语音识别接口失败", e);
+            throw new RuntimeException("调用AI语音识别接口失败: " + e.getMessage(), e);
+        }
+    }
+
+    public String getAccessToken() {
+        log.info("登录AI服务获取token...{}", loginUrl);
+        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("username", userName);
+        formData.add("password", passWord);
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
+
+        ResponseEntity<String> response;
+        try {
+            log.info("请求AI服务登录接口===================>");
+            response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
+        } catch (Exception e) {
+            log.error("请求AI服务登录接口失败", e);
+            return null;
+        }
+
+        if (response != null && response.getStatusCode() == HttpStatus.OK) {
+            String body = response.getBody();
+            log.info("请求AI服务登录成功 postForEntity.getBody()\t{}", body);
+            if (StringUtils.hasText(body)) {
+                JSONObject jsonObject = JSONObject.parseObject(body);
+                if (jsonObject != null) {
+                    JSONObject dataJson = jsonObject.getJSONObject("data");
+                    if (dataJson != null) {
+                        return dataJson.getString("access_token");
+                    }
+                }
+            }
+            log.warn("AI服务登录响应解析失败 body: {}", body);
+            return null;
+        }
+
+        log.error("请求AI服务登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+        return null;
+    }
+
+    /**
+     * 调用AI接口分配跟踪人员
+     *
+     * @param feedback 反馈记录
+     * @param imageUrls 图片URL列表
+     * @param videoUrls 视频URL列表
+     * @return 分配的跟踪人员ID,失败返回null
+     */
+    public Integer assignStaffByAI(LifeFeedback feedback, List<String> imageUrls, List<String> videoUrls) {
+        try {
+            // 1. 获取访问令牌
+            String accessToken = getAccessToken();
+            if (accessToken == null) {
+                log.error("获取AI访问令牌失败,无法分配跟踪人员");
+                return null;
+            }
+
+            // 2. 构建请求参数
+            Map<String, Object> requestBody = new HashMap<>();
+            requestBody.put("feedback_id", feedback.getId());
+            requestBody.put("feedback_content", feedback.getContent());
+            requestBody.put("feedback_type", feedback.getFeedbackType());
+            requestBody.put("feedback_source", feedback.getFeedbackSource());
+            requestBody.put("feedback_way", feedback.getFeedbackWay());
+            requestBody.put("contact_way", feedback.getContactWay());
+            requestBody.put("user_id", feedback.getUserId());
+            if (imageUrls != null && !imageUrls.isEmpty()) {
+                requestBody.put("image_urls", imageUrls);
+            }
+            if (videoUrls != null && !videoUrls.isEmpty()) {
+                requestBody.put("video_urls", videoUrls);
+            }
+
+            // 3. 设置请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.set("Authorization", "Bearer " + accessToken);
+
+            HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
+
+            // 4. 调用AI接口
+            log.info("调用AI分配跟踪人员接口,feedbackId={}, url={}", feedback.getId(), assignStaffUrl);
+            ResponseEntity<String> response = restTemplate.postForEntity(assignStaffUrl, requestEntity, String.class);
+
+            // 5. 处理响应
+            if (response != null && response.getStatusCodeValue() == 200) {
+                String responseBody = response.getBody();
+                log.info("AI分配跟踪人员接口响应:{}", responseBody);
+
+                if (StringUtils.hasText(responseBody)) {
+                    JSONObject responseJson = JSONObject.parseObject(responseBody);
+                    Integer code = responseJson.getInteger("code");
+                    
+                    if (code != null && code == 200) {
+                        JSONObject data = responseJson.getJSONObject("data");
+                        if (data != null) {
+                            Integer staffId = data.getInteger("staff_id");
+                            if (staffId != null) {
+                                log.info("AI分配跟踪人员成功,feedbackId={}, staffId={}, staffName={}", 
+                                        feedback.getId(), staffId, data.getString("staff_name"));
+                                return staffId;
+                            }
+                        }
+                    } else {
+                        String message = responseJson.getString("message");
+                        log.error("AI分配跟踪人员失败,feedbackId={}, code={}, message={}", 
+                                feedback.getId(), code, message);
+                    }
+                }
+            } else {
+                log.error("AI分配跟踪人员接口调用失败,feedbackId={}, http状态={}", 
+                        feedback.getId(), response != null ? response.getStatusCode() : null);
+            }
+
+            return null;
+        } catch (org.springframework.web.client.HttpServerErrorException.ServiceUnavailable e) {
+            log.error("AI分配跟踪人员接口返回503 Service Unavailable错误: {}", e.getResponseBodyAsString());
+            return null;
+        } catch (Exception e) {
+            log.error("调用AI分配跟踪人员接口异常,feedbackId={}", feedback.getId(), e);
+            return null;
+        }
+    }
+}

+ 6 - 0
pom.xml

@@ -79,6 +79,12 @@
             </dependency>
 
             <dependency>
+                <groupId>org.redisson</groupId>
+                <artifactId>redisson-spring-boot-starter</artifactId>
+                <version>3.17.7</version>
+            </dependency>
+
+            <dependency>
                 <groupId>com.alibaba</groupId>
                 <artifactId>fastjson</artifactId>
                 <version>2.0.7</version>