|
|
@@ -14,11 +14,16 @@ import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.cloud.context.config.annotation.RefreshScope;
|
|
|
+import org.springframework.http.HttpEntity;
|
|
|
import org.springframework.http.HttpHeaders;
|
|
|
import org.springframework.http.HttpStatus;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
import org.springframework.http.ResponseEntity;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
import org.springframework.web.multipart.MultipartRequest;
|
|
|
import shop.alien.entity.store.*;
|
|
|
@@ -31,6 +36,7 @@ import shop.alien.mapper.*;
|
|
|
import shop.alien.store.config.WebSocketProcess;
|
|
|
import shop.alien.store.service.StoreCommentAppealService;
|
|
|
import shop.alien.store.util.FileUploadUtil;
|
|
|
+import shop.alien.store.util.ai.AiAuthTokenUtil;
|
|
|
import shop.alien.util.common.netease.TextCheckUtil;
|
|
|
import shop.alien.util.common.safe.TextModerationResultVO;
|
|
|
import shop.alien.util.common.safe.TextModerationUtil;
|
|
|
@@ -52,6 +58,7 @@ import java.util.stream.Collectors;
|
|
|
@Service
|
|
|
@Transactional
|
|
|
@RequiredArgsConstructor
|
|
|
+@RefreshScope
|
|
|
public class StoreCommentAppealServiceImpl extends ServiceImpl<StoreCommentAppealMapper, StoreCommentAppeal> implements StoreCommentAppealService {
|
|
|
|
|
|
private final StoreCommentAppealMapper storeCommentAppealMapper;
|
|
|
@@ -85,6 +92,16 @@ public class StoreCommentAppealServiceImpl extends ServiceImpl<StoreCommentAppea
|
|
|
|
|
|
private final LifeLikeRecordMapper lifeLikeRecordMapper;
|
|
|
|
|
|
+ private final AiAuthTokenUtil aiAuthTokenUtil;
|
|
|
+
|
|
|
+ private final RestTemplate restTemplate;
|
|
|
+
|
|
|
+ @Value("${third-party-ai-analyze.base-url}")
|
|
|
+ private String analyzeUrl;
|
|
|
+
|
|
|
+ @Value("${third-party-ai-resultUrl.base-url}")
|
|
|
+ private String resultUrl;
|
|
|
+
|
|
|
/**
|
|
|
* 懒得查, 留着导出Excel
|
|
|
*/
|
|
|
@@ -324,10 +341,46 @@ public class StoreCommentAppealServiceImpl extends ServiceImpl<StoreCommentAppea
|
|
|
storeCommentAppealLogSystem.setDeleteFlag(0);
|
|
|
storeCommentAppealLogSystem.setCreatedTime(storeCommentAppeal.getCreatedTime());
|
|
|
boolean resultSystem = storeCommentAppealSave && storeCommentAppealLogMapper.insert(storeCommentAppealLogSystem) > 0;
|
|
|
+
|
|
|
+ // ========== 新增:调用AI分析接口并查询结果 ==========
|
|
|
+ if (result && resultSystem) {
|
|
|
+ try {
|
|
|
+ // 1. 调用AI分析接口提交分析任务
|
|
|
+ Integer recordId = invokeAiAnalyze(storeCommentAppeal);
|
|
|
+ if (recordId != null) {
|
|
|
+ // 2. 更新申诉记录,保存record_id
|
|
|
+ StoreCommentAppeal updateAppeal = new StoreCommentAppeal();
|
|
|
+ updateAppeal.setId(storeCommentAppeal.getId());
|
|
|
+ updateAppeal.setRecordId(recordId);
|
|
|
+ storeCommentAppealMapper.updateById(updateAppeal);
|
|
|
+
|
|
|
+ // 3. 立即查询AI分析结果
|
|
|
+ boolean queryResult = queryAiAnalysisResult(storeCommentAppeal.getId(), recordId);
|
|
|
+ if (queryResult) {
|
|
|
+ log.info("AI分析任务提交并查询结果成功,申诉ID: {},recordId: {}",
|
|
|
+ storeCommentAppeal.getId(), recordId);
|
|
|
+ } else {
|
|
|
+ log.warn("AI分析任务提交成功,但查询结果失败,申诉ID: {},recordId: {},将由定时任务后续查询",
|
|
|
+ storeCommentAppeal.getId(), recordId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新返回对象(recordId已通过BeanUtils.copyProperties复制)
|
|
|
+ } else {
|
|
|
+ log.warn("AI分析任务提交失败,但申诉记录已保存,申诉ID: {},状态保持: 待处理(0)",
|
|
|
+ storeCommentAppeal.getId());
|
|
|
+ // AI调用失败不影响申诉提交,保持原状态(0:待处理)
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("调用AI分析接口异常,申诉ID: {}", storeCommentAppeal.getId(), e);
|
|
|
+ // AI调用失败不影响申诉提交,保持原状态(0:待处理)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // ========== 新增代码结束 ==========
|
|
|
+
|
|
|
storeCommentAppealInfoVo.setResult(result && resultSystem ? 0 : 1);
|
|
|
} catch (Exception e) {
|
|
|
+ log.error("新增申诉异常", e);
|
|
|
storeCommentAppealInfoVo.setResult(1);
|
|
|
- return storeCommentAppealInfoVo;
|
|
|
}
|
|
|
return storeCommentAppealInfoVo;
|
|
|
}
|
|
|
@@ -669,4 +722,441 @@ public class StoreCommentAppealServiceImpl extends ServiceImpl<StoreCommentAppea
|
|
|
log.error("更新店铺评分失败,businessId={},error={}", businessId, e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调用AI分析接口提交申诉分析任务
|
|
|
+ *
|
|
|
+ * @param storeCommentAppeal 申诉记录
|
|
|
+ * @return recordId AI返回的记录ID,失败返回null
|
|
|
+ */
|
|
|
+ private Integer invokeAiAnalyze(StoreCommentAppeal storeCommentAppeal) {
|
|
|
+ log.info("开始调用AI差评申诉置信度分析接口,申诉ID: {}", storeCommentAppeal.getId());
|
|
|
+
|
|
|
+ // 1. 获取访问令牌(使用工具类)
|
|
|
+ String accessToken = aiAuthTokenUtil.getAccessToken();
|
|
|
+ if (!org.springframework.util.StringUtils.hasText(accessToken)) {
|
|
|
+ log.error("调用AI分析接口失败,获取accessToken失败,申诉ID: {}", storeCommentAppeal.getId());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 查询评论信息(CommonRating)
|
|
|
+ CommonRating rating = commonRatingMapper.selectById(storeCommentAppeal.getCommentId());
|
|
|
+ if (rating == null) {
|
|
|
+ log.error("查询评论信息失败,commentId: {}", storeCommentAppeal.getCommentId());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 构建请求参数
|
|
|
+ Map<String, Object> analyzeRequest = new HashMap<>();
|
|
|
+
|
|
|
+ // 商家申诉材料(必填)
|
|
|
+ analyzeRequest.put("merchant_material",
|
|
|
+ org.springframework.util.StringUtils.hasText(storeCommentAppeal.getAppealReason()) ?
|
|
|
+ storeCommentAppeal.getAppealReason() : "");
|
|
|
+
|
|
|
+ // 用户举证材料(必填)- 使用评价内容
|
|
|
+ analyzeRequest.put("user_material",
|
|
|
+ org.springframework.util.StringUtils.hasText(rating.getContent()) ? rating.getContent() : "");
|
|
|
+
|
|
|
+ // 商家图片:暂时传空数组,先测试文本分析流程
|
|
|
+ // TODO: 后续需要将图片转换为Base64
|
|
|
+ List<String> merchantImages = new ArrayList<>();
|
|
|
+ // if (org.springframework.util.StringUtils.hasText(storeCommentAppeal.getImgId())) {
|
|
|
+ // String[] imgIds = storeCommentAppeal.getImgId().split(",");
|
|
|
+ // for (String imgId : imgIds) {
|
|
|
+ // StoreImg storeImg = storeImgMapper.selectById(imgId.trim());
|
|
|
+ // if (storeImg != null && org.springframework.util.StringUtils.hasText(storeImg.getImgUrl())) {
|
|
|
+ // String base64 = convertImageToBase64(storeImg.getImgUrl());
|
|
|
+ // if (org.springframework.util.StringUtils.hasText(base64)) {
|
|
|
+ // merchantImages.add(base64);
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ analyzeRequest.put("merchant_images", merchantImages);
|
|
|
+
|
|
|
+ // 用户图片:暂时传空数组,先测试文本分析流程
|
|
|
+ // TODO: 后续需要将图片转换为Base64
|
|
|
+ List<String> userImages = new ArrayList<>();
|
|
|
+ // if (org.springframework.util.StringUtils.hasText(rating.getImageUrls())) {
|
|
|
+ // String[] imageUrls = rating.getImageUrls().split(",");
|
|
|
+ // for (String imageUrl : imageUrls) {
|
|
|
+ // String trimmedUrl = imageUrl.trim();
|
|
|
+ // if (org.springframework.util.StringUtils.hasText(trimmedUrl)) {
|
|
|
+ // String base64 = convertImageToBase64(trimmedUrl);
|
|
|
+ // if (org.springframework.util.StringUtils.hasText(base64)) {
|
|
|
+ // userImages.add(base64);
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ analyzeRequest.put("user_images", userImages);
|
|
|
+
|
|
|
+ // 其他字段(可选)
|
|
|
+ analyzeRequest.put("case_id", storeCommentAppeal.getCommentId() != null ?
|
|
|
+ storeCommentAppeal.getCommentId().toString() : "");
|
|
|
+ analyzeRequest.put("order_id", rating.getBusinessId() != null ?
|
|
|
+ rating.getBusinessId().toString() : "");
|
|
|
+
|
|
|
+ // 4. 设置请求头
|
|
|
+ HttpHeaders analyzeHeaders = new HttpHeaders();
|
|
|
+ analyzeHeaders.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ analyzeHeaders.set("Authorization", "Bearer " + accessToken);
|
|
|
+ HttpEntity<Map<String, Object>> analyzeEntity = new HttpEntity<>(analyzeRequest, analyzeHeaders);
|
|
|
+
|
|
|
+ // 5. 调用AI接口
|
|
|
+ try {
|
|
|
+ log.info("调用AI分析接口,URL: {},申诉ID: {},商家图片数: {},用户图片数: {}",
|
|
|
+ analyzeUrl, storeCommentAppeal.getId(), merchantImages.size(), userImages.size());
|
|
|
+ ResponseEntity<String> analyzeResp = restTemplate.postForEntity(analyzeUrl, analyzeEntity, String.class);
|
|
|
+
|
|
|
+ if (analyzeResp != null && analyzeResp.getStatusCodeValue() == 200) {
|
|
|
+ String analyzeBody = analyzeResp.getBody();
|
|
|
+ log.info("AI差评申诉置信度分析提交成功,申诉ID: {},返回: {}", storeCommentAppeal.getId(), analyzeBody);
|
|
|
+
|
|
|
+ JSONObject analyzeJson = JSONObject.parseObject(analyzeBody);
|
|
|
+ JSONObject dataJsonObj = analyzeJson.getJSONObject("data");
|
|
|
+
|
|
|
+ if (dataJsonObj != null) {
|
|
|
+ // 获取record_id用于后续查询
|
|
|
+ Integer recordId = dataJsonObj.getInteger("record_id");
|
|
|
+ if (recordId != null) {
|
|
|
+ log.info("AI分析任务提交成功,申诉ID: {},recordId: {}", storeCommentAppeal.getId(), recordId);
|
|
|
+ return recordId;
|
|
|
+ } else {
|
|
|
+ log.error("AI分析接口返回record_id为空,申诉ID: {},响应: {}",
|
|
|
+ storeCommentAppeal.getId(), analyzeBody);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.error("AI分析接口返回data为空,申诉ID: {},响应: {}",
|
|
|
+ storeCommentAppeal.getId(), analyzeBody);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.error("调用AI分析接口失败,申诉ID: {},http状态: {}",
|
|
|
+ storeCommentAppeal.getId(), analyzeResp != null ? analyzeResp.getStatusCode() : null);
|
|
|
+ }
|
|
|
+ } catch (org.springframework.web.client.HttpServerErrorException.ServiceUnavailable e) {
|
|
|
+ log.error("调用AI分析接口返回503 Service Unavailable错误,申诉ID: {},错误: {}",
|
|
|
+ storeCommentAppeal.getId(), e.getResponseBodyAsString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("调用AI分析接口异常,申诉ID: {}", storeCommentAppeal.getId(), e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询AI分析结果并更新申诉状态
|
|
|
+ * 差值>=10则同意,差值<10则驳回
|
|
|
+ *
|
|
|
+ * @param appealId 申诉ID
|
|
|
+ * @param recordId AI返回的记录ID
|
|
|
+ * @return 是否查询成功
|
|
|
+ */
|
|
|
+ private boolean queryAiAnalysisResult(Integer appealId, Integer recordId) {
|
|
|
+ log.info("开始查询AI分析结果,申诉ID: {},recordId: {}", appealId, recordId);
|
|
|
+
|
|
|
+ // 1. 获取访问令牌
|
|
|
+ String accessToken = aiAuthTokenUtil.getAccessToken();
|
|
|
+ if (!org.springframework.util.StringUtils.hasText(accessToken)) {
|
|
|
+ log.error("查询AI分析结果失败,获取accessToken失败,申诉ID: {},recordId: {}", appealId, recordId);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 构建查询URL
|
|
|
+ String completedUrl = buildCompletedUrl(recordId);
|
|
|
+
|
|
|
+ // 3. 设置请求头
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ headers.set("Authorization", "Bearer " + accessToken);
|
|
|
+ HttpEntity<String> requestEntity = new HttpEntity<>(headers);
|
|
|
+
|
|
|
+ try {
|
|
|
+ log.info("查询AI分析结果,URL: {},申诉ID: {},recordId: {}", completedUrl, appealId, recordId);
|
|
|
+ ResponseEntity<String> response = restTemplate.exchange(completedUrl,
|
|
|
+ org.springframework.http.HttpMethod.GET, requestEntity, String.class);
|
|
|
+
|
|
|
+ if (response != null && response.getStatusCodeValue() == 200) {
|
|
|
+ String responseBody = response.getBody();
|
|
|
+ log.info("AI分析结果查询返回,申诉ID: {},recordId: {},返回: {}", appealId, recordId, responseBody);
|
|
|
+
|
|
|
+ JSONObject responseJson = JSONObject.parseObject(responseBody);
|
|
|
+ Integer respCode = responseJson.getInteger("code");
|
|
|
+ JSONObject dataJsonObj = responseJson.getJSONObject("data");
|
|
|
+
|
|
|
+ // 检查AI响应码:202表示分析尚未完成
|
|
|
+ if (respCode != null && respCode == 202) {
|
|
|
+ // 从details中获取分析状态
|
|
|
+ JSONObject detailsObj = responseJson.getJSONObject("details");
|
|
|
+ String analysisStatus = detailsObj != null ? detailsObj.getString("analysis_status") : "unknown";
|
|
|
+ log.info("AI分析尚未完成(code=202),申诉ID: {},recordId: {},状态: {},将由定时任务后续查询",
|
|
|
+ appealId, recordId, analysisStatus);
|
|
|
+ return false; // 分析未完成,返回false,等待定时任务后续查询
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查AI响应码:200表示分析完成
|
|
|
+ if (respCode == null || respCode != 200) {
|
|
|
+ log.warn("AI分析接口返回非200响应码,申诉ID: {},recordId: {},code: {},message: {}",
|
|
|
+ appealId, recordId, respCode, responseJson.getString("message"));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dataJsonObj != null) {
|
|
|
+ // 检查分析状态
|
|
|
+ String analysisStatus = dataJsonObj.getString("analysis_status");
|
|
|
+ if (!"completed".equals(analysisStatus)) {
|
|
|
+ log.info("AI分析尚未完成,申诉ID: {},recordId: {},状态: {}",
|
|
|
+ appealId, recordId, analysisStatus);
|
|
|
+ return false; // 分析未完成,返回false,等待定时任务后续查询
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取置信度值
|
|
|
+ Double userConfidence = dataJsonObj.getDouble("user_confidence");
|
|
|
+ Double merchantConfidence = dataJsonObj.getDouble("merchant_confidence");
|
|
|
+
|
|
|
+ if (userConfidence == null || merchantConfidence == null) {
|
|
|
+ log.error("AI分析结果返回置信度为空,申诉ID: {},recordId: {},user_confidence: {},merchant_confidence: {}",
|
|
|
+ appealId, recordId, userConfidence, merchantConfidence);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算差值:差值>=10则同意,差值<10则驳回
|
|
|
+ double confidenceDiff = Math.abs(userConfidence - merchantConfidence);
|
|
|
+ log.info("AI分析结果,申诉ID: {},recordId: {},user_confidence: {},merchant_confidence: {},差值: {}",
|
|
|
+ appealId, recordId, userConfidence, merchantConfidence, confidenceDiff);
|
|
|
+
|
|
|
+ // 更新申诉状态
|
|
|
+ StoreCommentAppeal updateAppeal = new StoreCommentAppeal();
|
|
|
+ updateAppeal.setId(appealId);
|
|
|
+ updateAppeal.setAppealAiApproval(dataJsonObj.toJSONString());
|
|
|
+
|
|
|
+ if (confidenceDiff >= 10) {
|
|
|
+ // 差值>=10则同意
|
|
|
+ updateAppeal.setAppealStatus(2);
|
|
|
+ updateAppeal.setFinalResult("已同意");
|
|
|
+ log.info("AI分析结果:差值>=10,申诉通过,申诉ID: {},差值: {}", appealId, confidenceDiff);
|
|
|
+
|
|
|
+ // 审核通过后删除评价(common_rating)和评论(common_comment)
|
|
|
+ StoreCommentAppeal appeal = this.getById(appealId);
|
|
|
+ if (appeal != null) {
|
|
|
+ Integer ratingId = appeal.getCommentId(); // commentId 实际存储的是评价ID(common_rating.id)
|
|
|
+ if (ratingId != null) {
|
|
|
+ // 调用删除评价的逻辑(复用setAppealStatus中的逻辑)
|
|
|
+ deleteRatingAndComments(ratingId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 差值<10则驳回
|
|
|
+ updateAppeal.setAppealStatus(1);
|
|
|
+ updateAppeal.setFinalResult("已驳回");
|
|
|
+ log.info("AI分析结果:差值<10,申诉驳回,申诉ID: {},差值: {}", appealId, confidenceDiff);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新申诉记录
|
|
|
+ boolean updateResult = this.updateById(updateAppeal);
|
|
|
+
|
|
|
+ // 创建审核日志
|
|
|
+ StoreCommentAppealLog storeCommentAppealLog = new StoreCommentAppealLog();
|
|
|
+ storeCommentAppealLog.setAppealId(appealId);
|
|
|
+ if (confidenceDiff >= 10) {
|
|
|
+ storeCommentAppealLog.setProcessType(5); // 已同意
|
|
|
+ } else {
|
|
|
+ storeCommentAppealLog.setProcessType(4); // 已驳回
|
|
|
+ }
|
|
|
+ storeCommentAppealLog.setDeleteFlag(0);
|
|
|
+ storeCommentAppealLog.setCreatedTime(new Date());
|
|
|
+ storeCommentAppealLog.setLogRemark(String.format("AI自动审核:用户置信度=%.2f,商家置信度=%.2f,差值=%.2f",
|
|
|
+ userConfidence, merchantConfidence, confidenceDiff));
|
|
|
+ storeCommentAppealLogMapper.insert(storeCommentAppealLog);
|
|
|
+
|
|
|
+ // 发送通知给商家
|
|
|
+ sendAppealResultNotification(appealId, confidenceDiff >= 10);
|
|
|
+
|
|
|
+ return updateResult;
|
|
|
+ } else {
|
|
|
+ log.error("AI分析结果返回data为空,申诉ID: {},recordId: {},响应: {}",
|
|
|
+ appealId, recordId, responseBody);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.error("查询AI分析结果失败,申诉ID: {},recordId: {},http状态: {}",
|
|
|
+ appealId, recordId, response != null ? response.getStatusCode() : null);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("查询AI分析结果异常,申诉ID: {},recordId: {}", appealId, recordId, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建查询已完成分析结果的URL
|
|
|
+ *
|
|
|
+ * @param recordId AI返回的记录ID
|
|
|
+ * @return 完整的查询URL
|
|
|
+ */
|
|
|
+ private String buildCompletedUrl(Integer recordId) {
|
|
|
+ String baseUrl = resultUrl;
|
|
|
+ if (!org.springframework.util.StringUtils.hasText(baseUrl)) {
|
|
|
+ throw new IllegalStateException("差评申述分析接口地址未配置");
|
|
|
+ }
|
|
|
+ // 移除末尾的斜杠
|
|
|
+ if (baseUrl.endsWith("/")) {
|
|
|
+ baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
|
|
|
+ }
|
|
|
+ // 直接拼接 /{record_id}/completed
|
|
|
+ return baseUrl + "/" + recordId + "/completed";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除评价和评论(审核通过时调用)
|
|
|
+ *
|
|
|
+ * @param ratingId 评价ID
|
|
|
+ */
|
|
|
+ private void deleteRatingAndComments(Integer ratingId) {
|
|
|
+ try {
|
|
|
+ // 1. 删除评价(逻辑删除 common_rating 表)
|
|
|
+ CommonRating rating = commonRatingMapper.selectById(ratingId);
|
|
|
+ if (rating != null) {
|
|
|
+ int rows = commonRatingMapper.logicDeleteById(ratingId);
|
|
|
+ log.info("删除评价结果,ratingId={},影响行数={}", ratingId, rows);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 查询该评价下的所有评论ID(用于后续删除点赞记录)
|
|
|
+ LambdaQueryWrapper<CommonComment> commentQueryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ commentQueryWrapper.eq(CommonComment::getSourceType, CommentSourceTypeEnum.STORE_COMMENT.getType())
|
|
|
+ .eq(CommonComment::getSourceId, ratingId)
|
|
|
+ .eq(CommonComment::getDeleteFlag, 0);
|
|
|
+ List<CommonComment> comments = commonCommentMapper.selectList(commentQueryWrapper);
|
|
|
+ List<Long> commentIds = comments.stream().map(CommonComment::getId).collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 3. 删除该评价下的所有评论(使用原生 SQL 绕过 @TableLogic 限制)
|
|
|
+ int commentRows = commonCommentMapper.logicDeleteBySourceId(
|
|
|
+ CommentSourceTypeEnum.STORE_COMMENT.getType(), ratingId);
|
|
|
+ log.info("删除评价下的评论结果,ratingId={},影响行数={}", ratingId, commentRows);
|
|
|
+
|
|
|
+ // 4. 删除评价的点赞记录(type=7 表示评价点赞)
|
|
|
+ LambdaUpdateWrapper<LifeLikeRecord> ratingLikeWrapper = new LambdaUpdateWrapper<>();
|
|
|
+ ratingLikeWrapper.eq(LifeLikeRecord::getType, "7")
|
|
|
+ .eq(LifeLikeRecord::getHuifuId, String.valueOf(ratingId))
|
|
|
+ .eq(LifeLikeRecord::getDeleteFlag, 0);
|
|
|
+ ratingLikeWrapper.set(LifeLikeRecord::getDeleteFlag, 1);
|
|
|
+ ratingLikeWrapper.set(LifeLikeRecord::getUpdatedTime, new Date());
|
|
|
+ lifeLikeRecordMapper.update(null, ratingLikeWrapper);
|
|
|
+ log.info("删除评价的点赞记录,ratingId={}", ratingId);
|
|
|
+
|
|
|
+ // 5. 删除评论的点赞记录(type=1 表示评论点赞)
|
|
|
+ if (!commentIds.isEmpty()) {
|
|
|
+ for (Long commentId : commentIds) {
|
|
|
+ LambdaUpdateWrapper<LifeLikeRecord> commentLikeWrapper = new LambdaUpdateWrapper<>();
|
|
|
+ commentLikeWrapper.eq(LifeLikeRecord::getType, "1")
|
|
|
+ .eq(LifeLikeRecord::getHuifuId, String.valueOf(commentId))
|
|
|
+ .eq(LifeLikeRecord::getDeleteFlag, 0);
|
|
|
+ commentLikeWrapper.set(LifeLikeRecord::getDeleteFlag, 1);
|
|
|
+ commentLikeWrapper.set(LifeLikeRecord::getUpdatedTime, new Date());
|
|
|
+ lifeLikeRecordMapper.update(null, commentLikeWrapper);
|
|
|
+ }
|
|
|
+ log.info("删除评论的点赞记录,评论数={}", commentIds.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 重新统计店铺评分(删除评价后需要更新门店评分)
|
|
|
+ if (rating != null && rating.getBusinessType() == 1) {
|
|
|
+ updateStoreScore(rating.getBusinessId());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("删除评价和评论异常,ratingId={}", ratingId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送申诉结果通知给商家
|
|
|
+ *
|
|
|
+ * @param appealId 申诉ID
|
|
|
+ * @param approved 是否通过
|
|
|
+ */
|
|
|
+ private void sendAppealResultNotification(Integer appealId, boolean approved) {
|
|
|
+ try {
|
|
|
+ StoreCommentAppeal appeal = this.getById(appealId);
|
|
|
+ if (appeal == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ StoreInfo storeInfo = storeInfoMapper.selectById(appeal.getStoreId());
|
|
|
+ if (storeInfo == null || !org.springframework.util.StringUtils.hasText(storeInfo.getStoreTel())) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ StoreUser storeUser = storeUserMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<StoreUser>()
|
|
|
+ .eq(StoreUser::getStoreId, storeInfo.getId())
|
|
|
+ .eq(StoreUser::getDeleteFlag, 0));
|
|
|
+
|
|
|
+ if (storeUser == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
+ String commonDate = simpleDateFormat.format(new Date());
|
|
|
+ String text = "";
|
|
|
+ if (approved) {
|
|
|
+ text = "您在" + commonDate + "提交了申诉删除差评,经AI审核,确为不实评价,申诉成功,已将此条评价删除。";
|
|
|
+ } else {
|
|
|
+ text = "您在" + commonDate + "提交了申诉删除差评,经AI审核,确为顾客真实感受,申诉失败,此条评价不会被删除。";
|
|
|
+ }
|
|
|
+
|
|
|
+ LifeNotice lifeMessage = new LifeNotice();
|
|
|
+ lifeMessage.setReceiverId("store_" + storeUser.getPhone());
|
|
|
+ JSONObject jsonObject = new JSONObject();
|
|
|
+ jsonObject.put("message", text);
|
|
|
+ lifeMessage.setContext(jsonObject.toJSONString());
|
|
|
+ lifeMessage.setTitle("申诉删除差评");
|
|
|
+ lifeMessage.setSenderId("system");
|
|
|
+ lifeMessage.setIsRead(0);
|
|
|
+ lifeMessage.setNoticeType(1);
|
|
|
+ lifeNoticeMapper.insert(lifeMessage);
|
|
|
+
|
|
|
+ WebSocketVo websocketVo = new WebSocketVo();
|
|
|
+ websocketVo.setSenderId("system");
|
|
|
+ websocketVo.setReceiverId("store_" + storeUser.getPhone());
|
|
|
+ websocketVo.setCategory("notice");
|
|
|
+ websocketVo.setNoticeType("1");
|
|
|
+ websocketVo.setIsRead(0);
|
|
|
+ websocketVo.setText(JSONObject.from(lifeMessage).toJSONString());
|
|
|
+ try {
|
|
|
+ webSocketProcess.sendMessage("store_" + storeUser.getPhone(), JSONObject.from(websocketVo).toJSONString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送WebSocket通知异常,申诉ID: {}", appealId, e);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送申诉结果通知异常,申诉ID: {}", appealId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将图片URL转换为Base64编码
|
|
|
+ *
|
|
|
+ * @param imageUrl 图片URL
|
|
|
+ * @return Base64编码字符串
|
|
|
+ */
|
|
|
+ private String convertImageToBase64(String imageUrl) {
|
|
|
+ if (!org.springframework.util.StringUtils.hasText(imageUrl)) {
|
|
|
+ log.warn("图片URL为空");
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 下载图片并转换为Base64
|
|
|
+ byte[] imageBytes = restTemplate.getForObject(imageUrl, 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 "";
|
|
|
+ }
|
|
|
}
|