|
|
@@ -0,0 +1,622 @@
|
|
|
+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;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.util.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;
|
|
|
+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;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 装修需求动态表 服务实现类
|
|
|
+ *
|
|
|
+ * @author auto-generated
|
|
|
+ * @since 2025-01-15
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+@Transactional(rollbackFor = Exception.class)
|
|
|
+public class StoreRenovationRequirementServiceImpl extends ServiceImpl<StoreRenovationRequirementMapper, StoreRenovationRequirement> implements StoreRenovationRequirementService {
|
|
|
+
|
|
|
+ // 1. 自定义视频审核线程池(全局复用,避免频繁创建线程)
|
|
|
+ private ExecutorService videoAuditExecutor;
|
|
|
+
|
|
|
+ private final StoreInfoMapper storeInfoMapper;
|
|
|
+
|
|
|
+
|
|
|
+ private final LifeNoticeMapper lifeNoticeMapper;
|
|
|
+
|
|
|
+ private final WebSocketProcess webSocketProcess;
|
|
|
+
|
|
|
+ private final StoreUserMapper storeUserMapper;
|
|
|
+
|
|
|
+ private final AiContentModerationUtil aiContentModerationUtil;
|
|
|
+ private final AiVideoModerationUtil aiVideoModerationUtil;
|
|
|
+ // 定义图片后缀常量(不可修改,保证线程安全)
|
|
|
+ private static final Set<String> IMAGE_SUFFIXES = Collections.unmodifiableSet(
|
|
|
+ new HashSet<>(Arrays.asList(".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"))
|
|
|
+ );
|
|
|
+
|
|
|
+ // 定义视频后缀常量
|
|
|
+ private static final Set<String> VIDEO_SUFFIXES = Collections.unmodifiableSet(
|
|
|
+ new HashSet<>(Arrays.asList(".mp4", ".avi", ".mov", ".flv", ".mkv", ".wmv", ".mpeg"))
|
|
|
+ );
|
|
|
+
|
|
|
+ // 初始化线程池
|
|
|
+ @PostConstruct
|
|
|
+ public void initExecutor() {
|
|
|
+ // 核心参数根据业务调整,IO密集型任务线程数可设为CPU核心数*2~4
|
|
|
+ int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
|
|
|
+ int maxPoolSize = Runtime.getRuntime().availableProcessors() * 4;
|
|
|
+ videoAuditExecutor = new ThreadPoolExecutor(
|
|
|
+ corePoolSize,
|
|
|
+ maxPoolSize,
|
|
|
+ 60L,
|
|
|
+ TimeUnit.SECONDS,
|
|
|
+ new LinkedBlockingQueue<>(500), // 任务队列,避免无界队列溢出
|
|
|
+ new ThreadFactory() {
|
|
|
+ private int count = 0;
|
|
|
+ @Override
|
|
|
+ public Thread newThread(Runnable r) {
|
|
|
+ Thread t = new Thread(r);
|
|
|
+ t.setName("video-audit-" + count++); // 自定义线程名,便于排查
|
|
|
+ return t;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 任务拒绝策略:记录日志+调用者线程兜底(避免任务丢失)
|
|
|
+ new ThreadPoolExecutor.CallerRunsPolicy() {
|
|
|
+ @Override
|
|
|
+ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
|
|
|
+ log.error("视频审核任务被拒绝,队列已满!当前队列大小:{},活跃线程数:{}",
|
|
|
+ e.getQueue().size(), e.getActiveCount());
|
|
|
+ super.rejectedExecution(r, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 优雅关闭线程池
|
|
|
+ @PreDestroy
|
|
|
+ public void destroyExecutor() {
|
|
|
+ if (Objects.nonNull(videoAuditExecutor)) {
|
|
|
+ videoAuditExecutor.shutdown();
|
|
|
+ try {
|
|
|
+ if (!videoAuditExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
|
|
|
+ videoAuditExecutor.shutdownNow();
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ videoAuditExecutor.shutdownNow();
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean saveOrUpdateRequirement(StoreRenovationRequirementDto dto) {
|
|
|
+ log.info("StoreRenovationRequirementServiceImpl.saveOrUpdateRequirement?dto={}", dto);
|
|
|
+ try {
|
|
|
+ StoreRenovationRequirement requirement = new StoreRenovationRequirement();
|
|
|
+ BeanUtils.copyProperties(dto, requirement);
|
|
|
+
|
|
|
+ // 处理附件URL列表:List转逗号拼接字符串
|
|
|
+ if (dto.getAttachmentUrls() != null && !dto.getAttachmentUrls().isEmpty()) {
|
|
|
+ String attachmentUrlsStr = String.join(",", dto.getAttachmentUrls());
|
|
|
+ requirement.setAttachmentUrls(attachmentUrlsStr);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取当前登录用户ID
|
|
|
+ Integer currentUserId = null;
|
|
|
+ try {
|
|
|
+ com.alibaba.fastjson.JSONObject userInfo = JwtUtil.getCurrentUserInfo();
|
|
|
+ if (userInfo != null) {
|
|
|
+ currentUserId = userInfo.getInteger("userId");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("获取当前用户ID失败: {}", e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (requirement.getId() == null) {
|
|
|
+ // 新增
|
|
|
+ requirement.setCreatedUserId(currentUserId);
|
|
|
+ requirement.setCreatedTime(new Date());
|
|
|
+ if (requirement.getStatus() == null) {
|
|
|
+ requirement.setStatus(0); // 默认草稿
|
|
|
+ }
|
|
|
+ if (requirement.getViewCount() == null) {
|
|
|
+ requirement.setViewCount(0);
|
|
|
+ }
|
|
|
+ if (requirement.getInquiryCount() == null) {
|
|
|
+ requirement.setInquiryCount(0);
|
|
|
+ }
|
|
|
+ if (requirement.getAuditStatus() == null) {
|
|
|
+ requirement.setAuditStatus(0); // 默认待审核
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 更新
|
|
|
+ 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 {
|
|
|
+ // 如果文本/图片审核通过,且没有视频需要审核,直接设置为审核通过
|
|
|
+ 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 {
|
|
|
+ // 调用审核接口,增加超时控制(避免接口挂死)
|
|
|
+ videoAuditResult = CompletableFuture.supplyAsync(
|
|
|
+ () -> aiVideoModerationUtil.auditVideos(urlCategoryMap.get("video")),
|
|
|
+ videoAuditExecutor
|
|
|
+ ).get();
|
|
|
+
|
|
|
+ // 审核不通过则更新状态和原因
|
|
|
+ if (Objects.nonNull(videoAuditResult) && !videoAuditResult.isPassed()) {
|
|
|
+ // 重新查询最新的requirement,避免并发覆盖
|
|
|
+ StoreRenovationRequirement latestRequirement = this.getById(requirement.getId());
|
|
|
+ if (Objects.isNull(latestRequirement)) {
|
|
|
+ log.error("视频审核后更新失败,requirement不存在,ID:{}", requirement.getId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ latestRequirement.setAuditStatus(2);
|
|
|
+ latestRequirement.setAuditReason(videoAuditResult.getFailureReason());
|
|
|
+ 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());
|
|
|
+ if (Objects.nonNull(latestRequirement)) {
|
|
|
+ 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());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ 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(exceptionMessage);
|
|
|
+ this.saveOrUpdate(latestRequirement);
|
|
|
+ // 发送审核异常通知(视频)
|
|
|
+ if (finalStoreUser != null && finalStoreUser.getPhone() != null) {
|
|
|
+ sendAuditNotification(finalStoreUser.getPhone(), 2, exceptionMessage, "video");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, videoAuditExecutor);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ requirement.setAuditTime(new Date());
|
|
|
+ boolean isSuccess = this.saveOrUpdate(requirement);
|
|
|
+
|
|
|
+ return isSuccess;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("保存装修需求失败: {}", e.getMessage(), e);
|
|
|
+ throw new RuntimeException("保存装修需求失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对URL列表进行分类(图片/视频/其他)
|
|
|
+ * @param attachmentUrls 原始URL列表
|
|
|
+ * @return 分类后的Map
|
|
|
+ */
|
|
|
+ private static Map<String, List<String>> classifyUrls(List<String> attachmentUrls) {
|
|
|
+ // 初始化分类容器
|
|
|
+ List<String> imageUrls = new ArrayList<>();
|
|
|
+ List<String> videoUrls = new ArrayList<>();
|
|
|
+ List<String> otherUrls = new ArrayList<>();
|
|
|
+
|
|
|
+ if (Objects.isNull(attachmentUrls) || attachmentUrls.isEmpty()) {
|
|
|
+ return buildResultMap(imageUrls, videoUrls, otherUrls);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (String url : attachmentUrls) {
|
|
|
+ // 空URL直接归为其他
|
|
|
+ if (url == null || url.trim().isEmpty()) {
|
|
|
+ otherUrls.add(url);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ String lowerUrl = url.toLowerCase().trim();
|
|
|
+ // 优先判断图片(避免URL中同时包含图片+视频后缀的极端情况)
|
|
|
+ boolean isImage = IMAGE_SUFFIXES.stream().anyMatch(lowerUrl::contains);
|
|
|
+ if (isImage) {
|
|
|
+ imageUrls.add(url); // 保留原始URL,而非小写后的
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否为视频
|
|
|
+ boolean isVideo = VIDEO_SUFFIXES.stream().anyMatch(lowerUrl::contains);
|
|
|
+ if (isVideo) {
|
|
|
+ videoUrls.add(url);
|
|
|
+ } else {
|
|
|
+ otherUrls.add(url);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return buildResultMap(imageUrls, videoUrls, otherUrls);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建分类结果Map
|
|
|
+ */
|
|
|
+ private static Map<String, List<String>> buildResultMap(List<String> imageUrls, List<String> videoUrls, List<String> otherUrls) {
|
|
|
+ Map<String, List<String>> resultMap = new HashMap<>(3);
|
|
|
+ resultMap.put("image", Collections.unmodifiableList(imageUrls)); // 返回不可修改列表,避免外部篡改
|
|
|
+ resultMap.put("video", Collections.unmodifiableList(videoUrls));
|
|
|
+ resultMap.put("other", Collections.unmodifiableList(otherUrls));
|
|
|
+ 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) {
|
|
|
+ log.info("StoreRenovationRequirementServiceImpl.getRequirementDetail?id={}", id);
|
|
|
+ StoreRenovationRequirement requirement = this.getById(id);
|
|
|
+ if (requirement == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ StoreRenovationRequirementDto dto = new StoreRenovationRequirementDto();
|
|
|
+ BeanUtils.copyProperties(requirement, dto);
|
|
|
+
|
|
|
+ // 处理附件URL字符串:逗号拼接转List
|
|
|
+ if (StringUtils.hasText(requirement.getAttachmentUrls())) {
|
|
|
+ List<String> attachmentUrls = Arrays.asList(requirement.getAttachmentUrls().split(","));
|
|
|
+ dto.setAttachmentUrls(attachmentUrls);
|
|
|
+ } else {
|
|
|
+ dto.setAttachmentUrls(new ArrayList<>());
|
|
|
+ }
|
|
|
+
|
|
|
+ return dto;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IPage<StoreRenovationRequirementDto> getRequirementPage(Page<StoreRenovationRequirementDto> page,
|
|
|
+ Integer storeId,
|
|
|
+ String city,
|
|
|
+ Integer renovationType,
|
|
|
+ Integer status,
|
|
|
+ Integer auditStatus,
|
|
|
+ Integer sortType) {
|
|
|
+ log.info("StoreRenovationRequirementServiceImpl.getRequirementPage?storeId={}, city={}, renovationType={}, status={}, auditStatus={}, sortType={}",
|
|
|
+ storeId, city, renovationType, status, auditStatus, sortType);
|
|
|
+
|
|
|
+ LambdaQueryWrapper<StoreRenovationRequirement> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getDeleteFlag, 0);
|
|
|
+
|
|
|
+ if (storeId != null) {
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getStoreId, storeId);
|
|
|
+ }
|
|
|
+ if (StringUtils.hasText(city)) {
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getCity, city);
|
|
|
+ }
|
|
|
+ if (renovationType != null) {
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getRenovationType, renovationType);
|
|
|
+ }
|
|
|
+ if (status != null) {
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getStatus, status);
|
|
|
+ }
|
|
|
+ if (auditStatus != null) {
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getAuditStatus, auditStatus);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 排序处理:1-最新发布, 2-价格最高, 3-面积最大
|
|
|
+ if (sortType == null || sortType == 1) {
|
|
|
+ // 默认:最新发布(按创建时间降序)
|
|
|
+ wrapper.orderByDesc(StoreRenovationRequirement::getCreatedTime);
|
|
|
+ } else if (sortType == 2) {
|
|
|
+ // 价格最高(按装修预算降序)
|
|
|
+ wrapper.orderByDesc(StoreRenovationRequirement::getRenovationBudget);
|
|
|
+ wrapper.orderByDesc(StoreRenovationRequirement::getCreatedTime); // 价格相同则按时间排序
|
|
|
+ } else if (sortType == 3) {
|
|
|
+ // 面积最大(按房屋面积降序)
|
|
|
+ wrapper.orderByDesc(StoreRenovationRequirement::getHouseArea);
|
|
|
+ wrapper.orderByDesc(StoreRenovationRequirement::getCreatedTime); // 面积相同则按时间排序
|
|
|
+ } else {
|
|
|
+ // 无效的排序类型,使用默认排序
|
|
|
+ wrapper.orderByDesc(StoreRenovationRequirement::getCreatedTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ IPage<StoreRenovationRequirement> requirementPage = this.page(new Page<>(page.getCurrent(), page.getSize()), wrapper);
|
|
|
+
|
|
|
+ // 转换为DTO
|
|
|
+ IPage<StoreRenovationRequirementDto> dtoPage = requirementPage.convert(requirement -> {
|
|
|
+ StoreRenovationRequirementDto dto = new StoreRenovationRequirementDto();
|
|
|
+ BeanUtils.copyProperties(requirement, dto);
|
|
|
+
|
|
|
+ // 处理附件URL字符串:逗号拼接转List
|
|
|
+ if (StringUtils.hasText(requirement.getAttachmentUrls())) {
|
|
|
+ List<String> attachmentUrls = Arrays.asList(requirement.getAttachmentUrls().split(","));
|
|
|
+ dto.setAttachmentUrls(attachmentUrls);
|
|
|
+ } else {
|
|
|
+ dto.setAttachmentUrls(new ArrayList<>());
|
|
|
+ }
|
|
|
+
|
|
|
+ return dto;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 批量查询商铺信息并填充到DTO
|
|
|
+ if (dtoPage.getRecords() != null && !dtoPage.getRecords().isEmpty()) {
|
|
|
+ // 收集所有的门店ID
|
|
|
+ List<Integer> storeIds = dtoPage.getRecords().stream()
|
|
|
+ .map(StoreRenovationRequirementDto::getStoreId)
|
|
|
+ .filter(id -> id != null)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 批量查询商铺信息
|
|
|
+ if (!storeIds.isEmpty()) {
|
|
|
+ List<StoreInfo> storeInfoList = storeInfoMapper.selectBatchIds(storeIds);
|
|
|
+ // 转换为Map,key为storeId,value为StoreInfo
|
|
|
+ Map<Integer, StoreInfo> storeInfoMap = storeInfoList.stream()
|
|
|
+ .collect(Collectors.toMap(StoreInfo::getId, storeInfo -> storeInfo, (v1, v2) -> v1));
|
|
|
+
|
|
|
+ // 填充商铺信息到DTO
|
|
|
+ dtoPage.getRecords().forEach(dto -> {
|
|
|
+ StoreInfo storeInfo = storeInfoMap.get(dto.getStoreId());
|
|
|
+ if (storeInfo != null) {
|
|
|
+ dto.setStoreName(storeInfo.getStoreName());
|
|
|
+ dto.setStoreTel(storeInfo.getStoreTel());
|
|
|
+ dto.setStoreAddress(storeInfo.getStoreAddress());
|
|
|
+ dto.setStoreBlurb(storeInfo.getStoreBlurb());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return dtoPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void incrementViewCount(Integer id) {
|
|
|
+ log.info("StoreRenovationRequirementServiceImpl.incrementViewCount?id={}", id);
|
|
|
+ LambdaUpdateWrapper<StoreRenovationRequirement> wrapper = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getId, id);
|
|
|
+ wrapper.setSql("view_count = view_count + 1");
|
|
|
+ this.update(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void incrementInquiryCount(Integer id) {
|
|
|
+ log.info("StoreRenovationRequirementServiceImpl.incrementInquiryCount?id={}", id);
|
|
|
+ LambdaUpdateWrapper<StoreRenovationRequirement> wrapper = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getId, id);
|
|
|
+ wrapper.setSql("inquiry_count = inquiry_count + 1");
|
|
|
+ this.update(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean updateStatus(Integer id, Integer status) {
|
|
|
+ log.info("StoreRenovationRequirementServiceImpl.updateStatus?id={}, status={}", id, status);
|
|
|
+ LambdaUpdateWrapper<StoreRenovationRequirement> wrapper = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getId, id);
|
|
|
+ wrapper.set(StoreRenovationRequirement::getStatus, status);
|
|
|
+ wrapper.set(StoreRenovationRequirement::getUpdatedTime, new Date());
|
|
|
+
|
|
|
+ try {
|
|
|
+ com.alibaba.fastjson.JSONObject userInfo = JwtUtil.getCurrentUserInfo();
|
|
|
+ if (userInfo != null) {
|
|
|
+ wrapper.set(StoreRenovationRequirement::getUpdatedUserId, userInfo.getInteger("userId"));
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("获取当前用户ID失败: {}", e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.update(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean auditRequirement(Integer id, Integer auditStatus, String auditReason, Integer auditUserId) {
|
|
|
+ log.info("StoreRenovationRequirementServiceImpl.auditRequirement?id={}, auditStatus={}, auditReason={}, auditUserId={}",
|
|
|
+ id, auditStatus, auditReason, auditUserId);
|
|
|
+
|
|
|
+ if (auditStatus == 2 && !StringUtils.hasText(auditReason)) {
|
|
|
+ throw new RuntimeException("审核失败时必须填写审核原因");
|
|
|
+ }
|
|
|
+
|
|
|
+ LambdaUpdateWrapper<StoreRenovationRequirement> wrapper = new LambdaUpdateWrapper<>();
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getId, id);
|
|
|
+ wrapper.set(StoreRenovationRequirement::getAuditStatus, auditStatus);
|
|
|
+ wrapper.set(StoreRenovationRequirement::getAuditTime, new Date());
|
|
|
+ wrapper.set(StoreRenovationRequirement::getAuditUserId, auditUserId);
|
|
|
+ wrapper.set(StoreRenovationRequirement::getUpdatedTime, new Date());
|
|
|
+
|
|
|
+ if (auditStatus == 2) {
|
|
|
+ wrapper.set(StoreRenovationRequirement::getAuditReason, auditReason);
|
|
|
+ } else {
|
|
|
+ wrapper.set(StoreRenovationRequirement::getAuditReason, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.update(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean deleteRequirement(Integer id) {
|
|
|
+ log.info("StoreRenovationRequirementServiceImpl.deleteRequirement?id={}", id);
|
|
|
+ return this.removeById(id);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<StoreRenovationRequirementDto> getRequirementListByStoreId(Integer storeId) {
|
|
|
+ log.info("StoreRenovationRequirementServiceImpl.getRequirementListByStoreId?storeId={}", storeId);
|
|
|
+ LambdaQueryWrapper<StoreRenovationRequirement> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getStoreId, storeId);
|
|
|
+ wrapper.eq(StoreRenovationRequirement::getDeleteFlag, 0);
|
|
|
+ wrapper.orderByDesc(StoreRenovationRequirement::getCreatedTime);
|
|
|
+
|
|
|
+ List<StoreRenovationRequirement> requirements = this.list(wrapper);
|
|
|
+
|
|
|
+ return requirements.stream().map(requirement -> {
|
|
|
+ StoreRenovationRequirementDto dto = new StoreRenovationRequirementDto();
|
|
|
+ BeanUtils.copyProperties(requirement, dto);
|
|
|
+
|
|
|
+ // 处理附件URL字符串:逗号拼接转List
|
|
|
+ if (StringUtils.hasText(requirement.getAttachmentUrls())) {
|
|
|
+ List<String> attachmentUrls = Arrays.asList(requirement.getAttachmentUrls().split(","));
|
|
|
+ dto.setAttachmentUrls(attachmentUrls);
|
|
|
+ } else {
|
|
|
+ dto.setAttachmentUrls(new ArrayList<>());
|
|
|
+ }
|
|
|
+
|
|
|
+ return dto;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|