11 İşlemeler 1c1260ce56 ... 49708afbd6

Yazar SHA1 Mesaj Tarih
  fcw 49708afbd6 feat(store): 新增AI视频审核任务实体及Mapper 1 hafta önce
  fcw c14aa5ca04 fix(store): 修复差评申诉置信度分析状态获取错误 1 hafta önce
  fcw 66a2cb889c fix(store): 处理差评申述置信度分析状态检查 1 hafta önce
  fcw 5286d30364 chore(config): 更新AI服务地址配置 1 hafta önce
  fcw 16eedd5062 feat(job): 优化AI用户违规检查任务逻辑 1 hafta önce
  fcw f4ae954134 feat(ai): 集成AI审核任务功能 1 hafta önce
  fcw faa922e494 feat(entity): 添加AI任务ID字段 1 hafta önce
  fcw 5c2af672e0 feat(store): 实现验证码持久化存储功能 2 hafta önce
  fcw ed95468d2b feat(store): 新增短信验证码管理功能 2 hafta önce
  fcw d0e4e9874a feat(store): 实现差评申诉分析与处理功能 2 hafta önce
  fcw fc20652ae2 feat(store): 添加AI接口返回记录ID字段 3 hafta önce
27 değiştirilmiş dosya ile 2433 ekleme ve 85 silme
  1. 4 0
      alien-entity/src/main/java/shop/alien/entity/second/SecondGoods.java
  2. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/LifeUserViolation.java
  3. 66 0
      alien-entity/src/main/java/shop/alien/entity/store/SecondAiTask.java
  4. 7 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreCommentAppeal.java
  5. 73 0
      alien-entity/src/main/java/shop/alien/entity/store/StoreVerificationCode.java
  6. 12 0
      alien-entity/src/main/java/shop/alien/mapper/SecondAiTaskMapper.java
  7. 28 0
      alien-entity/src/main/java/shop/alien/mapper/StoreCommentAppealMapper.java
  8. 13 0
      alien-entity/src/main/java/shop/alien/mapper/StoreVerificationCodeMapper.java
  9. 20 0
      alien-entity/src/main/resources/mapper/StoreVerificationCodeMapper.xml
  10. 1 1
      alien-gateway/src/main/resources/bootstrap.yml
  11. 4 0
      alien-job/src/main/java/shop/alien/job/feign/SecondGoodsFeign.java
  12. 183 0
      alien-job/src/main/java/shop/alien/job/second/AiUserViolationJob.java
  13. 190 0
      alien-job/src/main/java/shop/alien/job/second/goodsCheckJob.java
  14. 419 0
      alien-job/src/main/java/shop/alien/job/store/BadReviewAppealJob.java
  15. 1 1
      alien-job/src/main/resources/bootstrap.yml
  16. 84 0
      alien-second/src/main/java/shop/alien/second/service/SecondGoodsAuditService.java
  17. 545 0
      alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsAuditServiceImpl.java
  18. 146 0
      alien-second/src/main/java/shop/alien/second/util/AiTaskUtils.java
  19. 1 1
      alien-second/src/main/resources/bootstrap.yml
  20. 85 0
      alien-store/src/main/java/shop/alien/store/controller/StoreVerificationCodeController.java
  21. 49 0
      alien-store/src/main/java/shop/alien/store/controller/UserStoreController.java
  22. 36 0
      alien-store/src/main/java/shop/alien/store/service/StoreVerificationCodeService.java
  23. 100 81
      alien-store/src/main/java/shop/alien/store/service/impl/LifeUserViolationServiceImpl.java
  24. 71 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreVerificationCodeServiceImpl.java
  25. 264 0
      alien-store/src/main/java/shop/alien/store/util/AiUserViolationUtils.java
  26. 26 0
      alien-store/src/main/java/shop/alien/store/util/ali/AliSms.java
  27. 1 1
      alien-store/src/main/resources/bootstrap.yml

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/second/SecondGoods.java

