|
|
@@ -1,5 +1,6 @@
|
|
|
package shop.alien.store.service.impl;
|
|
|
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
@@ -11,11 +12,17 @@ import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.util.StringUtils;
|
|
|
+import shop.alien.entity.store.LifeNotice;
|
|
|
import shop.alien.entity.store.StoreInfo;
|
|
|
import shop.alien.entity.store.StoreRenovationRequirement;
|
|
|
+import shop.alien.entity.store.StoreUser;
|
|
|
import shop.alien.entity.store.dto.StoreRenovationRequirementDto;
|
|
|
+import shop.alien.entity.store.vo.WebSocketVo;
|
|
|
+import shop.alien.mapper.LifeNoticeMapper;
|
|
|
import shop.alien.mapper.StoreInfoMapper;
|
|
|
import shop.alien.mapper.StoreRenovationRequirementMapper;
|
|
|
+import shop.alien.mapper.StoreUserMapper;
|
|
|
+import shop.alien.store.config.WebSocketProcess;
|
|
|
import shop.alien.store.service.StoreRenovationRequirementService;
|
|
|
import shop.alien.store.util.ai.AiContentModerationUtil;
|
|
|
import shop.alien.store.util.ai.AiVideoModerationUtil;
|
|
|
@@ -23,6 +30,7 @@ import shop.alien.util.common.JwtUtil;
|
|
|
|
|
|
import javax.annotation.PostConstruct;
|
|
|
import javax.annotation.PreDestroy;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
import java.util.*;
|
|
|
import java.util.concurrent.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
@@ -44,6 +52,13 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
|
|
|
|
|
|
private final StoreInfoMapper storeInfoMapper;
|
|
|
|
|
|
+
|
|
|
+ private final LifeNoticeMapper lifeNoticeMapper;
|
|
|
+
|
|
|
+ private final WebSocketProcess webSocketProcess;
|
|
|
+
|
|
|
+ private final StoreUserMapper storeUserMapper;
|
|
|
+
|
|
|
private final AiContentModerationUtil aiContentModerationUtil;
|
|
|
private final AiVideoModerationUtil aiVideoModerationUtil;
|
|
|
// 定义图片后缀常量(不可修改,保证线程安全)
|
|
|
@@ -150,17 +165,58 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
|
|
|
requirement.setUpdatedUserId(currentUserId);
|
|
|
requirement.setUpdatedTime(new Date());
|
|
|
}
|
|
|
+ StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, dto.getStoreId()).eq(StoreUser::getDeleteFlag, 0));
|
|
|
+
|
|
|
+ LifeNotice lifeMessage = new LifeNotice();
|
|
|
+ lifeMessage.setReceiverId("store_" + storeUser.getPhone());
|
|
|
+ String text = "您发布的装修动态已提交审核,请于1-3个工作日进行查看";
|
|
|
+ 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());
|
|
|
+ webSocketProcess.sendMessage("store_" + storeUser.getPhone(), JSONObject.from(websocketVo).toJSONString());
|
|
|
+
|
|
|
+
|
|
|
// TODO 审核文本和图片内容是否违规
|
|
|
// 一次遍历完成分类,避免多次流式处理
|
|
|
Map<String, List<String>> urlCategoryMap = classifyUrls(dto.getAttachmentUrls());
|
|
|
+ List<String> videoUrls = urlCategoryMap.get("video");
|
|
|
// 1.调用文本+图片审核接口 ai为同步接口
|
|
|
AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(requirement.getDetailedRequirement(), urlCategoryMap.get("image"));
|
|
|
if (!auditResult.isPassed()) {
|
|
|
requirement.setAuditStatus(2);
|
|
|
requirement.setAuditReason(auditResult.getFailureReason());
|
|
|
+ // 发送审核失败通知(文本图片)
|
|
|
+ if (storeUser != null && storeUser.getPhone() != null) {
|
|
|
+ sendAuditNotification(storeUser.getPhone(), 2, auditResult.getFailureReason(), "text_image");
|
|
|
+ }
|
|
|
+
|
|
|
} else {
|
|
|
- // 异步调用视频审核接口,图片审核通过后调用(核心优化)
|
|
|
- CompletableFuture.runAsync(() -> {
|
|
|
+ // 如果文本/图片审核通过,且没有视频需要审核,直接设置为审核通过
|
|
|
+ if (videoUrls == null || videoUrls.isEmpty()) {
|
|
|
+ requirement.setAuditStatus(1);
|
|
|
+ requirement.setAuditReason(null);
|
|
|
+ // 发送审核通过通知(文本图片,无视频)
|
|
|
+ if (storeUser != null && storeUser.getPhone() != null) {
|
|
|
+ sendAuditNotification(storeUser.getPhone(), 1, null, "text_image");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 异步调用视频审核接口,图片审核通过后调用(核心优化)
|
|
|
+ // 保存storeUser信息供异步任务使用
|
|
|
+ final StoreUser finalStoreUser = storeUser;
|
|
|
+ CompletableFuture.runAsync(() -> {
|
|
|
AiVideoModerationUtil.VideoAuditResult videoAuditResult = null;
|
|
|
try {
|
|
|
// 调用审核接口,增加超时控制(避免接口挂死)
|
|
|
@@ -182,6 +238,11 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
|
|
|
this.saveOrUpdate(latestRequirement);
|
|
|
log.info("视频审核不通过,已更新状态,requirementID:{},原因:{}",
|
|
|
requirement.getId(), videoAuditResult.getFailureReason());
|
|
|
+ // 发送审核不通过通知(视频)
|
|
|
+ if (finalStoreUser != null && finalStoreUser.getPhone() != null) {
|
|
|
+ sendAuditNotification(finalStoreUser.getPhone(), 2, videoAuditResult.getFailureReason(), "video");
|
|
|
+ }
|
|
|
+
|
|
|
} else if (Objects.nonNull(videoAuditResult) && videoAuditResult.isPassed()) {
|
|
|
// 审核通过也更新状态(可选,根据业务需求)
|
|
|
StoreRenovationRequirement latestRequirement = this.getById(requirement.getId());
|
|
|
@@ -189,6 +250,11 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
|
|
|
latestRequirement.setAuditStatus(1);
|
|
|
latestRequirement.setAuditReason(null);
|
|
|
this.saveOrUpdate(latestRequirement);
|
|
|
+ // 发送审核通过通知(视频)
|
|
|
+ if (finalStoreUser != null && finalStoreUser.getPhone() != null) {
|
|
|
+ sendAuditNotification(finalStoreUser.getPhone(), 1, null, "video");
|
|
|
+ }
|
|
|
+
|
|
|
log.info("视频审核通过,已更新状态,requirementID:{}", requirement.getId());
|
|
|
}
|
|
|
}
|
|
|
@@ -196,12 +262,18 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
|
|
|
log.error("视频审核接口调用异常,requirementID:{}", requirement.getId(), e);
|
|
|
StoreRenovationRequirement latestRequirement = this.getById(requirement.getId());
|
|
|
if (Objects.nonNull(latestRequirement)) {
|
|
|
+ String exceptionMessage = "视频审核接口调用异常:" + e.getMessage();
|
|
|
latestRequirement.setAuditStatus(2);
|
|
|
- latestRequirement.setAuditReason("视频审核接口调用异常:" + e.getMessage()); // 实际需捕获具体异常信息
|
|
|
+ latestRequirement.setAuditReason(exceptionMessage);
|
|
|
this.saveOrUpdate(latestRequirement);
|
|
|
+ // 发送审核异常通知(视频)
|
|
|
+ if (finalStoreUser != null && finalStoreUser.getPhone() != null) {
|
|
|
+ sendAuditNotification(finalStoreUser.getPhone(), 2, exceptionMessage, "video");
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- }, videoAuditExecutor);
|
|
|
+ }, videoAuditExecutor);
|
|
|
+ }
|
|
|
}
|
|
|
requirement.setAuditTime(new Date());
|
|
|
boolean isSuccess = this.saveOrUpdate(requirement);
|
|
|
@@ -266,6 +338,72 @@ public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreReno
|
|
|
return resultMap;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 发送审核结果通知
|
|
|
+ * @param storePhone 商铺电话
|
|
|
+ * @param auditStatus 审核状态:1-通过,2-不通过
|
|
|
+ * @param auditReason 审核原因(不通过时提供)
|
|
|
+ * @param auditType 审核类型:text_image-文本图片审核,video-视频审核
|
|
|
+ */
|
|
|
+ private void sendAuditNotification(String storePhone, Integer auditStatus, String auditReason, String auditType) {
|
|
|
+ try {
|
|
|
+ if (storePhone == null || storePhone.trim().isEmpty()) {
|
|
|
+ log.warn("发送审核通知失败,商铺电话为空");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
|
|
|
+ String commonDate = sdf.format(new Date());
|
|
|
+
|
|
|
+ String title;
|
|
|
+ String message;
|
|
|
+ if (auditStatus == 1) {
|
|
|
+ // 审核通过
|
|
|
+ title = "审核通知";
|
|
|
+ if ("video".equals(auditType)) {
|
|
|
+ message = "在" + commonDate + ",您发布的装修动态视频审核已通过。";
|
|
|
+ } else {
|
|
|
+ message = "在" + commonDate + ",您发布的装修动态审核已通过。";
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 审核不通过
|
|
|
+ title = "审核通知";
|
|
|
+ String reasonText = auditReason != null && !auditReason.trim().isEmpty()
|
|
|
+ ? ",原因:" + auditReason
|
|
|
+ : "";
|
|
|
+ if ("video".equals(auditType)) {
|
|
|
+ message = "在" + commonDate + ",您发布的装修动态视频审核未通过" + reasonText + ",请修改后重新提交。";
|
|
|
+ } else {
|
|
|
+ message = "在" + commonDate + ",您发布的装修动态审核未通过" + reasonText + ",请修改后重新提交。";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ LifeNotice lifeNotice = new LifeNotice();
|
|
|
+ lifeNotice.setReceiverId("store_" + storePhone);
|
|
|
+ JSONObject jsonObject = new JSONObject();
|
|
|
+ jsonObject.put("message", message);
|
|
|
+ lifeNotice.setContext(jsonObject.toJSONString());
|
|
|
+ lifeNotice.setTitle(title);
|
|
|
+ lifeNotice.setSenderId("system");
|
|
|
+ lifeNotice.setIsRead(0);
|
|
|
+ lifeNotice.setNoticeType(1);
|
|
|
+ lifeNoticeMapper.insert(lifeNotice);
|
|
|
+
|
|
|
+ WebSocketVo websocketVo = new WebSocketVo();
|
|
|
+ websocketVo.setSenderId("system");
|
|
|
+ websocketVo.setReceiverId("store_" + storePhone);
|
|
|
+ websocketVo.setCategory("notice");
|
|
|
+ websocketVo.setNoticeType("1");
|
|
|
+ websocketVo.setIsRead(0);
|
|
|
+ websocketVo.setText(JSONObject.from(lifeNotice).toJSONString());
|
|
|
+ webSocketProcess.sendMessage("store_" + storePhone, JSONObject.from(websocketVo).toJSONString());
|
|
|
+
|
|
|
+ log.info("审核通知发送成功,商铺电话:{},审核状态:{},审核类型:{}", storePhone, auditStatus, auditType);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送审核通知异常,商铺电话:{},审核状态:{},审核类型:{}", storePhone, auditStatus, auditType, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
@Override
|
|
|
public StoreRenovationRequirementDto getRequirementDetail(Integer id) {
|