@@ -112,6 +112,10 @@ public class SecondGoods implements Serializable {
     @ApiModelProperty(value = "审核失败原因")
     private String failedReason;
 
+    @TableField("ai_task_id")
+    @ApiModelProperty(value = "ai_task_id")
+    private String aiTaskId;
+
     @TableField(value = "created_time", fill = FieldFill.INSERT)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @ApiModelProperty(value = "创建时间")

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

@@ -131,6 +131,10 @@ public class LifeUserViolation extends Model<LifeUserViolation> {
     @TableField(value = "goods_id")
     private Integer goodsId;
 
+    @ApiModelProperty(value = "商品ID")
+    @TableField(value = "ai_task_id")
+    private String aiTaskId;
+
     @Override
     protected Serializable pkVal() {
         return this.id;

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

@@ -0,0 +1,66 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+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;
+
+/**
+ * second_ai_task 表实体
+ */
+@Data
+@TableName("second_ai_task")
+@ApiModel(value = "SecondAiTask", description = "AI视频审核任务表")
+public class SecondAiTask implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("主键ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty("数据ID")
+    @TableField("data_id")
+    private String dataId;
+
+    @ApiModelProperty("视频URL")
+    @TableField("video_url")
+    private String videoUrl;
+
+    @ApiModelProperty("任务ID")
+    @TableField("task_id")
+    private String taskId;
+
+    @ApiModelProperty("任务状态 SUBMITTED/PROCESSING/...")
+    @TableField("status")
+    private String status;
+
+    @ApiModelProperty("风险级别 none/low/medium/high")
+    @TableField("risk_level")
+    private String riskLevel;
+
+    @ApiModelProperty("审核结果(JSON)")
+    @TableField("result")
+    private String result;
+
+    @ApiModelProperty("重试次数")
+    @TableField("retry_count")
+    private Integer retryCount;
+
+    @ApiModelProperty("创建时间")
+    @TableField("create_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty("更新时间")
+    @TableField("update_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+}

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

@@ -74,4 +74,11 @@ public class StoreCommentAppeal extends Model<StoreCommentAppeal> {
     @TableField("updated_user_id")
     private Integer updatedUserId;
 
+    @ApiModelProperty(value = "AI接口返回的ID")
+    @TableField("record_id")
+    private Integer recordId;
+
+    @ApiModelProperty(value = "申诉ai审批过程")
+    @TableField("appeal_ai_approval")
+    private String appealAiApproval;
 }

+ 73 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreVerificationCode.java

@@ -0,0 +1,73 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 短信验证码记录
+ */
+@Data
+@TableName("store_verification_code")
+public class StoreVerificationCode extends Model<StoreVerificationCode> {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("主键id")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty("业务类型 (0:登录,1:修改密码,2:注册,3:修改手机号,4:注销店铺,5:注销账号,6:忘记密码)")
+    @TableField("business_type")
+    private Integer businessType;
+
+    @ApiModelProperty("手机号")
+    @TableField("phone")
+    private String phone;
+
+    @ApiModelProperty("端区分(0:用户,1:商家,2:律师端)")
+    @TableField("app_type")
+    private Integer appType;
+
+    @ApiModelProperty("验证码")
+    @TableField("code")
+    private String code;
+
+    @ApiModelProperty("创建时间")
+    @TableField("created_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty("更新时间")
+    @TableField("updated_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty("创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty("更新人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+
+    @ApiModelProperty("删除标志(0:正常,1:删除)")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @Override
+    protected Serializable pkVal() {
+        return this.id;
+    }
+}
+

+ 12 - 0
alien-entity/src/main/java/shop/alien/mapper/SecondAiTaskMapper.java

@@ -0,0 +1,12 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.SecondAiTask;
+
+/**
+ * second_ai_task Mapper
+ */
+@Mapper
+public interface SecondAiTaskMapper extends BaseMapper<SecondAiTask> {
+}

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

+ 13 - 0
alien-entity/src/main/java/shop/alien/mapper/StoreVerificationCodeMapper.java

@@ -0,0 +1,13 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.StoreVerificationCode;
+
+/**
+ * 短信验证码 Mapper
+ */
+@Mapper
+public interface StoreVerificationCodeMapper extends BaseMapper<StoreVerificationCode> {
+}
+

+ 20 - 0
alien-entity/src/main/resources/mapper/StoreVerificationCodeMapper.xml

@@ -0,0 +1,20 @@
+<?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.StoreVerificationCodeMapper">
+
+    <resultMap id="BaseResultMap" type="shop.alien.entity.store.StoreVerificationCode">
+        <id column="id" property="id"/>
+        <result column="business_type" property="businessType"/>
+        <result column="phone" property="phone"/>
+        <result column="app_type" property="appType"/>
+        <result column="code" property="code"/>
+        <result column="created_time" property="createdTime"/>
+        <result column="updated_time" property="updatedTime"/>
+        <result column="created_user_id" property="createdUserId"/>
+        <result column="updated_user_id" property="updatedUserId"/>
+        <result column="delete_flag" property="deleteFlag"/>
+    </resultMap>
+
+</mapper>
+

+ 1 - 1
alien-gateway/src/main/resources/bootstrap.yml

@@ -1,3 +1,3 @@
 spring:
   profiles:
-    active: dev
+    active: test

+ 4 - 0
alien-job/src/main/java/shop/alien/job/feign/SecondGoodsFeign.java

@@ -3,6 +3,7 @@ package shop.alien.job.feign;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestParam;
+import shop.alien.entity.second.SecondGoods;
 
 @FeignClient(name = "alien-second", url = "${feign.alienSecond.url}")
 public interface SecondGoodsFeign {
@@ -24,4 +25,7 @@ public interface SecondGoodsFeign {
      */
     @GetMapping("/video/moderation/processTask")
     boolean processTask(@RequestParam("taskId") String taskId);
+
+    @GetMapping("/secondGoods/approveAndListGoods")
+    boolean approveAndListGoods(SecondGoods goods);
 }

+ 183 - 0
alien-job/src/main/java/shop/alien/job/second/AiUserViolationJob.java

@@ -0,0 +1,183 @@
+package shop.alien.job.second;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.xxl.job.core.handler.annotation.XxlJob;
+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.http.client.ClientHttpRequestInterceptor;
+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.second.SecondGoods;
+import shop.alien.entity.store.LifeUserViolation;
+import shop.alien.entity.store.SecondAiTask;
+import shop.alien.mapper.LifeUserViolationMapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AiUserViolationJob {
+
+    private final RestTemplate restTemplate;
+    private final LifeUserViolationMapper lifeUserViolationMapper;
+
+    @Value("${third-party-user-name.base-url}")
+    private String userName;
+
+    @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";
+
+    private String aiUserViolationCheckUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/user_complaint_record/result";
+
+    @XxlJob("getAiUserViolationResult")
+    public R<String> getAiUserViolationResult() {
+        String accessToken = fetchAiServiceToken();
+        if (!StringUtils.hasText(accessToken)) {
+            return R.fail("调用AI举报用户辅助系统 登录接口失败");
+        }
+        return getAiUserViolationCheck(accessToken);
+    }
+
+    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 R<String> getAiUserViolationCheck(String accessToken) {
+
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
+        // 查询所有状态为处理中的申诉
+        List<LifeUserViolation> pendingTasks = lifeUserViolationMapper.selectList(
+                new QueryWrapper<LifeUserViolation>()
+                        .eq("processing_status", "0")
+                        .isNotNull("ai_task_id")
+        );
+
+        // 循环调用查询结果接口
+        for (LifeUserViolation task : pendingTasks) {
+            String completedUrl = buildCompletedUrl(task.getAiTaskId());
+
+            ResponseEntity<String> analyzeResp;
+
+            RestTemplate restTemplateWithAuth = new RestTemplate();
+            List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
+            interceptors.add((request, body, execution) -> {
+                request.getHeaders().set("Authorization", "Bearer " + accessToken);
+                return execution.execute(request, body);
+            });
+            restTemplateWithAuth.setInterceptors(interceptors);
+
+            ResponseEntity<String> response = null;
+
+            try {
+                analyzeResp = restTemplateWithAuth.getForEntity(completedUrl, String.class);
+
+                if (analyzeResp != null && analyzeResp.getStatusCodeValue() == 200) {
+                    String analyzeBody = analyzeResp.getBody();
+                    log.info("AI举报用户审核提交成功, 返回: {}", analyzeBody);
+
+                    JSONObject analyzeJson = JSONObject.parseObject(analyzeBody);
+                    JSONObject dataJsonObj = analyzeJson.getJSONObject("data");
+
+                    if (dataJsonObj == null) {
+                        log.error("AI举报用户审核返回数据为空");
+                        R.fail("AI举报用户审核返回数据为空");
+                        continue;
+                    }
+
+                    // 获取task_id用于后续查询
+                    String taskId = dataJsonObj.getString("task_id");
+                    if (taskId == null) {
+                        log.error("AI举报用户审核返回task_id为空");
+                        R.fail("AI举报用户审核返回task_id为空");
+                        continue;
+                    }
+
+                    LifeUserViolation aiTask = new LifeUserViolation();
+                    aiTask.setAiTaskId(taskId);
+                    if (dataJsonObj.getString("status").equals("pending")) {
+                        R.fail("审核未结束");
+                        continue;
+                    }
+
+                    if (dataJsonObj.getString("status").equals("completed")) {
+                        if (dataJsonObj.getString("is_valid").equals("true")) {
+                            aiTask.setProcessingStatus("1");
+                            aiTask.setReportResult(dataJsonObj.getString("decision_reason"));
+                        } else {
+                            aiTask.setProcessingStatus("0");
+                            aiTask.setReportResult(dataJsonObj.getString("decision_reason"));
+                        }
+                    }
+
+                    QueryWrapper<LifeUserViolation> queryWrapper = new QueryWrapper<>();
+                    queryWrapper.eq("ai_task_id", taskId);
+                    LifeUserViolation lifeUserViolation = lifeUserViolationMapper.selectOne(queryWrapper);
+                    if (lifeUserViolation == null) {
+                        log.error("AI举报用户不存在");
+                        R.fail("AI举报用户不存在");
+                        continue;
+                    }
+                    lifeUserViolationMapper.update(aiTask, queryWrapper);
+
+                } else {
+                    if (analyzeResp != null) {
+                        log.error("调用AI举报用户审核接口失败, http状态: {}", analyzeResp.getStatusCode());
+                        R.fail("调用AI举报用户审核接口失败, http状态: " + analyzeResp.getStatusCode());
+                    }
+                }
+            } catch (Exception e) {
+                log.error("调用差评申述查询结果接口异常", e);
+            }
+        }
+        return R.success("调用AI举报用户审核结果接口完成");
+    }
+
+    private String buildCompletedUrl(String recordId) {
+        String baseUrl = aiUserViolationCheckUrl;
+        if (!StringUtils.hasText(baseUrl)) {
+            throw new IllegalStateException("ai举报用户接口地址未配置");
+        }
+        if (baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+        }
+        // 构建新的URL格式: /api/v1/audit_task/getResult?task_id={recordId}
+        return baseUrl + "?task_id=" + recordId;
+    }
+}

+ 190 - 0
alien-job/src/main/java/shop/alien/job/second/goodsCheckJob.java

@@ -0,0 +1,190 @@
+package shop.alien.job.second;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+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.http.client.ClientHttpRequestInterceptor;
+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.second.SecondGoods;
+import shop.alien.entity.store.SecondAiTask;
+import shop.alien.entity.store.StoreComment;
+import shop.alien.entity.store.StoreCommentAppeal;
+import shop.alien.job.feign.SecondGoodsFeign;
+import shop.alien.mapper.SecondAiTaskMapper;
+import shop.alien.mapper.second.SecondGoodsMapper;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class goodsCheckJob {
+
+    private final RestTemplate restTemplate;
+
+    private final SecondAiTaskMapper secondAiTaskMapper;
+    private final SecondGoodsMapper secondGoodsMapper;
+
+    private final SecondGoodsFeign secondGoodsFeign;
+
+    private String loginUrl = "http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login";
+
+    private String goodsCheckUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/audit_task/getResult";
+
+    @Value("${third-party-user-name.base-url}")
+    private String userName;
+
+    @Value("${third-party-pass-word.base-url}")
+    private String passWord;
+
+    @XxlJob("getAiGoodsCheckResult")
+    public R<String> getAiGoodsCheckResult() {
+        String accessToken = fetchAiServiceToken();
+        if (!StringUtils.hasText(accessToken)) {
+            return R.fail("调用差评申诉辅助系统 登录接口失败");
+        }
+        return getGoodsCheck(accessToken);
+    }
+
+
+    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 R<String> getGoodsCheck(String accessToken) {
+
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
+        // 查询所有状态为处理中的申诉
+        List<SecondAiTask> pendingTasks = secondAiTaskMapper.selectList(
+                new QueryWrapper<SecondAiTask>()
+                        .eq("status", "PROCESSING")
+        );
+
+        // 循环调用查询结果接口
+        for (SecondAiTask task : pendingTasks) {
+            String completedUrl = buildCompletedUrl(task.getTaskId());
+
+            ResponseEntity<String> analyzeResp;
+
+            RestTemplate restTemplateWithAuth = new RestTemplate();
+            List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
+            interceptors.add((request, body, execution) -> {
+                request.getHeaders().set("Authorization", "Bearer " + accessToken);
+                return execution.execute(request, body);
+            });
+            restTemplateWithAuth.setInterceptors(interceptors);
+
+            ResponseEntity<String> response = null;
+
+            try {
+                analyzeResp = restTemplateWithAuth.getForEntity(completedUrl, String.class);
+
+                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("商品审核返回数据为空");
+                        continue;
+                    }
+
+                    // 获取task_id用于后续查询
+                    String taskId = dataJsonObj.getString("task_id");
+                    if (taskId == null) {
+                        log.error("商品审核返回task_id为空");
+                        R.fail("商品审核返回task_id为空");
+                        continue;
+                    }
+
+                    SecondAiTask aiTask = new SecondAiTask();
+                    aiTask.setTaskId(taskId);
+                    if (dataJsonObj.getString("status").equals("pending")) {
+                        R.fail("审核未结束");
+                        continue;
+                    }
+
+                    if (dataJsonObj.getString("status").equals("done")) {
+                        aiTask.setStatus("SUCCESS");
+                    }
+
+                    aiTask.setResult(dataJsonObj.toJSONString());
+
+                    QueryWrapper<SecondGoods> queryWrapper = new QueryWrapper<>();
+                    queryWrapper.eq("ai_task_id", taskId);
+                    SecondGoods goods = secondGoodsMapper.selectOne(queryWrapper);
+                    if (goods == null) {
+                        log.error("商品不存在");
+                        R.fail("商品不存在");
+                        continue;
+                    }
+                    secondGoodsFeign.approveAndListGoods(goods);
+
+                } else {
+                    if (analyzeResp != null) {
+                        log.error("调用商品审核接口失败, http状态: {}", analyzeResp.getStatusCode());
+                        R.fail("调用商品审核接口失败, http状态: " + analyzeResp.getStatusCode());
+                    }
+                }
+            } catch (Exception e) {
+                log.error("调用差评申述查询结果接口异常", e);
+            }
+        }
+        return R.success("调用商品审核结果接口完成");
+    }
+
+
+    private String buildCompletedUrl(String recordId) {
+        String baseUrl = goodsCheckUrl;
+        if (!StringUtils.hasText(baseUrl)) {
+            throw new IllegalStateException("差评申述分析接口地址未配置");
+        }
+        if (baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+        }
+        // 构建新的URL格式: /api/v1/audit_task/getResult?task_id={recordId}
+        return baseUrl + "?task_id=" + recordId;
+    }
+}

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

@@ -0,0 +1,419 @@
+package shop.alien.job.store;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.xxl.job.core.context.XxlJobHelper;
+import com.xxl.job.core.handler.annotation.XxlJob;
+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.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+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.store.StoreComment;
+import shop.alien.entity.store.StoreCommentAppeal;
+import shop.alien.entity.result.R;
+import shop.alien.mapper.StoreCommentAppealMapper;
+import shop.alien.mapper.StoreCommentMapper;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 调用AI差评申诉辅助系统服务类
+ *
+ * @author fcw
+ * @date 2025/11/20 14:21
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class BadReviewAppealJob {
+
+    private final RestTemplate restTemplate;
+
+    private final StoreCommentAppealMapper storeCommentAppealMapper;
+
+    private final StoreCommentMapper storeCommentMapper;
+
+//    @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";
+
+
+
+    /**
+     * 差评申述置信度分析接口地址
+     * 例如: http://192.168.2.250:9004/api/v1/analyze 或通过网关: http://192.168.2.250:9000/ai/auto_review/api/v1/analyze
+     */
+//    @Value("${bad-review-analyze.base-url}")
+//    private String analyzeUrl;
+
+    /**
+     * 调用AI服务获取Token
+     */
+    @XxlJob("getBadReviewAppealJob")
+    public R<String> getBadReviewAppealJob() {
+        String accessToken = fetchAiServiceToken();
+        if (!StringUtils.hasText(accessToken)) {
+            return R.fail("调用差评申诉辅助系统登录任务执行失败 返回异常");
+        }
+        return invokeAnalyzeTask(accessToken);
+    }
+
+
+    @XxlJob("getAppealCompletedResult")
+    public R<String> getAppealCompletedResult() {
+        String accessToken = fetchAiServiceToken();
+        if (!StringUtils.hasText(accessToken)) {
+            return R.fail("调用差评申诉辅助系统 登录接口失败");
+        }
+        return getNegativeReviewAppealCompletedResult(accessToken);
+    }
+
+    private R<String> getNegativeReviewAppealCompletedResult(String accessToken) {
+
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
+        // 查询所有状态为处理中的申诉
+        List<StoreCommentAppeal> pendingAppeals = storeCommentAppealMapper.getPendingAppeals();
+        // 循环调用查询结果接口
+        for (StoreCommentAppeal appeal : pendingAppeals) {
+            String completedUrl = buildCompletedUrl(appeal.getRecordId());
+
+            ResponseEntity<String> analyzeResp;
+
+            RestTemplate restTemplateWithAuth = new RestTemplate();
+            List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
+            interceptors.add((request, body, execution) -> {
+                request.getHeaders().set("Authorization", "Bearer " + accessToken);
+                return execution.execute(request, body);
+            });
+            restTemplateWithAuth.setInterceptors(interceptors);
+
+            ResponseEntity<String> response = null;
+
+            try {
+                analyzeResp = restTemplateWithAuth.getForEntity(completedUrl, String.class);
+
+                if (analyzeResp != null && analyzeResp.getStatusCodeValue() == 200) {
+                    String analyzeBody = analyzeResp.getBody();
+                    log.info("差评申述置信度分析提交成功, 返回: {}", analyzeBody);
+
+                    JSONObject analyzeJson = JSONObject.parseObject(analyzeBody);
+                    JSONObject details = analyzeJson.getJSONObject("details");
+                    String analysisStatus = details.get("analysis_status").toString();
+                    if (analysisStatus.equals("pending")) {
+                        log.error("差评申述置信度分析尚未完成");
+                        R.fail("差评申述置信度分析尚未完成");
+                        continue;
+                    }
+                    JSONObject dataJsonObj = analyzeJson.getJSONObject("data");
+
+                    if (dataJsonObj == null) {
+                        log.error("差评申述置信度分析返回数据为空");
+                        R.fail("差评申述置信度分析返回数据为空");
+                        continue;
+                    }
+
+                    // 获取record_id用于后续查询
+                    Integer recordId = dataJsonObj.getInteger("record_id");
+                    if (recordId == null) {
+                        log.error("差评申述置信度分析返回record_id为空");
+                        R.fail("差评申述置信度分析返回record_id为空");
+                        continue;
+                    }
+
+                    StoreCommentAppeal sCommentAppeal = new StoreCommentAppeal();
+                    sCommentAppeal.setRecordId(appeal.getRecordId());
+                    // 判断得分大小
+                    if (dataJsonObj.getDouble("user_confidence") > dataJsonObj.getDouble("merchant_confidence")){
+                        sCommentAppeal.setAppealStatus(1);
+                        sCommentAppeal.setFinalResult("已驳回");
+                    } else {
+                        sCommentAppeal.setAppealStatus(2);
+                        sCommentAppeal.setFinalResult("已同意");
+                        //假删除评论
+                        StoreComment storeComment = new StoreComment();
+                        storeComment.setId(appeal.getCommentId());
+                        storeComment.setDeleteFlag(1);
+                        storeCommentMapper.updateById(storeComment);
+                    }
+                    sCommentAppeal.setAppealAiApproval(dataJsonObj.toJSONString());
+
+                    sCommentAppeal.setRecordId(recordId);
+                    storeCommentAppealMapper.updateByRecordId(appeal.getRecordId(),
+                            sCommentAppeal.getAppealStatus(),
+                            sCommentAppeal.getFinalResult());
+                } else {
+                    if (analyzeResp != null) {
+                        log.error("调用差评申述置信度分析接口失败, http状态: {}", analyzeResp.getStatusCode());
+                        R.fail("调用差评申述置信度分析接口失败, http状态: " + analyzeResp.getStatusCode());
+                    }
+                }
+            } catch (Exception e) {
+                log.error("调用差评申述查询结果接口异常", e);
+            }
+        }
+        return R.success("调用差评申述置信度分析结果接口完成");
+    }
+
+
+
+
+    private R<String> invokeAnalyzeTask(String token) {
+        log.info("开始调用差评申述置信度分析接口, url: {}", analyzeUrl);
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + token);
+
+        // 查询一段时间内的差评申述
+        // 循环插入
+        List<Map<String, Object>> appealList = storeCommentAppealMapper.getAppealList();
+
+        if (appealList == null || appealList.isEmpty()) {
+            log.info("没有需要处理的差评申述");
+            return R.success("没有需要处理的差评申述");
+        }
+
+        for (Map<String, Object> storeCommentAppeal : appealList) {
+            Map<String, Object> analyzeRequest = new HashedMap<>();
+
+            // 商家申诉材料
+            analyzeRequest.put("merchant_material",
+                    storeCommentAppeal.get("appeal_reason") == null ? "" : storeCommentAppeal.get("appeal_reason").toString());
+
+            // 用户差评内容
+            analyzeRequest.put("user_material",
+                    storeCommentAppeal.get("comment_content") == null ? "" : storeCommentAppeal.get("comment_content").toString());
+
+            // 商家图片:支持多张,转成 Base64 数组
+            List<String> merchantImages = new ArrayList<>();
+            String imgUrls = storeCommentAppeal.get("img_url") == null ? "" : storeCommentAppeal.get("img_url").toString();
+            if (StringUtils.hasText(imgUrls)) {
+                // 假设 img_url 是多个图片用逗号分隔的字符串
+                for (String imageUrl : imgUrls.split(",")) {
+                    String base64 = convertImageToBase64(imageUrl.trim());
+                    if (StringUtils.hasText(base64)) {
+                        merchantImages.add(base64);
+                    }
+                }
+            }
+            analyzeRequest.put("merchant_images", merchantImages);
+
+            // 用户图片:同理
+            List<String> userImages = new ArrayList<>();
+            String userImgUrls = storeCommentAppeal.get("user_img_url") == null ? "" : storeCommentAppeal.get("user_img_url").toString();
+            if (StringUtils.hasText(userImgUrls)) {
+                for (String imageUrl : userImgUrls.split(",")) {
+                    String base64 = convertImageToBase64(imageUrl.trim());
+                    if (StringUtils.hasText(base64)) {
+                        userImages.add(base64);
+                    }
+                }
+            }
+            analyzeRequest.put("user_images", userImages);
+
+            // 其他字段
+            analyzeRequest.put("case_id", storeCommentAppeal.get("comment_id") == null ? "" : storeCommentAppeal.get("comment_id").toString());
+            analyzeRequest.put("order_id", storeCommentAppeal.get("business_id") == null ? "" : storeCommentAppeal.get("business_id").toString());
+
+            HttpEntity<Map<String, Object>> analyzeEntity = new HttpEntity<>(analyzeRequest, analyzeHeaders);
+
+            ResponseEntity<String> analyzeResp = null;
+            try {
+                analyzeResp = restTemplate.postForEntity(analyzeUrl, analyzeEntity, String.class);
+            } catch (org.springframework.web.client.HttpServerErrorException.ServiceUnavailable e) {
+                log.error("调用差评申述置信度分析接口返回503 Service Unavailable错误: {}", e.getResponseBodyAsString());
+                continue;
+            } catch (Exception e) {
+                log.error("调用差评申述置信度分析接口异常", e);
+                continue;
+            }
+
+            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("差评申述置信度分析返回数据为空");
+                    continue;
+                }
+
+                // 获取record_id用于后续查询
+                Integer recordId = dataJsonObj.getInteger("record_id");
+                if (recordId == null) {
+                    log.error("差评申述置信度分析返回record_id为空");
+                    R.fail("差评申述置信度分析返回record_id为空");
+                    continue;
+                }
+
+                //修改评论状态为"处理中"
+                StoreCommentAppeal sCommentAppeal = new StoreCommentAppeal();
+                sCommentAppeal.setId((Integer) storeCommentAppeal.get("id"));
+                sCommentAppeal.setAppealStatus(0);
+                sCommentAppeal.setRecordId(recordId);
+                storeCommentAppealMapper.updateById(sCommentAppeal);
+
+            } else {
+                if (analyzeResp != null) {
+                    log.error("调用差评申述置信度分析接口失败, http状态: {}", analyzeResp.getStatusCode());
+                    R.fail("调用差评申述置信度分析接口失败, http状态: " + analyzeResp.getStatusCode());
+                }
+            }
+        }
+        return R.success("调用差评申述置信度分析接口完成");
+    }
+
+    /**
+     * 将图片URL转换为Base64编码
+     *
+     * @param imageUrl 图片URL
+     * @return Base64编码字符串
+     */
+    private String convertImageToBase64(String imageUrl) {
+        // 1. 检查是否为空
+        if (!StringUtils.hasText(imageUrl)) {
+            log.warn("图片URL为空");
+            return "";
+        }
+
+        // 2. 对URL进行编码处理(解决特殊字符问题)
+        String encodedUrl = encodeImageUrl(imageUrl);
+
+        // 3. 验证URL格式
+        if (!isValidUrl(encodedUrl)) {
+            log.warn("无效的图片URL: {}", imageUrl);
+            return "";
+        }
+
+        try {
+            // 4. 下载图片并转换Base64
+            byte[] imageBytes = restTemplate.getForObject(encodedUrl, byte[].class);
+            if (imageBytes != null) {
+                return java.util.Base64.getEncoder().encodeToString(imageBytes);
+            }
+        } catch (org.springframework.web.client.HttpClientErrorException.NotFound e) {
+            log.warn("图片不存在 (404), URL: {}", imageUrl);
+            return "";
+        } catch (Exception e) {
+            log.error("图片转换为Base64失败, URL: {}", imageUrl, e);
+            return "";
+        }
+        return "";
+    }
+
+    // 新增方法:对URL中的特殊字符进行编码
+    private String encodeImageUrl(String imageUrl) {
+        try {
+            URI uri = new URI(imageUrl);
+            String scheme = uri.getScheme();
+            String host = uri.getHost();
+            String path = uri.getPath();
+            String query = uri.getQuery();
+
+            // 对路径中的特殊字符进行编码
+            String encodedPath = java.net.URLEncoder.encode(path, "UTF-8")
+                    .replace("%2F", "/")  // 保留斜杠
+                    .replace("+", "%20"); // 空格编码
+
+            StringBuilder encodedUrl = new StringBuilder();
+            encodedUrl.append(scheme).append("://").append(host).append(encodedPath);
+
+            if (query != null) {
+                encodedUrl.append("?").append(query);
+            }
+
+            return encodedUrl.toString();
+        } catch (Exception e) {
+            log.warn("URL编码失败,使用原始URL: {}", imageUrl);
+            return imageUrl;
+        }
+    }
+
+
+
+    // 辅助方法:验证URL格式合法性
+    private boolean isValidUrl(String url) {
+        try {
+            // 尝试构造URI,验证格式
+            new URI(url);
+            // 进一步检查是否以http/https开头(可选,根据业务需求)
+            return url.startsWith("http://") || url.startsWith("https://");
+        } catch (URISyntaxException e) {
+            return false;
+        }
+    }
+
+    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 buildCompletedUrl(Integer recordId) {
+        String baseUrl = resultUrl;
+        if (!StringUtils.hasText(baseUrl)) {
+            throw new IllegalStateException("差评申述分析接口地址未配置");
+        }
+        if (baseUrl.endsWith("/")) {
+            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+        }
+        String completedBase = baseUrl.replace("/api/v1/analyze", "");
+        if (!completedBase.endsWith("/")) {
+            completedBase = completedBase + "/";
+        }
+        return completedBase + "api/v1/records/" + recordId + "/completed";
+    }
+
+}

+ 1 - 1
alien-job/src/main/resources/bootstrap.yml

@@ -1,3 +1,3 @@
 spring:
   profiles:
-    active: dev
+    active: test

+ 84 - 0
alien-second/src/main/java/shop/alien/second/service/SecondGoodsAuditService.java

@@ -0,0 +1,84 @@
+package shop.alien.second.service;
+
+import org.springframework.stereotype.Service;
+import shop.alien.entity.SecondVideoTask;
+import shop.alien.entity.second.SecondGoods;
+import shop.alien.entity.second.vo.SecondGoodsVo;
+
+import java.util.List;
+
+/**
+ * 二手商品审核服务接口
+ * 负责商品的图片、文本、视频审核相关业务逻辑
+ */
+public interface SecondGoodsAuditService {
+
+    /**
+     * 执行内容审核(图片、文本和视频)
+     * @param goodsDTO 商品信息DTO
+     * @param goods 商品实体
+     * @throws Exception 审核过程中可能抛出的异常
+     */
+    void performContentReview(SecondGoodsVo goodsDTO, SecondGoods goods) throws Exception;
+
+    /**
+     * 执行图片审核
+     * @param goods 商品信息
+     * @param goodsDTO 商品DTO信息
+     * @return 审核结果,true表示通过,false表示不通过
+     * @throws Exception 审核过程中可能抛出的异常
+     */
+    boolean performImageReviews(SecondGoods goods, SecondGoodsVo goodsDTO) throws Exception;
+
+    /**
+     * 执行文本审核
+     * @param goods 商品信息
+     * @param goodsDTO 商品DTO信息
+     * @return 审核结果,true表示通过,false表示不通过
+     * @throws Exception 审核过程中可能抛出的异常
+     */
+    boolean performTextReview(SecondGoods goods, SecondGoodsVo goodsDTO) throws Exception;
+
+    /**
+     * 执行视频审核
+     * @param goods 商品信息
+     * @param goodsDTO 商品DTO信息
+     * @return 视频审核任务ID列表
+     */
+    List<String> performVideoReviews(SecondGoods goods, SecondGoodsVo goodsDTO);
+
+    /**
+     * 处理视频审核结果
+     * @param task 视频审核任务
+     */
+    void processVideoModerationResult(SecondVideoTask task);
+
+    /**
+     * 审核通过后上架商品
+     * @param goods 商品信息
+     */
+    void approveAndListGoods(SecondGoods goods);
+
+    /**
+     * 创建商品审核记录
+     * @param goods 商品信息
+     * @param failReason 审核失败原因
+     * @param goodsStatus 商品状态
+     */
+    void createGoodsAudit(SecondGoods goods, String failReason, Integer goodsStatus);
+
+    /**
+     * 从图片URL列表中提取视频URL
+     * @param imageUrls 图片URL列表
+     * @return 视频URL列表
+     */
+    List<String> extractVideoUrls(List<String> imageUrls);
+
+    /**
+     * 判断URL是否为视频地址
+     * @param url 输入URL
+     * @return 是否为视频地址
+     */
+    boolean isVideoUrl(String url);
+}
+

+ 545 - 0
alien-second/src/main/java/shop/alien/second/service/impl/SecondGoodsAuditServiceImpl.java

@@ -0,0 +1,545 @@
+package shop.alien.second.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.google.common.collect.Lists;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import shop.alien.entity.SecondVideoTask;
+import shop.alien.entity.second.SecondGoods;
+import shop.alien.entity.second.SecondGoodsAudit;
+import shop.alien.entity.second.SecondGoodsRecord;
+import shop.alien.entity.second.enums.SecondGoodsStatusEnum;
+import shop.alien.entity.second.vo.SecondGoodsVo;
+import shop.alien.entity.store.SecondAiTask;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.mapper.SecondAiTaskMapper;
+import shop.alien.mapper.StoreImgMapper;
+import shop.alien.mapper.second.SecondGoodsAuditMapper;
+import shop.alien.mapper.second.SecondGoodsMapper;
+import shop.alien.mapper.second.SecondGoodsRecordMapper;
+import shop.alien.second.service.*;
+import shop.alien.second.util.AiTaskUtils;
+import shop.alien.util.common.Constants;
+import shop.alien.util.common.safe.ImageModerationResultVO;
+import shop.alien.util.common.safe.ImageModerationUtil;
+import shop.alien.util.common.safe.ImageReviewServiceEnum;
+import shop.alien.util.common.safe.TextModerationResultVO;
+import shop.alien.util.common.safe.TextModerationUtil;
+import shop.alien.util.common.safe.TextReviewServiceEnum;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 二手商品审核服务实现类
+ * 负责商品的图片、文本、视频审核相关业务逻辑
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class SecondGoodsAuditServiceImpl implements SecondGoodsAuditService {
+
+    private final SecondAiTaskMapper secondAiTaskMapper;
+    private final StoreImgMapper storeImgMapper;
+    /**
+     * 视频审核功能是否启用的配置项
+     */
+    @Value("${video.moderation.enabled}")
+    private boolean videoModerationEnabled;
+
+    /**
+     * 视频审核失败时是否阻塞商品发布的配置项
+     */
+    @Value("${video.moderation.block-on-failure}")
+    private boolean videoModerationBlockOnFailure;
+
+    /**
+     * 视频审核服务,用于处理商品中视频内容的审核
+     */
+    private final VideoModerationService videoModerationService;
+
+    /**
+     * 文本审核工具,用于审核商品标题、描述等文本内容
+     */
+    private final TextModerationUtil textModerationUtil;
+
+    /**
+     * 图片审核工具,用于审核商品图片内容
+     */
+    private final ImageModerationUtil imageModerationUtil;
+
+    /**
+     * 二手商品Mapper
+     */
+    private final SecondGoodsMapper secondGoodsMapper;
+
+    /**
+     * 二手商品审核Mapper
+     */
+    private final SecondGoodsAuditMapper secondGoodsAuditMapper;
+
+    /**
+     * 商品操作历史记录Mapper
+     */
+    private final SecondGoodsRecordMapper secondGoodsRecordMapper;
+
+    /**
+     * 消息通知服务
+     */
+    private final SecondGoodsNotificationService notificationService;
+
+    /**
+     * 操作历史记录服务
+     */
+    private final SecondGoodsOperationRecordService operationRecordService;
+
+    private final AiTaskUtils aiTaskUtil;
+
+    /**
+     * 执行内容审核
+     * @param goodsDTO 商品信息
+     * @param goods 商品实体
+     */
+    @Override
+    public void performContentReview(SecondGoodsVo goodsDTO, SecondGoods goods) throws Exception {
+        // 图片审核
+        boolean imageAuditResult = performImageReviews(goods, goodsDTO);
+        
+        // 审核失败,直接返回
+        if (!imageAuditResult) {
+            // 图片审核不通过,记录操作历史
+            operationRecordService.recordGoodsOperation(goods, "图片审核失败");
+            return;
+        }
+
+        // 文本审核
+        boolean textAuditResult = performTextReview(goods, goodsDTO);
+        
+        // 审核失败,直接返回
+        if (!textAuditResult) {
+            // 文本审核不通过,记录操作历史
+            operationRecordService.recordGoodsOperation(goods, "文本审核失败");
+            return;
+        }
+        
+        // 视频审核
+        List<String> taskIds = performVideoReviews(goods, goodsDTO);
+
+        // 如果成功提交了视频审核任务,设置商品状态为审核中
+        if (!taskIds.isEmpty()) {
+            goods.setGoodsStatus(SecondGoodsStatusEnum.UNDER_REVIEW.getCode()); // 审核中
+            goods.setVideoTaskId(taskIds.get(0)); // 保存第一个任务ID到商品表
+            goods.setFailedReason("");
+            secondGoodsMapper.updateById(goods);
+            // 审核中审核记录
+            createGoodsAudit(goods, "", Constants.AuditStatus.UNDER_REVIEW);
+        } else {
+            // 第二轮AI审核
+            boolean b = performSecondRoundReview(goods, goodsDTO);
+            if (!b) {
+                // 第二轮审核失败,记录操作历史
+                operationRecordService.recordGoodsOperation(goods, "第二轮审核失败");
+                goods.setGoodsStatus(2);
+                goods.setFailedReason("调用AI接口失败,未获取到task_id");
+                secondGoodsMapper.updateById(goods);
+            }
+        }
+        
+        // 审核通过后上架商品
+//        approveAndListGoods(goods);
+    }
+
+    // 第二轮审核(AI)
+    private boolean performSecondRoundReview(SecondGoods goods, SecondGoodsVo goodsDTO) {
+        try {
+            // 参数校验
+            if (goodsDTO == null || CollectionUtil.isEmpty(goodsDTO.getImgUrl())) {
+                log.warn("Second round review skipped: empty image URLs for goods id={}",
+                        goods != null ? goods.getId() : "unknown");
+                return false;
+            }
+
+            // 获取访问令牌
+            String accessToken = aiTaskUtil.getAccessToken();
+            if (StringUtils.isEmpty(accessToken)) {
+                log.error("Failed to obtain access token for second round review, goods id={}", goods.getId());
+                return false;
+            }
+
+            // 创建AI任务
+            String taskId = aiTaskUtil.createTask(accessToken, goods.getDescription(), goodsDTO.getImgUrl());
+            if (StringUtils.isEmpty(taskId)) {
+                log.warn("Failed to create AI task for second round review, goods id={}", goods.getId());
+                return false;
+            }
+            goods.setAiTaskId(taskId);
+            goods.setGoodsStatus(SecondGoodsStatusEnum.UNDER_REVIEW.getCode());
+            secondGoodsMapper.updateById(goods);
+
+            // 保存任务信息
+            SecondAiTask secondAiTask = new SecondAiTask();
+            secondAiTask.setTaskId(taskId);
+            secondAiTask.setStatus("PROCESSING");
+            Date currentTime = new Date();
+            secondAiTask.setCreateTime(currentTime);
+            secondAiTask.setUpdateTime(currentTime);
+            secondAiTaskMapper.insert(secondAiTask);
+
+            log.info("Successfully created second round AI review task, taskId={}, goodsId={}",
+                    taskId, goods.getId());
+            return true;
+        } catch (Exception e) {
+            log.error("Error during second round review for goods id={}",
+                    goods != null ? goods.getId() : "unknown", e);
+            return false;
+        }
+    }
+
+
+    /**
+     * 执行图片审核
+     * @param goods 商品信息
+     * @param goodsDTO 商品DTO信息
+     * @return 审核结果
+     */
+    @Override
+    public boolean performImageReviews(SecondGoods goods, SecondGoodsVo goodsDTO) throws Exception {
+        // 图片审核(循环处理)
+        List<String> imageUrls = goodsDTO.getImgUrl();
+        // 根据imageUrls过滤不是图片的url
+        imageUrls = imageUrls.stream()
+                .filter(url -> url.toLowerCase().endsWith(".jpg") 
+                        || url.toLowerCase().endsWith(".png") 
+                        || url.toLowerCase().endsWith(".jpeg") 
+                        || url.toLowerCase().endsWith(".gif"))
+                .collect(Collectors.toList());
+        
+        // 图片审核
+        if (imageUrls != null && !imageUrls.isEmpty()) {
+            for (String imageUrl : imageUrls) {
+                List<String> imgServicesList = Lists.newArrayList();
+                // 内容治理检测 + AIGC图片风险检测
+                imgServicesList.add(ImageReviewServiceEnum.TONALITY_IMPROVE.getService());
+                imgServicesList.add(ImageReviewServiceEnum.AIGC_CHECK.getService());
+                
+                ImageModerationResultVO response = imageModerationUtil.productPublishCheck(imageUrl, imgServicesList);
+                if ("high".equals(response.getRiskLevel())) {
+                    // 图片审核不通过或存在高风险
+                    goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode()); // 审核失败
+                    String failReason = "图片审核不通过:图片中包含" + 
+                            (response.getDescriptions() != null ? response.getDescriptions() : "高风险内容");
+                    goods.setFailedReason(failReason);
+                    // 插入审核记录
+                    createGoodsAudit(goods, failReason, Constants.AuditStatus.FAILED);
+                    // 发送审核失败消息
+                    notificationService.sendFailedMsg(goods);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 执行文本审核
+     * @param goods 商品信息
+     * @param goodsDTO 商品DTO信息
+     * @return 审核结果
+     */
+    @Override
+    public boolean performTextReview(SecondGoods goods, SecondGoodsVo goodsDTO) throws Exception {
+        List<String> servicesList = Lists.newArrayList();
+        servicesList.add(TextReviewServiceEnum.AD_COMPLIANCE_DETECTION_PRO.getService());
+        servicesList.add(TextReviewServiceEnum.LLM_QUERY_MODERATION.getService());
+        
+        // 使用商品发布场景的审核服务
+        String test = goodsDTO.getDescription() + goodsDTO.getTitle() + goods.getLabel() + goods.getTopic();
+        TextModerationResultVO textCheckResult = textModerationUtil.invokeFunction(test, servicesList);
+
+        if ("high".equals(textCheckResult.getRiskLevel())) {
+            // 文本审核不通过或存在高风险
+            goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode()); // 审核失败
+            String failReason = "文本审核不通过:" + 
+                    (textCheckResult.getRiskWords() != null ? textCheckResult.getRiskWords() : "存在高风险内容");
+            goods.setFailedReason(failReason);
+            // 插入审核记录
+            createGoodsAudit(goods, failReason, Constants.AuditStatus.FAILED);
+            // 发送审核失败消息
+            notificationService.sendFailedMsg(goods);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 执行视频审核
+     * @param goods 商品信息
+     * @param goodsDTO 商品DTO信息
+     * @return 审核结果
+     */
+    @Override
+    public List<String> performVideoReviews(SecondGoods goods, SecondGoodsVo goodsDTO) {
+        List<String> videoUrls = extractVideoUrls(goodsDTO.getImgUrl());
+        List<String> taskIds = new ArrayList<>();
+        
+        // 视频审核
+        if (videoModerationEnabled) {
+            if (!videoUrls.isEmpty()) {
+                // 提交视频审核任务
+                for (String videoUrl : videoUrls) {
+                    try {
+                        String taskId = videoModerationService.submitVideoModerationTask(videoUrl);
+                        taskIds.add(taskId);
+                    } catch (Exception e) {
+                        log.error("提交视频审核任务失败,视频URL: {}", videoUrl, e);
+                        if (videoModerationBlockOnFailure) {
+                            // 视频审核提交失败,设置为审核失败状态
+                            goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode());
+                            goods.setFailedReason("视频审核提交失败: " + e.getMessage());
+                            createGoodsAudit(goods, "视频审核提交失败", Constants.AuditStatus.FAILED);
+                            notificationService.sendFailedMsg(goods);
+                        }
+                    }
+                }
+            }
+        }
+        return taskIds;
+    }
+
+    /**
+     * 处理视频审核结果
+     * @param task 视频审核任务
+     */
+    @Override
+    public void processVideoModerationResult(SecondVideoTask task) {
+        try {
+            // 查找关联的商品
+            QueryWrapper<SecondGoods> queryWrapper = new QueryWrapper<>();
+            queryWrapper.eq("video_task_id", task.getTaskId());
+            SecondGoods goods = secondGoodsMapper.selectOne(queryWrapper);
+            
+            if (goods == null) {
+                log.warn("未找到关联的商品,任务ID: {}", task.getTaskId());
+                return;
+            }
+
+
+            // 根据审核结果更新商品状态
+            if ("none".equals(task.getRiskLevel())) {
+                QueryWrapper<StoreImg> imgQueryWrapper = new QueryWrapper<>();
+                imgQueryWrapper.eq("store_id", goods.getId());
+                imgQueryWrapper.eq("img_type", 19);
+                List<StoreImg> storeImgs = storeImgMapper.selectList(imgQueryWrapper);
+                List<String> imgUrls = storeImgs.stream()
+                        .map(StoreImg::getImgUrl)
+                        .filter(imgUrl -> StringUtils.hasText(imgUrl))
+                        .collect(Collectors.toList());
+
+                SecondGoodsVo goodsDTO = new SecondGoodsVo();
+                goodsDTO.setImgUrl(imgUrls);
+
+                // 开始第二轮审核
+                boolean b = performSecondRoundReview(goods, goodsDTO);
+
+                // 审核通过
+//                approveAndListGoods(goods);
+            } else {
+                // 审核不通过
+                goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode());
+                
+                // 解析审核结果,生成具体的失败原因
+                String failedReason = parseVideoModerationFailureReason(task);
+                goods.setFailedReason(failedReason);
+                secondGoodsMapper.updateById(goods);
+                
+                // 更新审核记录
+                createGoodsAudit(goods, failedReason, Constants.AuditStatus.FAILED);
+
+                // 记录操作历史
+                operationRecordService.recordGoodsOperation(goods, "视频审核失败");
+                // 发送审核失败消息
+                notificationService.sendFailedMsg(goods);
+            }
+        } catch (Exception e) {
+            log.error("处理视频审核结果时发生异常,任务ID: {}", task.getTaskId(), e);
+        }
+    }
+
+    /**
+     * 审核通过后上架商品
+     * @param goods 商品信息
+     */
+    @Override
+    public void approveAndListGoods(SecondGoods goods) {
+        boolean isNotified = false;
+        try {
+            // 如果所有审核都通过,设置为上架状态
+            goods.setGoodsStatus(SecondGoodsStatusEnum.LISTED.getCode()); // 上架
+            goods.setFailedReason("");
+            goods.setReleaseTime(new Date()); // 上架时间
+            secondGoodsMapper.updateById(goods);
+            // 插入审核记录
+            createGoodsAudit(goods, "", Constants.AuditStatus.PASSED);
+            // 发送审核成功消息
+            notificationService.sendMessage(goods);
+            isNotified = true; // 标记通知已发送
+            
+            // 上架 记录商品操作历史
+            String operationName = "";
+            QueryWrapper<SecondGoodsRecord> queryWrapper = new QueryWrapper<>();
+            queryWrapper.eq("goods_id", goods.getId());
+            List<SecondGoodsRecord> recordList = secondGoodsRecordMapper.selectList(queryWrapper);
+            if (CollectionUtil.isNotEmpty(recordList)) {
+                operationName = "重新发布";
+            } else {
+                operationName = "首次发布";
+            }
+            operationRecordService.recordGoodsOperation(goods, operationName);
+        } catch (Exception e) {
+            log.error("商品上架过程中发生异常,执行回滚", e);
+            // 如果通知已发送但后续操作失败,需要补偿
+            if (isNotified) {
+                // 发送补偿消息,比如撤销通知或标记为异常状态
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * 创建商品审核记录
+     * @param goods 商品信息
+     * @param failReason 审核失败原因
+     * @param goodsStatus 商品状态
+     */
+    @Override
+    public void createGoodsAudit(SecondGoods goods, String failReason, Integer goodsStatus) {
+        // 保存审核结果
+        secondGoodsMapper.updateById(goods);
+        // 插入审核记录
+        SecondGoodsAudit auditRecord = new SecondGoodsAudit();
+        auditRecord.setGoodsId(goods.getId());
+        auditRecord.setGoodsStatus(goodsStatus); // 审核状态
+        if (Constants.AuditStatus.FAILED.equals(goodsStatus)) {
+            auditRecord.setFailedReason(failReason);
+        }
+        auditRecord.setCreatedUserId(goods.getUserId());
+        auditRecord.setUpdatedUserId(goods.getUserId());
+        auditRecord.setCreatedTime(new Date());
+        auditRecord.setUpdatedTime(new Date());
+        secondGoodsAuditMapper.insert(auditRecord);
+        goods.setAuditRecordId(auditRecord.getId());
+    }
+
+    /**
+     * 从图片URL列表中提取视频URL
+     * @param imageUrls 图片URL列表
+     * @return 视频URL列表
+     */
+    @Override
+    public List<String> extractVideoUrls(List<String> imageUrls) {
+        if (CollectionUtil.isEmpty(imageUrls)) {
+            return Collections.emptyList();
+        }
+        
+        List<String> videoUrls = new ArrayList<>();
+        for (String url : imageUrls) {
+            if (isVideoUrl(url)) {
+                videoUrls.add(url);
+            }
+        }
+        return videoUrls;
+    }
+
+    /**
+     * 判断URL是否为视频地址
+     * @param url 输入URL
+     * @return 是否为视频地址
+     */
+    @Override
+    public boolean isVideoUrl(String url) {
+        if (url == null) return false;
+        url = url.toLowerCase();
+        return url.endsWith(".mp4") ||
+                url.endsWith(".avi") ||
+                url.endsWith(".mov") ||
+                url.endsWith(".flv") ||
+                url.endsWith(".wmv") ||
+                url.endsWith(".mkv");
+    }
+
+    /**
+     * 解析视频审核失败原因
+     * @param task 视频审核任务
+     * @return 失败原因
+     */
+    private String parseVideoModerationFailureReason(SecondVideoTask task) {
+        StringBuilder reasonBuilder = new StringBuilder("视频审核不通过,风险等级: " + task.getRiskLevel());
+        
+        try {
+            // 解析审核结果JSON
+            JSONObject resultJson = JSON.parseObject(task.getResult());
+            if (resultJson != null && resultJson.containsKey("data")) {
+                JSONObject data = resultJson.getJSONObject("data");
+                
+                // 处理帧结果(视频画面)
+                if (data.containsKey("FrameResult")) {
+                    JSONObject frameResult = data.getJSONObject("FrameResult");
+                    if (frameResult != null && frameResult.containsKey("FrameSummarys")) {
+                        JSONArray frameSummarys = frameResult.getJSONArray("FrameSummarys");
+                        if (frameSummarys != null && !frameSummarys.isEmpty()) {
+                            reasonBuilder.append("。检测到违规内容:");
+                            for (int i = 0; i < frameSummarys.size(); i++) {
+                                JSONObject summary = frameSummarys.getJSONObject(i);
+                                String label = summary.getString("Label");
+                                String description = summary.getString("Description");
+                                Integer labelSum = summary.getInteger("LabelSum");
+                                
+                                if (label != null && !label.isEmpty()) {
+                                    reasonBuilder.append("[").append(description != null ? description : label)
+                                            .append("]出现").append(labelSum != null ? labelSum : 1).append("次;");
+                                }
+                            }
+                        }
+                    }
+                }
+                
+                // 处理音频结果
+                if (data.containsKey("AudioResult")) {
+                    JSONObject audioResult = data.getJSONObject("AudioResult");
+                    if (audioResult != null && audioResult.containsKey("AudioSummarys")) {
+                        JSONArray audioSummarys = audioResult.getJSONArray("AudioSummarys");
+                        if (audioSummarys != null && !audioSummarys.isEmpty()) {
+                            reasonBuilder.append("。检测到违规音频:");
+                            for (int i = 0; i < audioSummarys.size(); i++) {
+                                JSONObject summary = audioSummarys.getJSONObject(i);
+                                String label = summary.getString("Label");
+                                String description = summary.getString("Description");
+                                Integer labelSum = summary.getInteger("LabelSum");
+                                
+                                if (label != null && !label.isEmpty()) {
+                                    reasonBuilder.append("[").append(description != null ? description : label)
+                                            .append("]出现").append(labelSum != null ? labelSum : 1).append("次;");
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.warn("解析视频审核结果失败,使用默认原因,任务ID: {}", task.getTaskId(), e);
+        }
+        
+        return reasonBuilder.toString();
+    }
+}
+

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

@@ -0,0 +1,146 @@
+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.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
+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;
+
+    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;
+    }
+}

+ 1 - 1
alien-second/src/main/resources/bootstrap.yml

@@ -1,3 +1,3 @@
 spring:
   profiles:
-    active: dev
+    active: test

+ 85 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreVerificationCodeController.java

@@ -0,0 +1,85 @@
+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 io.swagger.annotations.ApiOperationSupport;
+import io.swagger.annotations.ApiSort;
+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.StoreVerificationCode;
+import shop.alien.store.service.StoreVerificationCodeService;
+
+/**
+ * 短信验证码 CRUD
+ */
+@Api(tags = {"二期-验证码管理"})
+@Slf4j
+@ApiSort(30)
+@CrossOrigin
+@RestController
+@RequestMapping("/storeVerificationCode")
+@RequiredArgsConstructor
+public class StoreVerificationCodeController {
+
+    private final StoreVerificationCodeService storeVerificationCodeService;
+
+    @ApiOperation("分页查询验证码记录")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "phone", value = "手机号", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "appType", value = "端区分(0:用户,1:商家,2:律师端)", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "businessType", value = "业务类型", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "每页数量", dataType = "Integer", paramType = "query")
+    })
+    @GetMapping("/page")
+    public R<IPage<StoreVerificationCode>> page(@RequestParam(value = "phone", required = false) String phone,
+                                                @RequestParam(value = "appType", required = false) Integer appType,
+                                                @RequestParam(value = "businessType", required = false) Integer businessType,
+                                                @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum,
+                                                @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize) {
+        log.info("StoreVerificationCodeController.page?phone={}&appType={}&businessType={}&pageNum={}&pageSize={}",
+                phone, appType, businessType, pageNum, pageSize);
+        return R.data(storeVerificationCodeService.pageList(phone, appType, businessType, pageNum, pageSize));
+    }
+
+    @ApiOperation("根据ID获取验证码记录")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParam(name = "id", value = "主键id", dataType = "Integer", paramType = "path", required = true)
+    @GetMapping("/{id}")
+    public R<StoreVerificationCode> detail(@PathVariable("id") Integer id) {
+        log.info("StoreVerificationCodeController.detail?id={}", id);
+        return R.data(storeVerificationCodeService.getById(id));
+    }
+
+    @ApiOperation("新增验证码记录")
+    @ApiOperationSupport(order = 3)
+    @PostMapping
+    public R<StoreVerificationCode> create(@RequestBody StoreVerificationCode request) {
+        log.info("StoreVerificationCodeController.create?request={}", request);
+        return R.data(storeVerificationCodeService.create(request));
+    }
+
+    @ApiOperation("更新验证码记录")
+    @ApiOperationSupport(order = 4)
+    @PutMapping
+    public R<StoreVerificationCode> update(@RequestBody StoreVerificationCode request) {
+        log.info("StoreVerificationCodeController.update?request={}", request);
+        return R.data(storeVerificationCodeService.updateRecord(request));
+    }
+
+    @ApiOperation("删除验证码记录")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParam(name = "id", value = "主键id", dataType = "Integer", paramType = "path", required = true)
+    @DeleteMapping("/{id}")
+    public R<Boolean> delete(@PathVariable("id") Integer id) {
+        log.info("StoreVerificationCodeController.delete?id={}", id);
+        return R.data(storeVerificationCodeService.removeRecord(id));
+    }
+}
+

+ 49 - 0
alien-store/src/main/java/shop/alien/store/controller/UserStoreController.java

@@ -1,17 +1,41 @@
 package shop.alien.store.controller;
 
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import io.swagger.annotations.*;
 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.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.client.RestTemplate;
 import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreComment;
+import shop.alien.entity.store.StoreCommentAppeal;
+import shop.alien.entity.store.vo.StoreCommentAppealVo;
+import shop.alien.mapper.StoreCommentAppealMapper;
+import shop.alien.mapper.StoreCommentMapper;
 import shop.alien.store.service.LifeUserStoreService;
 import shop.alien.util.common.AlipayTradeAppPay;
 import shop.alien.util.common.ListToPage;
 import shop.alien.util.common.UniqueRandomNumGenerator;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -25,6 +49,30 @@ public class UserStoreController {
 
     private final LifeUserStoreService lifeUserStoreService;
 
+    private final RestTemplate restTemplate;
+
+    private final StoreCommentAppealMapper storeCommentAppealMapper;
+
+    private final StoreCommentMapper storeCommentMapper;
+
+//    @Value("${third-party-login.base-url}")
+    private String loginUrl = "http://192.168.2.250:9000/ai/user-auth-core/api/v1/auth/login";
+
+    private String resultUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/records/{record_id}/completed";
+
+//    @Value("${third-party-user-name.base-url}")
+    private String userName = "UdUser";
+
+//    @Value("${third-party-pass-word.base-url}")
+    private String passWord = "123456";
+
+    /**
+     * 差评申述置信度分析接口地址
+     * 例如: http://192.168.2.250:9004/api/v1/analyze 或通过网关: http://192.168.2.250:9000/ai/auto_review/api/v1/analyze
+     */
+//    @Value("${bad-review-analyze.base-url}")
+    private String analyzeUrl = "http://192.168.2.250:9000/ai/auto-review/api/v1/analyze";
+
     @ApiOperation("查询商铺列表")
     @ApiOperationSupport(order = 1)
     @ApiImplicitParams({@ApiImplicitParam(name = "page", value = "分页页数", dataType = "String", paramType = "query", required = true),
@@ -118,4 +166,5 @@ public class UserStoreController {
         log.info("LifeUserStoreController.buyQuan?quanId={},couponId={},userId={},cnt={},payPrice={},orderNo={}", quanId, couponId, userId, cnt, payPrice, orderNo);
         return R.data(lifeUserStoreService.buyQuan(quanId, couponId, userId, cnt, payPrice, orderNo));
     }
+
 }

+ 36 - 0
alien-store/src/main/java/shop/alien/store/service/StoreVerificationCodeService.java

@@ -0,0 +1,36 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import shop.alien.entity.store.StoreVerificationCode;
+
+/**
+ * 短信验证码 service
+ */
+public interface StoreVerificationCodeService extends IService<StoreVerificationCode> {
+
+    /**
+     * 分页查询
+     */
+    IPage<StoreVerificationCode> pageList(String phone,
+                                          Integer appType,
+                                          Integer businessType,
+                                          Integer pageNum,
+                                          Integer pageSize);
+
+    /**
+     * 新增记录
+     */
+    StoreVerificationCode create(StoreVerificationCode request);
+
+    /**
+     * 更新记录
+     */
+    StoreVerificationCode updateRecord(StoreVerificationCode request);
+
+    /**
+     * 删除
+     */
+    boolean removeRecord(Integer id);
+}
+

+ 100 - 81
alien-store/src/main/java/shop/alien/store/service/impl/LifeUserViolationServiceImpl.java

@@ -7,6 +7,7 @@ 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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.google.common.collect.Lists;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
@@ -17,6 +18,7 @@ import org.springframework.util.CollectionUtils;
 import shop.alien.entity.second.SecondGoodsRecord;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.LifeUserViolationDto;
+import shop.alien.entity.store.excelVo.LifeUserOrderExcelVo;
 import shop.alien.entity.store.excelVo.LifeUserViolationExcelVO;
 import shop.alien.entity.store.excelVo.util.ExcelGenerator;
 import shop.alien.entity.store.vo.LifeUserViolationVo;
@@ -25,19 +27,24 @@ import shop.alien.mapper.*;
 import shop.alien.mapper.second.SecondGoodsRecordMapper;
 import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.service.*;
+import shop.alien.store.util.AiUserViolationUtils;
 import shop.alien.store.util.FunctionMagic;
 import shop.alien.util.ali.AliOSSUtil;
 import shop.alien.util.common.EnumUtil;
+import shop.alien.util.common.JwtUtil;
 
 import java.io.File;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
+import java.time.Instant;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
+import static shop.alien.util.common.constant.Constant.*;
+
 
 /**
  * <p>
@@ -81,6 +88,8 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
     private final SecondGoodsRecordMapper secondGoodsRecordMapper;
     private final StoreImgService storeImgService;
 
+    private final AiUserViolationUtils aiUserViolationUtils;
+
     @Value("${spring.web.resources.excel-path}")
     private String excelPath;
 
@@ -101,34 +110,47 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
             }
             int result = lifeUserViolationMapper.insert(lifeuserViolation);
             if (result > 0) {
+                // AI审核
+                //登录获取token
+                String token = aiUserViolationUtils.getAccessToken();
+                //调用AI接口
+                String taskId = aiUserViolationUtils.createTask(token, lifeuserViolation);
+                if (org.springframework.util.StringUtils.isEmpty(taskId)) {
+                    log.warn("Failed to create AI task for second round review, lifeuserViolation id={}", lifeuserViolation.getId());
+                    return 0;
+                }
+                lifeuserViolation.setAiTaskId(taskId);
+                lifeUserViolationMapper.updateById(lifeuserViolation);
+
+
                 //String phoneId = Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getString("userType") + "_" + JwtUtil.getCurrentUserInfo().getString("phone");
-                // 举报人消息
-                LifeNotice lifeNotice = getLifeNotice(lifeuserViolation);
-                lifeNoticeMapper.insert(lifeNotice);
-                WebSocketVo websocketVo = new WebSocketVo();
-                websocketVo.setSenderId("system");
-                websocketVo.setReceiverId(lifeNotice.getReceiverId());
-                websocketVo.setCategory("notice");
-                websocketVo.setNoticeType("1");
-                websocketVo.setIsRead(0);
-                websocketVo.setText(com.alibaba.fastjson2.JSONObject.from(lifeNotice).toJSONString());
-                webSocketProcess.sendMessage(lifeNotice.getReceiverId(), com.alibaba.fastjson2.JSONObject.from(websocketVo).toJSONString());
-
-                // 被举报人消息
-                if (StringUtils.isNotEmpty(lifeuserViolation.getReportContextType()) && "1,2,3".contains(lifeuserViolation.getReportContextType())) {
-                    LifeNotice lifeNoticeReported = getLifeReportedNotice(lifeuserViolation);
-                    if (lifeNoticeReported != null) {
-                        lifeNoticeMapper.insert(lifeNoticeReported);
-                        WebSocketVo websocketVoReported = new WebSocketVo();
-                        websocketVoReported.setSenderId("system");
-                        websocketVoReported.setReceiverId(lifeNoticeReported.getReceiverId());
-                        websocketVoReported.setCategory("notice");
-                        websocketVoReported.setNoticeType("1");
-                        websocketVoReported.setIsRead(0);
-                        websocketVoReported.setText(com.alibaba.fastjson2.JSONObject.from(lifeNoticeReported).toJSONString());
-                        webSocketProcess.sendMessage(lifeNoticeReported.getReceiverId(), com.alibaba.fastjson2.JSONObject.from(websocketVoReported).toJSONString());
+                    // 举报人消息
+                    LifeNotice lifeNotice = getLifeNotice(lifeuserViolation);
+                    lifeNoticeMapper.insert(lifeNotice);
+                    WebSocketVo websocketVo = new WebSocketVo();
+                    websocketVo.setSenderId("system");
+                    websocketVo.setReceiverId(lifeNotice.getReceiverId());
+                    websocketVo.setCategory("notice");
+                    websocketVo.setNoticeType("1");
+                    websocketVo.setIsRead(0);
+                    websocketVo.setText(com.alibaba.fastjson2.JSONObject.from(lifeNotice).toJSONString());
+                    webSocketProcess.sendMessage(lifeNotice.getReceiverId(), com.alibaba.fastjson2.JSONObject.from(websocketVo).toJSONString());
+
+                    // 被举报人消息
+                    if(StringUtils.isNotEmpty(lifeuserViolation.getReportContextType()) && "1,2,3".contains(lifeuserViolation.getReportContextType())){
+                        LifeNotice lifeNoticeReported = getLifeReportedNotice(lifeuserViolation);
+                        if (lifeNoticeReported != null) {
+                            lifeNoticeMapper.insert(lifeNoticeReported);
+                            WebSocketVo websocketVoReported = new WebSocketVo();
+                            websocketVoReported.setSenderId("system");
+                            websocketVoReported.setReceiverId(lifeNoticeReported.getReceiverId());
+                            websocketVoReported.setCategory("notice");
+                            websocketVoReported.setNoticeType("1");
+                            websocketVoReported.setIsRead(0);
+                            websocketVoReported.setText(com.alibaba.fastjson2.JSONObject.from(lifeNoticeReported).toJSONString());
+                            webSocketProcess.sendMessage(lifeNoticeReported.getReceiverId(), com.alibaba.fastjson2.JSONObject.from(websocketVoReported).toJSONString());
+                        }
                     }
-                }
                 return result;
             }
         } catch (Exception e) {
@@ -153,16 +175,16 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 
         if ("1".equals(lifeuserViolation.getReportingUserType())) {
             StoreUser storeUsers = storeUserMapper.selectById(reportUserId);
-            phoneId = "store_" + storeUsers.getPhone();
+            phoneId = "store_"+storeUsers.getPhone();
         } else {
             LifeUser lifeUsers = lifeUserMapper.selectById(reportUserId);
-            phoneId = "user_" + lifeUsers.getUserPhone();
+            phoneId = "user_"+lifeUsers.getUserPhone();
         }
 
         lifeNotice.setReceiverId(phoneId);
 
-        if (StringUtils.isNotEmpty(reportContextType) && "1,2,3".contains(reportContextType)) {
-            String violationType = StringUtils.isNotEmpty(lifeuserViolation.getViolationType()) ? lifeuserViolation.getViolationType() : "13";
+        if(StringUtils.isNotEmpty(reportContextType) && "1,2,3".contains(reportContextType)){
+            String violationType = StringUtils.isNotEmpty(lifeuserViolation.getViolationType())?lifeuserViolation.getViolationType():"13";
             String violationText = EnumUtil.getStatusValue(Integer.parseInt(violationType));
 
             String storeOrUserName = "";
@@ -182,25 +204,25 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 
             switch (reportContextType) {
                 case "1":
-                    message = "您在" + storeDate + "举报用户“" + storeOrUserName + "”,涉嫌" + violationText + ",已提交至平台审核,1-3个工作日会将审核结果发送到您应用内的消息-通知中,请注意查收。";
+                    message = "您在" + storeDate + "举报用户“" + storeOrUserName + "”,涉嫌"+violationText+",已提交至平台审核,1-3个工作日会将审核结果发送到您应用内的消息-通知中,请注意查收。";
                     break;
                 case "2":
                     String dynamicsId = lifeuserViolation.getDynamicsId();
                     String dynamicsDate = simpleDateFormat.format(new Date());
-                    if (StringUtils.isNotEmpty(dynamicsId)) {
+                    if(StringUtils.isNotEmpty(dynamicsId)){
                         LifeUserDynamics lifeUserDynamics = lifeUserDynamicsMapper.selectById(dynamicsId);
                         dynamicsDate = simpleDateFormats.format(lifeUserDynamics.getCreatedTime());
                     }
-                    message = "您在" + storeDate + "举报用户“" + storeOrUserName + "”在" + dynamicsDate + "发布的动态,涉嫌" + violationText + ",已提交至平台审核,1-3个工作日会将审核结果发送到您应用内的消息-通知中,请注意查收。";
+                    message = "您在" + storeDate + "举报用户“" + storeOrUserName + "”在"+dynamicsDate+"发布的动态,涉嫌"+violationText+",已提交至平台审核,1-3个工作日会将审核结果发送到您应用内的消息-通知中,请注意查收。";
                     break;
                 case "3":
                     String commonId = lifeuserViolation.getCommentId();
                     String commonDate = simpleDateFormat.format(new Date());
-                    if (StringUtils.isNotEmpty(commonId)) {
-                        StoreComment storeComment = storeCommentMapper.selectById(commonId);
+                    if(StringUtils.isNotEmpty(commonId)){
+                        StoreComment storeComment  = storeCommentMapper.selectById(commonId);
                         commonDate = simpleDateFormats.format(storeComment.getCreatedTime());
                     }
-                    message = "您在" + storeDate + "举报用户“" + storeOrUserName + "”在" + commonDate + "发布的评论,涉嫌" + violationText + ",已提交至平台审核,1-3个工作日会将审核结果发送到您应用内的消息-通知中,请注意查收。";
+                    message = "您在" + storeDate + "举报用户“" + storeOrUserName + "”在"+commonDate+"发布的评论,涉嫌"+violationText+",已提交至平台审核,1-3个工作日会将审核结果发送到您应用内的消息-通知中,请注意查收。";
                     break;
             }
         }
@@ -229,7 +251,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
             lifeNotice.setReceiverId("user_" + phoneId);
 
         }
-        if (StringUtils.isEmpty(phoneId)) {
+        if (StringUtils.isEmpty(phoneId)){
             return null;
         }
         String violationText = EnumUtil.getStatusValue(Integer.parseInt(lifeuserViolation.getViolationType()));
@@ -243,8 +265,8 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
         String storeDate = simpleDateFormat.format(new Date());
         String reportContextType = lifeuserViolation.getReportContextType();
 
-        if (StringUtils.isNotEmpty(reportContextType) && reportContextType.equals("1")) {
-            message = "您在" + storeDate + "被举报涉嫌" + violationText + ",平台将会进行核实。如确实存在违规行为,平台将禁用您的账号**天,到期后账号可恢复使用,应用内的环境需要我们共同维护。";
+        if(StringUtils.isNotEmpty(reportContextType) && reportContextType.equals("1")){
+            message = "您在" + storeDate + "被举报涉嫌"+violationText+",平台将会进行核实。如确实存在违规行为,平台将禁用您的账号**天,到期后账号可恢复使用,应用内的环境需要我们共同维护。";
         } else {
             return null;
         }
@@ -310,9 +332,8 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
         QueryWrapper<LifeUserViolationVo> queryWrapper = new QueryWrapper<>();
 
         // 基础查询条件
-        queryWrapper.eq("luv.delete_flag", 0);
-//                //排除二手的举报
-//                .notIn("luv.report_context_type", Arrays.asList("4", "5"));
+        queryWrapper.eq("luv.delete_flag", 0)
+                .in("luv.report_context_type", Arrays.asList("1", "2", "3"));
 
         // 动态查询条件
         queryWrapper.like(StringUtils.isNotEmpty(nickName), "ui.nick_name", nickName)
@@ -350,7 +371,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 
     @Override
     public void approve(int id, String processingStatus, String reportResult) {
-        if (id == 0 || StringUtils.isBlank(processingStatus)) {
+        if(id==0 || StringUtils.isBlank(processingStatus)){
             return;
         }
         LifeUserViolation v = lifeUserViolationMapper.selectById(id);
@@ -364,15 +385,15 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
         String violationTime = simpleDateFormat.format(v.getCreatedTime());
         // 被举报人信息
         String reportedUserName = "";
-        if (StringUtils.isNotEmpty(v.getReportedUserId())) {
+        if(StringUtils.isNotEmpty(v.getReportedUserId())){
             if (v.getReportedUserType().equals("1")) {
                 StoreUser storeUser = storeUserMapper.selectById(v.getReportedUserId());
-                if (storeUser != null) {
+                if(storeUser != null){
                     reportedUserName = storeUser.getNickName();
                 }
-            } else {
+            } else{
                 LifeUser lifeUser = lifeUserMapper.selectById(v.getReportedUserId());
-                if (lifeUser != null) {
+                if(lifeUser != null) {
                     reportedUserName = lifeUser.getUserName();
                 }
             }
@@ -380,15 +401,15 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
         // 被举报动态信息
         String dynamicsId = v.getDynamicsId();
         String dynamicsDate = simpleDateFormats.format(new Date());
-        if (StringUtils.isNotEmpty(dynamicsId)) {
+        if(StringUtils.isNotEmpty(dynamicsId)){
             LifeUserDynamics lifeUserDynamics = lifeUserDynamicsMapper.selectById(dynamicsId);
             dynamicsDate = simpleDateFormats.format(lifeUserDynamics.getCreatedTime());
         }
         // 被举报评论信息
         String commonId = v.getCommentId();
         String commonDate = simpleDateFormats.format(new Date());
-        if (StringUtils.isNotEmpty(commonId)) {
-            StoreComment storeComment = storeCommentMapper.selectById(commonId);
+        if(StringUtils.isNotEmpty(commonId)){
+            StoreComment storeComment  = storeCommentMapper.selectById(commonId);
             commonDate = simpleDateFormats.format(storeComment.getCreatedTime());
         }
 
@@ -401,13 +422,13 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
             // 通过
             if (v.getReportContextType().equals("1")) {
                 // 用户
-                message = "您在" + violationTime + "举报用户“" + reportedUserName + "”,涉嫌违法违规,经核实,确实存在违规行为,平台已将用户禁用,感谢您为此做出的贡献。";
+                message = "您在"+violationTime+"举报用户“"+reportedUserName+"”,涉嫌违法违规,经核实,确实存在违规行为,平台已将用户禁用,感谢您为此做出的贡献。";
                 title = "用户举报成功通知";
             }
             if (v.getReportContextType().equals("2")) {
                 // 动态
-                message = "您在" + violationTime + "举报用户“" + reportedUserName + "”在" + dynamicsDate + "发布的动态,涉嫌违法违规,经核实,确实存在违规行为,平台已将此动态下架,感谢您为此做出的贡献";
-                reportedMessage = "您在" + dynamicsDate + "发布的动态,经核实,确实存在违规行为,平台已将此动态下架,应用内的环境需要我们共同维护";
+                message = "您在"+violationTime+"举报用户“"+reportedUserName+"”在"+dynamicsDate+"发布的动态,涉嫌违法违规,经核实,确实存在违规行为,平台已将此动态下架,感谢您为此做出的贡献";
+                reportedMessage = "您在"+dynamicsDate+"发布的动态,经核实,确实存在违规行为,平台已将此动态下架,应用内的环境需要我们共同维护";
                 lifeUserDynamicsService.removeById(v.getDynamicsId());
                 title = "动态举报成功通知";
             }
@@ -415,29 +436,29 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
                 // 评论
                 if (v.getReportedUserType().equals("1")) {
                     // 商户
-                    message = "您在" + violationTime + "举报用户“" + reportedUserName + "”在" + commonDate + "发布的评论,涉嫌违法违规,经核实,确实存在违规行为,平台已将此评论下架,感谢您为此做出的贡献。";
+                    message = "您在"+violationTime+"举报用户“"+reportedUserName+"”在"+commonDate+"发布的评论,涉嫌违法违规,经核实,确实存在违规行为,平台已将此评论下架,感谢您为此做出的贡献。";
                     storeCommentService.removeById(v.getCommentId());
                 } else {
                     // 用户
-                    message = "您在" + violationTime + "举报用户“" + reportedUserName + "”在" + commonDate + "发布的评论,涉嫌违法违规,经核实,确实存在违规行为,平台已将此评论下架,感谢您为此做出的贡献。";
+                    message = "您在"+violationTime+"举报用户“"+reportedUserName+"”在"+commonDate+"发布的评论,涉嫌违法违规,经核实,确实存在违规行为,平台已将此评论下架,感谢您为此做出的贡献。";
                     lifeCommentMapper.deleteById(v.getCommentId());
                 }
-                reportedMessage = "您在" + commonDate + "发布的评论,经核实,确实存在违规行为,平台已将此评论下架,应用内的环境需要我们共同维护。";
+                reportedMessage = "您在"+commonDate+"发布的评论,经核实,确实存在违规行为,平台已将此评论下架,应用内的环境需要我们共同维护。";
                 title = "评论举报成功通知";
             }
         } else {
             // 驳回
             switch (v.getReportContextType()) {
                 case "1":
-                    message = "您在" + violationTime + "举报用户“" + reportedUserName + "”,涉嫌违法违规,经核实,不存在违规行为,感谢您为此做出的贡献。";
+                    message = "您在"+violationTime+"举报用户“"+reportedUserName+"”,涉嫌违法违规,经核实,不存在违规行为,感谢您为此做出的贡献。";
                     title = "用户举报失败通知";
                     break;
                 case "2":
-                    message = "您在" + violationTime + "举报用户“" + reportedUserName + "”在" + dynamicsDate + "发布的动态,涉嫌违法违规,经核实,不存在违规行为,感谢您为此做出的贡献。";
+                    message = "您在"+violationTime+"举报用户“"+reportedUserName+"”在"+dynamicsDate+"发布的动态,涉嫌违法违规,经核实,不存在违规行为,感谢您为此做出的贡献。";
                     title = "动态举报失败通知";
                     break;
                 case "3":
-                    message = "您在" + violationTime + "举报用户“" + reportedUserName + "”在" + commonDate + "发布的评论,涉嫌违法违规,经核实,不存在违规行为,感谢您为此做出的贡献。";
+                    message = "您在"+violationTime+"举报用户“"+reportedUserName+"”在"+commonDate+"发布的评论,涉嫌违法违规,经核实,不存在违规行为,感谢您为此做出的贡献。";
                     title = "评论举报失败通知";
                     break;
             }
@@ -479,7 +500,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 
 
         // 被举报通知
-        if (StringUtils.isNotEmpty(reportedMessage)) {
+        if(StringUtils.isNotEmpty(reportedMessage)) {
             LifeNotice reportedLifeMessage = new LifeNotice();
             com.alibaba.fastjson2.JSONObject jsonObjectReported = new com.alibaba.fastjson2.JSONObject();
             jsonObjectReported.put("message", reportedMessage);
@@ -522,12 +543,10 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
         if (entity == null) return null;
         LifeUserViolationDto dto = new LifeUserViolationDto();
         BeanUtils.copyProperties(entity, dto);
-        LambdaQueryWrapper<StoreImg> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(StoreImg::getStoreId, entity.getId());
-        queryWrapper.in(StoreImg::getImgType, 19);
-        List<StoreImg> storeImgList = storeImgService.list(queryWrapper);
-        List<String> collect = storeImgList.stream().map(StoreImg::getImgUrl).collect(Collectors.toList());
-        dto.setImageList(collect);
+        if (Objects.nonNull(entity.getReportEvidenceImg())) {
+            List<String> list = Arrays.stream(entity.getReportEvidenceImg().split(",")).map(String::trim).collect(Collectors.toList());
+            dto.setImageList(list);
+        }
         // 处理举报人信息
         FunctionMagic.handleUserInfo(dto.getReportingUserType(), dto.getReportingUserId(), storeId -> storeUserService.getOne(FunctionMagic.idQueryWrapper(storeId)), lifeId -> lifeUserService.getOne(FunctionMagic.idQueryWrapper(lifeId)), user -> {
             if (user instanceof StoreUser) {
@@ -576,7 +595,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
         queryWrapper.orderByDesc("luv.updated_time");
 
         List<LifeUserViolationVo> violationList = lifeUserViolationMapper.getViolationList(queryWrapper);
-
+        
         // 如果查询结果为空,返回空列表生成的Excel
         if (CollectionUtils.isEmpty(violationList)) {
             log.warn("导出Excel时查询结果为空,nickName={}, phone={}, processingStatus={}", nickName, phone, processingStatus);
@@ -584,7 +603,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
 
         // 日期格式化器(复用)
         DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-
+        
         // 使用Stream API进行转换,提高性能和可读性
         AtomicInteger serialNumber = new AtomicInteger(1);
         List<LifeUserViolationExcelVO> excelDataList = violationList.stream()
@@ -594,10 +613,10 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
         // 生成Excel文件
         String fileName = UUID.randomUUID().toString().replace("-", "");
         String filePath = ExcelGenerator.generateExcel(
-                excelPath + excelGeneratePath + fileName + ".xlsx",
-                excelDataList,
+                excelPath + excelGeneratePath + fileName + ".xlsx", 
+                excelDataList, 
                 LifeUserViolationExcelVO.class);
-
+        
         // 上传到OSS并返回URL
         return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
     }
@@ -605,18 +624,18 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
     /**
      * 将 LifeUserViolationVo 转换为 LifeUserViolationExcelVO
      *
-     * @param vo           源对象
+     * @param vo 源对象
      * @param serialNumber 序号
-     * @param formatter    日期格式化器
+     * @param formatter 日期格式化器
      * @return Excel VO对象
      */
     private LifeUserViolationExcelVO convertToExcelVO(LifeUserViolationVo vo, int serialNumber, DateTimeFormatter formatter) {
         LifeUserViolationExcelVO excelVO = new LifeUserViolationExcelVO();
         BeanUtils.copyProperties(vo, excelVO);
-
+        
         excelVO.setId(serialNumber);
         excelVO.setNickname(vo.getNickName());
-
+        
         // 处理举报凭证图片(安全分割,避免数组越界)
         if (StringUtils.isNotEmpty(vo.getReportEvidenceImg())) {
             String[] imageParts = vo.getReportEvidenceImg().split(",");
@@ -624,7 +643,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
                 excelVO.setReportEvidenceImg(imageParts[0].trim());
             }
         }
-
+        
         // 处理举报内容类型
         if (StringUtils.isNotEmpty(vo.getReportContextType())) {
             try {
@@ -634,7 +653,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
                 excelVO.setReportContextType(vo.getReportContextType());
             }
         }
-
+        
         // 处理举报理由
         if (StringUtils.isNotEmpty(vo.getViolationType())) {
             try {
@@ -644,7 +663,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
                 excelVO.setViolationType(vo.getViolationType());
             }
         }
-
+        
         // 处理处理状态
         if (StringUtils.isNotEmpty(vo.getProcessingStatus())) {
             try {
@@ -654,7 +673,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
                 excelVO.setProcessingStatus(vo.getProcessingStatus());
             }
         }
-
+        
         // 格式化创建时间
         if (Objects.nonNull(vo.getCreatedTime())) {
             try {
@@ -667,7 +686,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
                 log.warn("格式化创建时间失败,createdTime={}, error={}", vo.getCreatedTime(), e.getMessage());
             }
         }
-
+        
         // 格式化处理时间
         if (Objects.nonNull(vo.getProcessingTime())) {
             try {
@@ -680,7 +699,7 @@ public class LifeUserViolationServiceImpl extends ServiceImpl<LifeUserViolationM
                 log.warn("格式化处理时间失败,processingTime={}, error={}", vo.getProcessingTime(), e.getMessage());
             }
         }
-
+        
         return excelVO;
     }
 

+ 71 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreVerificationCodeServiceImpl.java

@@ -0,0 +1,71 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.StoreVerificationCode;
+import shop.alien.mapper.StoreVerificationCodeMapper;
+import shop.alien.store.service.StoreVerificationCodeService;
+
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * 短信验证码 service 实现
+ */
+@Service
+@RequiredArgsConstructor
+public class StoreVerificationCodeServiceImpl extends ServiceImpl<StoreVerificationCodeMapper, StoreVerificationCode>
+        implements StoreVerificationCodeService {
+
+    @Override
+    public IPage<StoreVerificationCode> pageList(String phone, Integer appType, Integer businessType, Integer pageNum, Integer pageSize) {
+        LambdaQueryWrapper<StoreVerificationCode> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StringUtils.isNotBlank(phone), StoreVerificationCode::getPhone, phone);
+        wrapper.eq(null != appType, StoreVerificationCode::getAppType, appType);
+        wrapper.eq(null != businessType, StoreVerificationCode::getBusinessType, businessType);
+        wrapper.eq(StoreVerificationCode::getDeleteFlag, 0);
+        wrapper.orderByDesc(StoreVerificationCode::getCreatedTime);
+        Page<StoreVerificationCode> page = new Page<>(null == pageNum ? 1 : pageNum, null == pageSize ? 10 : pageSize);
+        return this.page(page, wrapper);
+    }
+
+    @Override
+    public StoreVerificationCode create(StoreVerificationCode request) {
+        request.setId(null);
+        Date now = new Date();
+        request.setCreatedTime(now);
+        request.setUpdatedTime(now);
+        request.setDeleteFlag(0);
+        this.save(request);
+        return request;
+    }
+
+    @Override
+    public StoreVerificationCode updateRecord(StoreVerificationCode request) {
+        if (Objects.isNull(request.getId())) {
+            throw new IllegalArgumentException("id不能为空");
+        }
+        request.setUpdatedTime(new Date());
+        this.updateById(request);
+        return this.getById(request.getId());
+    }
+
+    @Override
+    public boolean removeRecord(Integer id) {
+        if (Objects.isNull(id)) {
+            return false;
+        }
+        StoreVerificationCode entity = new StoreVerificationCode();
+        entity.setId(id);
+        entity.setDeleteFlag(1);
+        entity.setUpdatedTime(new Date());
+        return this.updateById(entity);
+    }
+
+}
+

+ 264 - 0
alien-store/src/main/java/shop/alien/store/util/AiUserViolationUtils.java

@@ -0,0 +1,264 @@
+package shop.alien.store.util;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+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.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.LifeMessage;
+import shop.alien.entity.store.LifeUser;
+import shop.alien.entity.store.LifeUserViolation;
+import shop.alien.entity.store.vo.LifeUserVo;
+import shop.alien.mapper.LifeMessageMapper;
+import shop.alien.mapper.LifeUserMapper;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AiUserViolationUtils {
+
+    private final RestTemplate restTemplate;
+    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-user-name.base-url:UdUser}")
+    private String userName;
+
+    @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";
+
+
+    /**
+     * 登录 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, LifeUserViolation lifeuserViolation) {
+        log.info("创建Ai服务任务...{}", userComplaintRecordUrl);
+
+        HttpHeaders analyzeHeaders = new HttpHeaders();
+        analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
+        analyzeHeaders.set("Authorization", "Bearer " + accessToken);
+
+        Map<String, Object> analyzeRequest = new HashedMap<>();
+        // 对应参数
+        if (lifeuserViolation.getViolationType().equals("1")){
+            analyzeRequest.put("complaint_type", "用户违规");
+        } else if (lifeuserViolation.getViolationType().equals("2")){
+            analyzeRequest.put("complaint_type", "色情低俗");
+        } else if (lifeuserViolation.getViolationType().equals("3")){
+            analyzeRequest.put("complaint_type", "违法违规");
+        } else if (lifeuserViolation.getViolationType().equals("5")){
+            analyzeRequest.put("complaint_type", "涉嫌诈骗");
+        } else if (lifeuserViolation.getViolationType().equals("6")){
+            analyzeRequest.put("complaint_type", "人身攻击");
+        } else if (lifeuserViolation.getViolationType().equals("7")){
+            analyzeRequest.put("complaint_type", "种族歧视");
+        } else if (lifeuserViolation.getViolationType().equals("4")){
+            analyzeRequest.put("complaint_type", "辱骂敏感");
+        } else if (lifeuserViolation.getViolationType().equals("12")){
+            analyzeRequest.put("complaint_type", "网络暴力");
+        } else {
+            analyzeRequest.put("complaint_type", "其他");
+        }
+
+        analyzeRequest.put("reporter_user_id", lifeuserViolation.getReportingUserId());
+        analyzeRequest.put("reported_user_id", lifeuserViolation.getReportedUserId());
+
+        //举报人
+        String reporterUserId = lifeuserViolation.getReportingUserId();
+        //被举报人
+        String reportedUserId = lifeuserViolation.getReportedUserId();
+
+        //通过双方userId查询用户信息,查出用户电话,
+        QueryWrapper<LifeUserVo> reporterUserQueryWrapper = new QueryWrapper<>();
+        reporterUserQueryWrapper.eq("id", reporterUserId);
+        LifeUserVo reporterUserById = lifeUserMapper.getUserById(reporterUserQueryWrapper);
+        QueryWrapper<LifeUserVo> reportedUserQueryWrapper = new QueryWrapper<>();
+        reportedUserQueryWrapper.eq("id", reportedUserId);
+        LifeUserVo reportedUserById = lifeUserMapper.getUserById(reportedUserQueryWrapper);
+        // 用user_去拼接,查询life_message表,获取双方两天记录
+        String userReporterUserById = "user_" + reporterUserById.getUserPhone();
+        String userReportedUserById = "user_" + reportedUserById.getUserPhone();
+
+        //将双方两天记录拼接成text,传入analyzeRequest
+        // 获取双方最近的聊天记录
+        List<LifeMessage> chatMessages = getRecentChatMessages(userReporterUserById, userReportedUserById);
+
+        // 将聊天记录转换为文本格式
+        String text = convertMessagesToText(chatMessages);
+        analyzeRequest.put("conversation_context", StringUtils.hasText(text) ? text : "");
+
+//        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(userComplaintRecordUrl, 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;
+    }
+
+    /**
+     * 获取双方用户的最近聊天记录
+     * @param userReporterUserById 举报人ID
+     * @param userReportedUserById 被举报人ID
+     * @return 聊天记录列表
+     */
+    public List<LifeMessage> getRecentChatMessages(String userReporterUserById, String userReportedUserById) {
+        // 构建查询条件
+        QueryWrapper<LifeMessage> queryWrapper = new QueryWrapper<>();
+
+        // 查询双方的聊天记录
+        queryWrapper.and(wrapper -> wrapper
+                        .eq("sender_id", userReporterUserById)
+                        .eq("receiver_id", userReportedUserById))
+                .or(wrapper -> wrapper
+                        .eq("sender_id", userReportedUserById)
+                        .eq("receiver_id", userReporterUserById));
+
+        // 按时间倒序排列
+        queryWrapper.orderByDesc("created_time");
+
+        // 限制50条记录
+        queryWrapper.last("LIMIT 50");
+
+        return lifeMessageMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * 将聊天消息列表转换为文本格式
+     * @param messages 聊天消息列表
+     * @return 格式化后的文本字符串
+     */
+    private String convertMessagesToText(List<LifeMessage> messages) {
+        if (messages == null || messages.isEmpty()) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        // 按时间顺序排列(因为数据库查询是倒序的,这里需要重新排序)
+        for (int i = messages.size() - 1; i >= 0; i--) {
+            LifeMessage message = messages.get(i);
+            // 格式:[时间] 发送者ID: 消息内容
+            sb.append("[").append(formatTime(message.getCreatedTime())).append("] ")
+                    .append(message.getSenderId()).append(": ")
+                    .append(message.getContent()).append("\n");
+        }
+
+        return sb.toString().trim();
+    }
+
+    /**
+     * 格式化时间显示
+     * @param date 时间
+     * @return 格式化后的时间字符串
+     */
+    private String formatTime(Date date) {
+        if (date == null) {
+            return "";
+        }
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        return sdf.format(date);
+    }
+}

+ 26 - 0
alien-store/src/main/java/shop/alien/store/util/ali/AliSms.java

@@ -9,10 +9,14 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
+import shop.alien.entity.store.StoreVerificationCode;
+import shop.alien.mapper.StoreVerificationCodeMapper;
 import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.config.NacosConfig;
 import shop.alien.util.common.RandomCreateUtil;
 
+import java.util.Date;
+
 /**
  * 阿里云验证码配置
  *
@@ -28,6 +32,8 @@ public class AliSms {
 
     private final NacosConfig nacosConfig;
 
+    private final StoreVerificationCodeMapper storeVerificationCodeMapper;
+
     @Value("${ali.sms.accessKeyId}")
     private String accessKeyId;
 
@@ -99,6 +105,7 @@ public class AliSms {
             if (nacosConfig.getTestPhone().contains(phone)) {
                 // 验证码发送成功,将验证码保存到redis中 设置60秒过期
                 baseRedisService.setString("verification_" + appTypeStr + "_" + businessTypeStr + "_" + phone, "123456", codeTimeOut);
+                saveVerificationCode(appType, businessType, phone, "123456");
                 return 123456;
             }
             // -----------------测试用手机号--------------------------------------------------------------------------------------------
@@ -129,6 +136,8 @@ public class AliSms {
             }
             // 验证码发送成功,将验证码保存到redis中 设置60秒过期
             baseRedisService.setString("verification_" + appTypeStr + "_" + businessTypeStr + "_" + phone, code.toString(), codeTimeOut);
+            saveVerificationCode(appType, businessType, phone, code.toString());
+
             return code;
         } catch (Exception e) {
             log.error("AliSmsConfig.sendSms ERROR Msg={}", e.getMessage());
@@ -210,4 +219,21 @@ public class AliSms {
         }
     }
 
+    private void saveVerificationCode(Integer appType, Integer businessType, String phone, String code) {
+        try {
+            StoreVerificationCode entity = new StoreVerificationCode();
+            entity.setAppType(appType);
+            entity.setBusinessType(businessType);
+            entity.setPhone(phone);
+            entity.setCode(code);
+            Date now = new Date();
+            entity.setCreatedTime(now);
+            entity.setUpdatedTime(now);
+            entity.setDeleteFlag(0);
+            storeVerificationCodeMapper.insert(entity);
+        } catch (Exception ex) {
+            log.error("AliSms.saveVerificationCode ERROR Msg={}", ex.getMessage(), ex);
+        }
+    }
+
 }

+ 1 - 1
alien-store/src/main/resources/bootstrap.yml

@@ -1,3 +1,3 @@
 spring:
   profiles:
-    active: dev
+    active: test