|
|
@@ -0,0 +1,2288 @@
|
|
|
+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.alipay.api.domain.GoodsVO;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+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.lang.StringUtils;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import shop.alien.config.properties.RiskControlProperties;
|
|
|
+import shop.alien.entity.SecondVideoTask;
|
|
|
+import shop.alien.entity.second.*;
|
|
|
+import shop.alien.entity.second.enums.RiskControlRuleTypeEnum;
|
|
|
+import shop.alien.entity.second.enums.SecondGoodsStatusEnum;
|
|
|
+import shop.alien.entity.second.enums.SecondUserCreditScoreEnum;
|
|
|
+import shop.alien.entity.second.vo.*;
|
|
|
+import shop.alien.entity.store.*;
|
|
|
+import shop.alien.entity.store.vo.LifeUserVo;
|
|
|
+import shop.alien.entity.store.vo.WebSocketVo;
|
|
|
+import shop.alien.mapper.*;
|
|
|
+import shop.alien.mapper.second.*;
|
|
|
+import shop.alien.second.feign.AlienStoreFeign;
|
|
|
+import shop.alien.second.service.*;
|
|
|
+import shop.alien.util.common.Constants;
|
|
|
+import shop.alien.util.common.VideoUtils;
|
|
|
+import shop.alien.util.common.safe.*;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 二手商品服务实现类
|
|
|
+ * 提供二手商品的增删改查、审核、上下架、风控等相关业务逻辑处理
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class SecondGoodsServiceImpl extends ServiceImpl<SecondGoodsMapper, SecondGoods> implements SecondGoodsService {
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 视频审核功能是否启用的配置项
|
|
|
+ */
|
|
|
+ @Value("${video.moderation.enabled}")
|
|
|
+ private boolean videoModerationEnabled;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 视频审核失败时是否阻塞商品发布的配置项
|
|
|
+ */
|
|
|
+ @Value("${video.moderation.block-on-failure}")
|
|
|
+ private boolean videoModerationBlockOnFailure;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 视频审核服务,用于处理商品中视频内容的审核
|
|
|
+ */
|
|
|
+ private final VideoModerationService videoModerationService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 视频工具类,提供视频相关操作功能
|
|
|
+ */
|
|
|
+ private final VideoUtils videoUtils;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 文本审核工具,用于审核商品标题、描述等文本内容
|
|
|
+ */
|
|
|
+ private final TextModerationUtil textModerationUtil;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 图片审核工具,用于审核商品图片内容
|
|
|
+ */
|
|
|
+ private final ImageModerationUtil imageModerationUtil;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 二手商品Mapper,用于操作二手商品数据表
|
|
|
+ */
|
|
|
+ private final SecondGoodsMapper secondGoodsMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 二手商品审核Mapper,用于操作二手商品审核记录表
|
|
|
+ */
|
|
|
+ private final SecondGoodsAuditMapper secondGoodsAuditMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用户信息Mapper,用于操作用户信息表
|
|
|
+ */
|
|
|
+ private final LifeUserMapper lifeUserMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 公告Mapper,用于操作公告信息表
|
|
|
+ */
|
|
|
+ private final LifeNoticeMapper lifeNoticeMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 店铺图片Mapper,用于操作图片信息表
|
|
|
+ */
|
|
|
+ private final StoreImgMapper storeImgMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 黑名单Mapper,用于操作用户黑名单表
|
|
|
+ */
|
|
|
+ private final LifeBlacklistMapper lifeBlacklistMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 点赞记录Mapper,用于操作点赞记录表
|
|
|
+ */
|
|
|
+ private final LifeLikeRecordMapper lifeLikeRecordMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 收藏Mapper,用于操作收藏记录表
|
|
|
+ */
|
|
|
+ private final LifeCollectMapper lifeCollectMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 店铺服务Feign接口,用于跨服务调用
|
|
|
+ */
|
|
|
+ private final AlienStoreFeign alienStoreFeign;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 商品操作历史记录Mapper,用于操作商品操作记录表
|
|
|
+ */
|
|
|
+ private final SecondGoodsRecordMapper secondGoodsRecordMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 二手交易记录Mapper,用于操作二手交易记录表
|
|
|
+ */
|
|
|
+ @Autowired
|
|
|
+ private SecondTradeRecordMapper secondTradeRecordMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用户违规举报Mapper,用于操作用户举报记录表
|
|
|
+ */
|
|
|
+ private final LifeUserViolationMapper lifeUserViolationMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 字典Mapper,用于操作数据字典表
|
|
|
+ */
|
|
|
+ private final StoreDictionaryMapper storeDictionaryMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 平台二手交易服务,提供二手交易相关业务逻辑
|
|
|
+ */
|
|
|
+ private final PlatformSecondTradeService platformSecondTradeService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 风控配置属性,包含各种风控规则的配置参数
|
|
|
+ */
|
|
|
+ @Autowired
|
|
|
+ private RiskControlProperties riskControlProperties;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 风控服务,提供风控相关业务逻辑处理
|
|
|
+ */
|
|
|
+ private final RiskControlService riskControlService;
|
|
|
+
|
|
|
+ private final SecondGoodsAuditService secondGoodsAuditService;
|
|
|
+
|
|
|
+ private final SecondUserCreditMapper secondUserCreditMapper;
|
|
|
+ private final SecondUserCreditRecordMapper secondUserCreditRecordMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取商品操作记录详情(管理后台使用)
|
|
|
+ * @param recordId 商品操作记录ID
|
|
|
+ * @return 商品操作记录详情VO对象
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 获取商品操作记录详情(管理后台使用)
|
|
|
+ * @param recordId 商品操作记录ID
|
|
|
+ * @return 商品操作记录详情VO对象
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public SecondGoodsRecordDetailVo getAdminGoodsRecordDetail(Integer recordId) {
|
|
|
+ // 1. 获取商品操作记录基本信息
|
|
|
+ QueryWrapper<SecondGoodsRecord> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper
|
|
|
+ .eq("sg.id", recordId);
|
|
|
+ SecondGoodsRecord record = secondGoodsRecordMapper.selectGoodsRecordById(queryWrapper);
|
|
|
+ if (record == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 转换为VO对象
|
|
|
+ SecondGoodsRecordDetailVo detailVo = SecondGoodsRecordDetailVo.fromRecord(record);
|
|
|
+
|
|
|
+ if (record.getUserId() != null){
|
|
|
+ // 获取联系人
|
|
|
+ QueryWrapper<LifeUser> userQueryWrapper = new QueryWrapper<>();
|
|
|
+ userQueryWrapper.lambda()
|
|
|
+ .eq(LifeUser::getId, record.getUserId())
|
|
|
+ .eq(LifeUser::getDeleteFlag, Constants.DeleteFlag.NOT_DELETED);
|
|
|
+ LifeUser user = lifeUserMapper.selectOne(userQueryWrapper);
|
|
|
+
|
|
|
+ detailVo.setUserName(user.getUserName());
|
|
|
+ detailVo.setUserPhone(user.getUserPhone());
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 3. 获取商品图片列表
|
|
|
+ QueryWrapper<StoreImg> imageQueryWrapper = new QueryWrapper<>();
|
|
|
+ imageQueryWrapper.lambda()
|
|
|
+ .eq(StoreImg::getStoreId, record.getId())
|
|
|
+ .eq(StoreImg::getImgType, Constants.ImageType.SECOND_HAND_RECORD)
|
|
|
+ .eq(StoreImg::getDeleteFlag, Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .orderByAsc(StoreImg::getImgSort);
|
|
|
+ List<StoreImg> imageList = storeImgMapper.selectList(imageQueryWrapper);
|
|
|
+
|
|
|
+ // 4. 提取图片URL列表
|
|
|
+ if (CollectionUtil.isNotEmpty(imageList)) {
|
|
|
+ List<String> imageUrls = imageList.stream()
|
|
|
+ .map(StoreImg::getImgUrl)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<Map<String, Object>> imgList = processReportImages(imageList,2);
|
|
|
+ detailVo.setImgList(imgList);
|
|
|
+ detailVo.setImageUrls(imageUrls);
|
|
|
+ }
|
|
|
+
|
|
|
+ return detailVo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取商品详情(管理后台使用)
|
|
|
+ * @param goodsId 商品ID
|
|
|
+ * @return 商品详情VO对象
|
|
|
+ * @throws Exception 处理过程中可能抛出的异常
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 获取商品详情(管理后台使用)
|
|
|
+ * @param goodsId 商品ID
|
|
|
+ * @return 商品详情VO对象
|
|
|
+ * @throws Exception 处理过程中可能抛出的异常
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public SecondGoodsDetailVo getAdminGoodsDetail(Integer goodsId) throws Exception {
|
|
|
+ // 基本信息
|
|
|
+ SecondGoodsDetailVo detailVo = dealSecondGoodsInfo(goodsId);
|
|
|
+ // 3. 获取商品操作记录集合
|
|
|
+ QueryWrapper<SecondGoodsRecord> recordQueryWrapper = new QueryWrapper<>();
|
|
|
+ recordQueryWrapper.lambda()
|
|
|
+ .eq(SecondGoodsRecord::getGoodsId, goodsId)
|
|
|
+ .orderByDesc(SecondGoodsRecord::getCreatedTime);
|
|
|
+ List<SecondGoodsRecord> operationRecords = secondGoodsRecordMapper.selectdminGoodsList(recordQueryWrapper);
|
|
|
+ detailVo.setOperationRecords(operationRecords);
|
|
|
+
|
|
|
+ // 4. 获取商品交易记录集合
|
|
|
+ QueryWrapper<SecondTradeRecord> tradeQueryWrapper = new QueryWrapper<>();
|
|
|
+ tradeQueryWrapper.eq("goods_id", goodsId)
|
|
|
+ .orderByDesc("created_time");
|
|
|
+ List<SecondTradeRecord> tradeRecords = secondTradeRecordMapper.selectList(tradeQueryWrapper);
|
|
|
+ // 处理交易步骤 调取 PlatformSecondTradeServiceImpl.getOperationJsonList
|
|
|
+ List<SecondTradeRecordVo> secondTradeRecordVos = Lists.newArrayList();
|
|
|
+ if (CollectionUtil.isNotEmpty(tradeRecords)){
|
|
|
+ for (SecondTradeRecord tradeRecord : tradeRecords) {
|
|
|
+ SecondTradeRecordVo secondTradeRecordVo = new SecondTradeRecordVo();
|
|
|
+ BeanUtils.copyProperties(tradeRecord, secondTradeRecordVo);
|
|
|
+ // 交易节点
|
|
|
+ secondTradeRecordVo.setOperationJsonList(platformSecondTradeService.getOperationJsonList(tradeRecord.getId()));
|
|
|
+ // 获取联系人
|
|
|
+ QueryWrapper<LifeUser> userQueryWrapper = new QueryWrapper<>();
|
|
|
+ userQueryWrapper.lambda()
|
|
|
+ .eq(LifeUser::getId, secondTradeRecordVo.getBuyerId())
|
|
|
+ .eq(LifeUser::getDeleteFlag, Constants.DeleteFlag.NOT_DELETED);
|
|
|
+ LifeUser user = lifeUserMapper.selectOne(userQueryWrapper);
|
|
|
+ secondTradeRecordVo.setUserName(user.getUserName());
|
|
|
+ secondTradeRecordVo.setUserPhone(user.getUserPhone());
|
|
|
+ secondTradeRecordVos.add(secondTradeRecordVo);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ detailVo.setTradeRecords(secondTradeRecordVos);
|
|
|
+
|
|
|
+ // 5. 获取商品举报集合
|
|
|
+ QueryWrapper<LifeUserViolation> reportQueryWrapper = new QueryWrapper<>();
|
|
|
+ reportQueryWrapper.lambda()
|
|
|
+ .eq(LifeUserViolation::getGoodsId, goodsId)
|
|
|
+ .eq(LifeUserViolation::getReportContextType, "4") // 4:二手商品
|
|
|
+ .orderByDesc(LifeUserViolation::getCreatedTime);
|
|
|
+ List<LifeUserViolation> reports = lifeUserViolationMapper.selectList(reportQueryWrapper);
|
|
|
+
|
|
|
+ // 转换举报信息为SecondReportingVo
|
|
|
+ List<SecondReportingVo> reportingVos = convertReportsToVos(reports);
|
|
|
+ detailVo.setReports(reportingVos);
|
|
|
+
|
|
|
+ return detailVo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理商品信息(管理后台使用)
|
|
|
+ * @param goodsId 商品ID
|
|
|
+ * @return 商品详情VO对象
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public SecondGoodsDetailVo dealSecondGoodsInfo(Integer goodsId) {
|
|
|
+ SecondGoodsDetailVo SecondGoodsDetailVo = new SecondGoodsDetailVo();
|
|
|
+ QueryWrapper<SecondGoodsVo> goodsVoQueryWrapper = new QueryWrapper<>();
|
|
|
+ goodsVoQueryWrapper
|
|
|
+ .eq("sg.id", goodsId);
|
|
|
+ // 1. 获取商品基本信息
|
|
|
+ SecondGoodsVo goodsInfo = secondGoodsMapper.getGoodsById(goodsVoQueryWrapper);
|
|
|
+
|
|
|
+ // 2. 获取商品图片列表
|
|
|
+ QueryWrapper<StoreImg> imageQueryWrapper = new QueryWrapper<>();
|
|
|
+ imageQueryWrapper.lambda()
|
|
|
+ .eq(StoreImg::getStoreId, goodsId)
|
|
|
+ .eq(StoreImg::getImgType, Constants.ImageType.SECOND_HAND_GOODS)
|
|
|
+ .eq(StoreImg::getDeleteFlag, Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .orderByAsc(StoreImg::getImgSort);
|
|
|
+ List<StoreImg> imageList = storeImgMapper.selectList(imageQueryWrapper);
|
|
|
+ // 提取图片URL列表
|
|
|
+ if (CollectionUtil.isNotEmpty(imageList)) {
|
|
|
+ List<String> imageUrls = imageList.stream()
|
|
|
+ .map(StoreImg::getImgUrl)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<Map<String, Object>> imgList = processReportImages(imageList,2);
|
|
|
+ goodsInfo.setImgList(imgList);
|
|
|
+ goodsInfo.setImgUrl(imageUrls);
|
|
|
+ }
|
|
|
+ SecondGoodsDetailVo.setGoodsInfo(goodsInfo);
|
|
|
+ return SecondGoodsDetailVo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理商品记录信息
|
|
|
+ * @param goodsId 商品ID
|
|
|
+ * @return 商品VO对象
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public SecondGoodsVo dealSecondGoodsRecordInfo(Integer goodsId) {
|
|
|
+ QueryWrapper<SecondGoodsVo> goodsVoQueryWrapper = new QueryWrapper<>();
|
|
|
+ goodsVoQueryWrapper.eq("sg.id", goodsId);
|
|
|
+ // 1. 获取商品基本信息
|
|
|
+ SecondGoodsVo goodsInfo = secondGoodsMapper.getGoodsRecordById(goodsVoQueryWrapper);
|
|
|
+
|
|
|
+ // 2. 获取商品图片列表
|
|
|
+ QueryWrapper<StoreImg> imageQueryWrapper = new QueryWrapper<>();
|
|
|
+ imageQueryWrapper.lambda()
|
|
|
+ .eq(StoreImg::getStoreId, goodsId)
|
|
|
+ .eq(StoreImg::getImgType, Constants.ImageType.SECOND_HAND_RECORD)
|
|
|
+ .eq(StoreImg::getDeleteFlag, Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .orderByAsc(StoreImg::getImgSort);
|
|
|
+ List<StoreImg> imageList = storeImgMapper.selectList(imageQueryWrapper);
|
|
|
+ // 提取图片URL列表
|
|
|
+ if (CollectionUtil.isNotEmpty(imageList)) {
|
|
|
+ List<String> imageUrls = imageList.stream()
|
|
|
+ .map(StoreImg::getImgUrl)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<Map<String, Object>> imgList = processReportImages(imageList,2);
|
|
|
+ goodsInfo.setImgList(imgList);
|
|
|
+ goodsInfo.setImgUrl(imageUrls);
|
|
|
+ }
|
|
|
+ return goodsInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录商品操作历史
|
|
|
+ * @param goods 商品信息
|
|
|
+ * @param operationName 操作名称
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void recordGoodsOperation(SecondGoods goods,String operationName) {
|
|
|
+ try {
|
|
|
+ log.warn("开始创建操作历史: {},{} ", goods,operationName);
|
|
|
+ SecondGoodsRecord record = new SecondGoodsRecord();
|
|
|
+ record.setOperationName(operationName);
|
|
|
+ record.setGoodsId(goods.getId());
|
|
|
+ record.setUserId(goods.getUserId());
|
|
|
+ record.setTitle(goods.getTitle());
|
|
|
+ record.setDescription(goods.getDescription());
|
|
|
+ // 价格转换
|
|
|
+ if (goods.getAmount() != null) {
|
|
|
+ record.setPrice(goods.getAmount());
|
|
|
+ } else if (goods.getPrice() != null && !goods.getPrice().isEmpty()) {
|
|
|
+ try {
|
|
|
+ record.setPrice(new BigDecimal(goods.getPrice()));
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("转换商品价格时出错: {}", e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ record.setPosition(goods.getPosition());
|
|
|
+ record.setLikeCount(goods.getLikeCount());
|
|
|
+ record.setCollectCount(goods.getCollectCount());
|
|
|
+ record.setCategoryOneId(goods.getCategoryOneId());
|
|
|
+ record.setCategoryTwoId(goods.getCategoryTwoId());
|
|
|
+ record.setLabel(goods.getLabel());
|
|
|
+ record.setTopic(goods.getTopic());
|
|
|
+ record.setTradeId(goods.getTradeId());
|
|
|
+ record.setReleaseTime(goods.getReleaseTime());
|
|
|
+ record.setGoodsStatus(goods.getGoodsStatus());
|
|
|
+ record.setFailedReason(goods.getFailedReason());
|
|
|
+ record.setHomeImage(goods.getHomeImage());
|
|
|
+ record.setVideoTaskId(goods.getVideoTaskId());
|
|
|
+ record.setVideoFirstFrame(goods.getVideoFirstFrame());
|
|
|
+ record.setDeleteFlag(goods.getDeleteFlag());
|
|
|
+ record.setCreatedTime(goods.getCreatedTime());
|
|
|
+ record.setCreatedUserId(goods.getCreatedUserId());
|
|
|
+ record.setUpdatedTime(goods.getUpdatedTime());
|
|
|
+ record.setUpdatedUserId(goods.getUpdatedUserId());
|
|
|
+ record.setAddressText(goods.getAddressText());
|
|
|
+
|
|
|
+ secondGoodsRecordMapper.insert(record);
|
|
|
+ log.warn("创建操作历史结束: {} ", record);
|
|
|
+ // 保存图片信息
|
|
|
+ saveRecordGoodsImages(record);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("记录商品操作历史时发生异常", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存二手商品记录图片类型信息
|
|
|
+ *
|
|
|
+ * @param record 保存后的商品记录
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 保存二手商品记录图片类型信息
|
|
|
+ *
|
|
|
+ * @param record 保存后的商品记录
|
|
|
+ */
|
|
|
+ private void saveRecordGoodsImages(SecondGoodsRecord record ) {
|
|
|
+
|
|
|
+ log.info("创建操作历图片史开始: {} ", record);
|
|
|
+ // 获取商品图片列表
|
|
|
+ QueryWrapper<StoreImg> query = new QueryWrapper<>();
|
|
|
+ query.lambda().eq(StoreImg::getStoreId, record.getGoodsId())
|
|
|
+ .eq(StoreImg::getImgType, Constants.ImageType.SECOND_HAND_GOODS);
|
|
|
+ List<StoreImg> storeImgs = storeImgMapper.selectList(query);
|
|
|
+ // 保存前先把原有的删除
|
|
|
+ storeImgs.forEach(storeImgModel -> {
|
|
|
+ StoreImg storeImg = new StoreImg();
|
|
|
+ storeImg.setStoreId(record.getId());
|
|
|
+ storeImg.setImgType(Constants.ImageType.SECOND_HAND_RECORD);
|
|
|
+ storeImg.setImgSort(storeImgModel.getImgSort());
|
|
|
+ storeImg.setImgDescription("二手商品记录图片类型");
|
|
|
+ storeImg.setDeleteFlag(Constants.DeleteFlag.NOT_DELETED);
|
|
|
+ storeImg.setCreatedTime(new Date());
|
|
|
+ storeImg.setUpdatedTime(new Date());
|
|
|
+ storeImg.setCreatedUserId(1);
|
|
|
+ storeImg.setUpdatedUserId(1);
|
|
|
+ storeImg.setImgUrl(storeImgModel.getImgUrl());
|
|
|
+ // 保存图片 插入store_img数据库
|
|
|
+ storeImgMapper.insert(storeImg);
|
|
|
+ log.info("创建操作历图片结束: {} ", storeImg);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量转换举报信息为SecondReportingVo对象
|
|
|
+ * @param reports 举报信息列表
|
|
|
+ * @return SecondReportingVo列表
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 批量转换举报信息为SecondReportingVo对象
|
|
|
+ * @param reports 举报信息列表
|
|
|
+ * @return SecondReportingVo列表
|
|
|
+ */
|
|
|
+ private List<SecondReportingVo> convertReportsToVos(List<LifeUserViolation> reports) {
|
|
|
+ List<SecondReportingVo> reportingVos = new ArrayList<>();
|
|
|
+
|
|
|
+ // 获取所有相关的举报类型字典信息
|
|
|
+ List<String> dictTypes = reports.stream()
|
|
|
+ .map(LifeUserViolation::getDictType)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ List<Integer> dictIds = reports.stream()
|
|
|
+ .map(LifeUserViolation::getDictId)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ Map<String, StoreDictionary> dictMap = new HashMap<>();
|
|
|
+ if (!dictTypes.isEmpty() && !dictIds.isEmpty()) {
|
|
|
+ QueryWrapper<StoreDictionary> dictQueryWrapper = new QueryWrapper<>();
|
|
|
+ dictQueryWrapper.lambda()
|
|
|
+ .in(StoreDictionary::getTypeName, dictTypes)
|
|
|
+ .in(StoreDictionary::getDictId, dictIds);
|
|
|
+ List<StoreDictionary> dicts = storeDictionaryMapper.selectList(dictQueryWrapper);
|
|
|
+ dictMap = dicts.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ d -> d.getTypeName() + "_" + d.getDictId(),
|
|
|
+ d -> d));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转换每个举报信息
|
|
|
+ for (LifeUserViolation report : reports) {
|
|
|
+ SecondReportingVo reportingVo = new SecondReportingVo();
|
|
|
+ BeanUtils.copyProperties(report, reportingVo);
|
|
|
+
|
|
|
+ // 查询用户表 根据举报用户类型和举报用户ID 查询 life_user 表 user_phone
|
|
|
+ LifeUser reporter = lifeUserMapper.selectById(report.getReportingUserId());
|
|
|
+ if (reporter != null) {
|
|
|
+ // 处理举报用户名称 life_user 表 user_name
|
|
|
+ reportingVo.setReportingUserName(reporter.getUserName());
|
|
|
+ // 处理联系方式 life_user 表 user_phone
|
|
|
+ reportingVo.setReportingUserPhone(reporter.getUserPhone());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 基本信息
|
|
|
+ reportingVo.setId(report.getId());
|
|
|
+ // 处理状态
|
|
|
+ reportingVo.setProcessingStatus(report.getProcessingStatus());
|
|
|
+
|
|
|
+ // 举报时间
|
|
|
+ reportingVo.setReportingTime(report.getCreatedTime());
|
|
|
+ // 举报内容补充
|
|
|
+ reportingVo.setReportingContext(report.getOtherReasonContent());
|
|
|
+// reportingVo.setFeedbackTime(report.getCreatedTime());
|
|
|
+// reportingVo.setFeedbackContext("平台已受理,感谢您的反馈!");
|
|
|
+
|
|
|
+ // 获取举报类型信息
|
|
|
+ StoreDictionary storeDictionary = dictMap.get(report.getDictType() + "_" + report.getDictId());
|
|
|
+ // 举报类型
|
|
|
+ reportingVo.setReportContextType(storeDictionary.getDictDetail());
|
|
|
+ // 二手商品举报
|
|
|
+ SecondGoods secondGoods = secondGoodsMapper.selectById(report.getBusinessId());
|
|
|
+ if (secondGoods != null) {
|
|
|
+ reportingVo.setPrice(secondGoods.getPrice() != null ?
|
|
|
+ secondGoods.getPrice().toString() : null);
|
|
|
+ reportingVo.setHomeImage(secondGoods.getHomeImage());
|
|
|
+ reportingVo.setTitle(secondGoods.getTitle());
|
|
|
+ reportingVo.setDescription(secondGoods.getDescription());
|
|
|
+ }
|
|
|
+ // 处理举报凭证图片
|
|
|
+ // 查询图片信息
|
|
|
+ LambdaQueryWrapper<StoreImg> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(StoreImg::getStoreId, report.getId());
|
|
|
+ wrapper.eq(StoreImg::getImgType, Constants.ImageType.SECOND_HAND_REPORT);
|
|
|
+ List<StoreImg> imgList = storeImgMapper.selectList(wrapper);
|
|
|
+ if (CollectionUtil.isNotEmpty(imgList)) {
|
|
|
+ // 提取图片URL
|
|
|
+ List<String> urlList = imgList.stream()
|
|
|
+ .map(StoreImg::getImgUrl)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ reportingVo.setImgList(processReportImages(imgList,1));
|
|
|
+ }
|
|
|
+
|
|
|
+ reportingVos.add(reportingVo);
|
|
|
+ }
|
|
|
+
|
|
|
+ return reportingVos;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理举报凭证图片
|
|
|
+ * @param imageList 图片URL集合
|
|
|
+ * @param type 类型 1-举报 2-商品
|
|
|
+ * @return 图片列表
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 处理举报凭证图片
|
|
|
+ * @param imageList 图片URL集合
|
|
|
+ * @param type 类型 1-举报 2-商品
|
|
|
+ * @return 图片列表
|
|
|
+ */
|
|
|
+ private List<Map<String, Object>> processReportImages(List<StoreImg> imageList, Integer type) {
|
|
|
+ List<Map<String, Object>> list = new ArrayList<>();
|
|
|
+ List<String> videoFileType = Arrays.asList("mp4", "avi", "flv", "mkv", "rmvb", "wmv", "3gp", "mov");
|
|
|
+
|
|
|
+ for (StoreImg img : imageList) {
|
|
|
+ // 过滤掉imgUrl为空或null的记录
|
|
|
+ if (img == null || StringUtils.isBlank(img.getImgUrl())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
+ String fileType = img.getImgUrl().substring(img.getImgUrl().lastIndexOf(".") + 1);
|
|
|
+ if (videoFileType.contains(fileType.toLowerCase())) {
|
|
|
+ map.put("type", "video");
|
|
|
+ } else {
|
|
|
+ map.put("type", "image");
|
|
|
+ }
|
|
|
+ map.put("imgUrl", img.getImgUrl());
|
|
|
+ if (type == 1) {
|
|
|
+ map.put("videoUrl", img.getImgUrl());
|
|
|
+ }
|
|
|
+ list.add(map);
|
|
|
+ }
|
|
|
+
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存商品为草稿状态
|
|
|
+ * @param goods 商品实体
|
|
|
+ * @return 是否成功保存
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean saveAsDraft(SecondGoodsVo goods) {
|
|
|
+ // 设置商品状态为草稿
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.DRAFT.getCode());
|
|
|
+ if (null != goods.getId()){
|
|
|
+ // 更新商品基本信息
|
|
|
+ if (!updateById(goods)) {
|
|
|
+ return false; // 保存失败直接返回
|
|
|
+ }
|
|
|
+ }else {
|
|
|
+ // 保存商品基本信息
|
|
|
+ if (!save(goods)) {
|
|
|
+ return false; // 保存失败直接返回
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 获取保存后的商品ID,用于后续业务处理
|
|
|
+ Integer savedGoodsId = goods.getId();
|
|
|
+ if (savedGoodsId == null) {
|
|
|
+ return false; // 如果获取不到ID,视为操作失败
|
|
|
+ }
|
|
|
+ // 保存商品图片信息
|
|
|
+ return saveStoreImages(savedGoodsId, goods);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建商品基本信息
|
|
|
+ * @param goodsDTO 商品信息DTO
|
|
|
+ * @param editFlag 编辑标识 1-编辑 0-新增
|
|
|
+ * @return 是否创建成功
|
|
|
+ * @throws Exception 处理过程中可能抛出的异常
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean createBasicInfo(SecondGoodsVo goodsDTO,Integer editFlag) throws Exception {
|
|
|
+ try {
|
|
|
+ // 实现基本信息保存逻辑
|
|
|
+ SecondGoods goods = new SecondGoods();
|
|
|
+ BeanUtils.copyProperties(goodsDTO, goods);
|
|
|
+
|
|
|
+ boolean saveResult;
|
|
|
+ if (editFlag == 1) {
|
|
|
+ goods.setId(goodsDTO.getId());
|
|
|
+// goods = secondGoodsMapper.selectById(goodsDTO.getId());
|
|
|
+ // 保存商品基本信息
|
|
|
+ saveResult = updateById(goods);
|
|
|
+ } else {
|
|
|
+ // 保存商品基本信息
|
|
|
+ saveResult = save(goods);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!saveResult) {
|
|
|
+ return false; // 保存失败直接返回
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取保存后的商品ID,用于后续业务处理
|
|
|
+ Integer savedGoodsId = goods.getId();
|
|
|
+ if (savedGoodsId == null) {
|
|
|
+ return false; // 如果获取不到ID,视为操作失败
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存商品图片信息
|
|
|
+ if (!saveStoreImages(savedGoodsId, goodsDTO )) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ goods = secondGoodsMapper.selectById(savedGoodsId);
|
|
|
+ // 审核不通过时已设置状态,返回成功但标记为审核失败
|
|
|
+ performContentReview(goodsDTO, goods);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 记录异常日志
|
|
|
+ log.error("创建或更新二手商品基本信息时发生异常", e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查用户在24小时内 频繁修改发布商品的数量是否超过限制
|
|
|
+ * @param goods 用户ID,商品ID
|
|
|
+ * @return 是否未超过限制
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 检查用户在24小时内 频繁修改发布商品的数量是否超过限制
|
|
|
+ * @param goods 用户ID,商品ID
|
|
|
+ * @return 是否未超过限制
|
|
|
+ */
|
|
|
+ private boolean checkUserPublishLimit(SecondGoods goods) {
|
|
|
+ // 获取配置的阈值
|
|
|
+ int publishLimit = riskControlProperties.getTradeFraud().getPublishCount24h();
|
|
|
+ // 时间窗口(小时)
|
|
|
+ int timeWindowHours = riskControlProperties.getTradeFraud().getTimeWindowHours();
|
|
|
+
|
|
|
+ // 计算时间窗口前的时间
|
|
|
+ Date timeWindowStart = new Date(System.currentTimeMillis() - timeWindowHours * 60 * 60 * 1000L);
|
|
|
+
|
|
|
+ // 查询用户在24小时内发布的商品数量
|
|
|
+ LambdaQueryWrapper<SecondGoodsRecord> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(SecondGoodsRecord::getUserId, goods.getUserId())
|
|
|
+ .eq(SecondGoodsRecord::getGoodsId, goods.getId())
|
|
|
+ .ge(SecondGoodsRecord::getReleaseTime, timeWindowStart)
|
|
|
+ .in(SecondGoodsRecord::getGoodsStatus,
|
|
|
+ SecondGoodsStatusEnum.LISTED.getCode(),
|
|
|
+ SecondGoodsStatusEnum.SOLD.getCode());
|
|
|
+ // 获取发布
|
|
|
+ List<SecondGoodsRecord> secondGoodsRecordList = secondGoodsRecordMapper.selectList(queryWrapper);
|
|
|
+ // 提取商品id集合
|
|
|
+ List<Integer> secondGoodsRecordIdList = secondGoodsRecordList.stream().map(SecondGoodsRecord::getId).collect(Collectors.toList());
|
|
|
+ // 转成json
|
|
|
+ String json = JSON.toJSONString(secondGoodsRecordIdList);
|
|
|
+ // 获取实际发布数量
|
|
|
+ int sameCategoryCount = secondGoodsRecordList.size();
|
|
|
+ // 如果发布数量超过限制,记录风控数据
|
|
|
+ if (sameCategoryCount > publishLimit) {
|
|
|
+ // "异常发布-同类商品发布频率"
|
|
|
+// riskControlService.recordRiskControlData(goods.getUserId(), RiskControlRuleTypeEnum.TRANSACTION_FRAUD.getRuleType(), RiskControlRuleTypeEnum.TRANSACTION_FRAUD.getDescription(), goods.getId().toString(),json);
|
|
|
+
|
|
|
+ String startDate = LocalDateTime.now().minusHours(24L).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
|
|
+ String endDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
|
|
+ List<SecondUserCreditRecord> num = secondUserCreditRecordMapper.selectList(new LambdaQueryWrapper<SecondUserCreditRecord>()
|
|
|
+ .eq(SecondUserCreditRecord::getUserId, goods.getUserId())
|
|
|
+ .eq(SecondUserCreditRecord::getPointsType, SecondUserCreditScoreEnum.TRADING_FRAUD.getCode())
|
|
|
+ .ge(SecondUserCreditRecord::getCreatedTime, startDate)
|
|
|
+ .le(SecondUserCreditRecord::getCreatedTime, endDate));
|
|
|
+ if (num.size() <= 0) {
|
|
|
+ LambdaQueryWrapper<SecondUserCredit> queryWrapper1 = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper1.eq(SecondUserCredit::getUserId, goods.getUserId())
|
|
|
+ .orderByDesc(SecondUserCredit::getCreatedTime).last("LIMIT 1");
|
|
|
+ SecondUserCredit secondUserCredit = secondUserCreditMapper.selectOne(queryWrapper1);
|
|
|
+ int score = secondUserCredit.getUserPoints() + SecondUserCreditScoreEnum.TRADING_FRAUD.getScore();
|
|
|
+ secondUserCredit.setUserPoints(score);
|
|
|
+ secondUserCreditMapper.updateById(secondUserCredit);
|
|
|
+
|
|
|
+ SecondUserCreditRecord record = new SecondUserCreditRecord();
|
|
|
+ record.setUserId(goods.getUserId());
|
|
|
+ record.setPointsType(SecondUserCreditScoreEnum.TRADING_FRAUD.getCode());
|
|
|
+ record.setPoints(SecondUserCreditScoreEnum.TRADING_FRAUD.getScore());
|
|
|
+ record.setCurrentScoreCount(score);
|
|
|
+ record.setCreatedTime(new Date());
|
|
|
+ secondUserCreditRecordMapper.insert(record);
|
|
|
+
|
|
|
+ // 小于100分封禁用户
|
|
|
+ if (score < 100) {
|
|
|
+ LifeUser user = new LifeUser();
|
|
|
+ user.setId(goods.getUserId());
|
|
|
+ user.setIsBanned(1);
|
|
|
+ lifeUserMapper.updateById(user);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查用户在指定时间窗口内发布同类商品的数量是否超过限制
|
|
|
+ * @param goods 商品信息
|
|
|
+ * @return 是否未超过限制
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 检查用户在指定时间窗口内发布同类商品的数量是否超过限制
|
|
|
+ * @param goods 商品信息
|
|
|
+ * @return 是否未超过限制
|
|
|
+ */
|
|
|
+ private boolean checkUserPublishSameCategoryLimit(SecondGoods goods) {
|
|
|
+ // 获取配置的阈值
|
|
|
+ int sameCategoryLimit = riskControlProperties.getAbnormalPublish().getSameCategoryCount24h();
|
|
|
+ int timeWindowHours = riskControlProperties.getAbnormalPublish().getTimeWindowHours();
|
|
|
+
|
|
|
+ // 计算时间窗口前的时间
|
|
|
+ Date timeWindowStart = new Date(System.currentTimeMillis() - timeWindowHours * 60 * 60 * 1000L);
|
|
|
+
|
|
|
+ // 查询用户在时间窗口内发布同类商品的数量
|
|
|
+ LambdaQueryWrapper<SecondGoods> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(SecondGoods::getUserId, goods.getUserId())
|
|
|
+ .eq(SecondGoods::getCategoryOneId, goods.getCategoryOneId())
|
|
|
+ .eq(SecondGoods::getCategoryTwoId, goods.getCategoryTwoId())
|
|
|
+ .ge(SecondGoods::getReleaseTime, timeWindowStart)
|
|
|
+ .in(SecondGoods::getGoodsStatus,
|
|
|
+ SecondGoodsStatusEnum.LISTED.getCode(),
|
|
|
+ SecondGoodsStatusEnum.SOLD.getCode());
|
|
|
+
|
|
|
+ // 获取发布
|
|
|
+ List<SecondGoods> secondGoodsList = list(queryWrapper);
|
|
|
+ // 提取商品id集合
|
|
|
+ List<Integer> secondGoodsIds = secondGoodsList.stream().map(SecondGoods::getId).collect(Collectors.toList());
|
|
|
+ // 转成json
|
|
|
+ String json = JSON.toJSONString(secondGoodsIds);
|
|
|
+ // 获取实际发布数量
|
|
|
+ int sameCategoryCount = secondGoodsList.size();
|
|
|
+ // 如果发布数量超过限制,记录风控数据
|
|
|
+ if (sameCategoryCount > sameCategoryLimit) {
|
|
|
+ // "异常发布-同类商品发布频率"
|
|
|
+// riskControlService.recordRiskControlData(goods.getUserId(), RiskControlRuleTypeEnum.ABNORMAL_PUBLISH.getRuleType(), RiskControlRuleTypeEnum.ABNORMAL_PUBLISH.getDescription(), goods.getId().toString(),json);
|
|
|
+
|
|
|
+ String startDate = LocalDateTime.now().minusHours(24L).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
|
|
+ String endDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
|
|
+ List<SecondUserCreditRecord> num = secondUserCreditRecordMapper.selectList(new LambdaQueryWrapper<SecondUserCreditRecord>()
|
|
|
+ .eq(SecondUserCreditRecord::getUserId, goods.getUserId())
|
|
|
+ .eq(SecondUserCreditRecord::getPointsType, SecondUserCreditScoreEnum.ABNORMAL_RELEASE.getCode())
|
|
|
+ .ge(SecondUserCreditRecord::getCreatedTime, startDate)
|
|
|
+ .le(SecondUserCreditRecord::getCreatedTime, endDate));
|
|
|
+ if (num.size() <= 0) {
|
|
|
+ LambdaQueryWrapper<SecondUserCredit> queryWrapper1 = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper1.eq(SecondUserCredit::getUserId, goods.getUserId())
|
|
|
+ .orderByDesc(SecondUserCredit::getCreatedTime).last("LIMIT 1");
|
|
|
+ SecondUserCredit secondUserCredit = secondUserCreditMapper.selectOne(queryWrapper1);
|
|
|
+ int score = secondUserCredit.getUserPoints() + SecondUserCreditScoreEnum.ABNORMAL_RELEASE.getScore();
|
|
|
+ secondUserCredit.setUserPoints(score);
|
|
|
+ secondUserCreditMapper.updateById(secondUserCredit);
|
|
|
+
|
|
|
+ SecondUserCreditRecord record = new SecondUserCreditRecord();
|
|
|
+ record.setUserId(goods.getUserId());
|
|
|
+ record.setPointsType(SecondUserCreditScoreEnum.ABNORMAL_RELEASE.getCode());
|
|
|
+ record.setPoints(SecondUserCreditScoreEnum.ABNORMAL_RELEASE.getScore());
|
|
|
+ record.setCurrentScoreCount(score);
|
|
|
+ record.setCreatedTime(new Date());
|
|
|
+ secondUserCreditRecordMapper.insert(record);
|
|
|
+
|
|
|
+ // 小于100分封禁用户
|
|
|
+ if (score < 100) {
|
|
|
+ LifeUser user = new LifeUser();
|
|
|
+ user.setId(goods.getUserId());
|
|
|
+ user.setIsBanned(1);
|
|
|
+ lifeUserMapper.updateById(user);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行商品发布风控检查
|
|
|
+ * @param goods 商品信息
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void performPublishRiskCheck(SecondGoods goods) {
|
|
|
+ // 检查用户是否在24小时内发布同类商品超过阈值
|
|
|
+ if (!checkUserPublishSameCategoryLimit(goods)) {
|
|
|
+ log.warn("用户 {} 在24小时内发布同类商品次数超过限制", goods.getUserId());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查用户是否在24小时内发布商品超过阈值
|
|
|
+ if (!checkUserPublishLimit(goods)) {
|
|
|
+ log.warn("用户 {} 在24小时内发布商品次数超过限制", goods.getUserId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行内容审核
|
|
|
+ * @param goodsDTO 商品信息
|
|
|
+ * @param goods 商品实体
|
|
|
+ */
|
|
|
+ private void performContentReview(SecondGoodsVo goodsDTO, SecondGoods goods) throws Exception {
|
|
|
+ // 图片审核
|
|
|
+ boolean imageAuditResult = performImageReviews(goods, goodsDTO);
|
|
|
+ /* 添加截断,避免审核结果过多,三个顺序执行,第一个失败后直接返回,以此类推 */
|
|
|
+
|
|
|
+ // 审核失败。直接返回
|
|
|
+ if (!imageAuditResult) {
|
|
|
+ // 图片审核不通过,记录操作历史
|
|
|
+ recordGoodsOperation(goods,"图片审核失败");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 文本审核
|
|
|
+ boolean textAuditResult = performTextReview(goods, goodsDTO);
|
|
|
+ // 审核失败。直接返回
|
|
|
+ if (!textAuditResult) {
|
|
|
+ // 文本审核不通过,记录操作历史
|
|
|
+ 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("");
|
|
|
+ updateById(goods);
|
|
|
+ // 审核中审核记录
|
|
|
+ createGoodsAudit(goods, "", Constants.AuditStatus.UNDER_REVIEW);
|
|
|
+ // 审核中,记录操作历史
|
|
|
+// recordGoodsOperation(goods);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> videoUrls = extractVideoUrls(goodsDTO.getImgUrl());
|
|
|
+ if (videoUrls.isEmpty()) {
|
|
|
+ // ai 审核
|
|
|
+ secondGoodsAuditService.performSecondRoundReview(goods, goodsDTO);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 审核通过后上架商品
|
|
|
+ approveAndListGoods(goods);
|
|
|
+
|
|
|
+ // 检查用户是否在24小时内发布同类商品超过阈值
|
|
|
+ if (!checkUserPublishSameCategoryLimit(goods)) {
|
|
|
+ log.warn("用户 {} 在24小时内发布同类商品次数超过限制", goodsDTO.getUserId());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查用户是否在24小时内发布商品超过阈值
|
|
|
+ if (!checkUserPublishLimit(goods)) {
|
|
|
+ log.warn("用户 {} 在24小时内发布商品次数超过限制", goodsDTO.getUserId());
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建商品审核记录
|
|
|
+ * @param goods 商品信息
|
|
|
+ */
|
|
|
+// @Transactional(rollbackFor = Exception.class)
|
|
|
+ private void approveAndListGoods(SecondGoods goods) {
|
|
|
+ boolean isNotified = false;
|
|
|
+ try {
|
|
|
+ // 如果所有审核都通过,设置为上架状态
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.LISTED.getCode()); // 上架
|
|
|
+ goods.setFailedReason("");
|
|
|
+ goods.setReleaseTime(new Date()); // 上架时间
|
|
|
+ updateById(goods);
|
|
|
+ // 插入审核记录
|
|
|
+ createGoodsAudit(goods, "", Constants.AuditStatus.PASSED);
|
|
|
+ // 发送审核成功消息
|
|
|
+ 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 = "首次发布";
|
|
|
+ }
|
|
|
+ recordGoodsOperation(goods, operationName);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("商品上架过程中发生异常,执行回滚", e);
|
|
|
+ // 如果通知已发送但后续操作失败,需要补偿
|
|
|
+ if (isNotified) {
|
|
|
+ // 发送补偿消息,比如撤销通知或标记为异常状态
|
|
|
+// sendCompensationMessage(goods);
|
|
|
+ }
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行视频审核
|
|
|
+ * @param goods 商品信息
|
|
|
+ * @param goodsDTO 商品DTO信息
|
|
|
+ * @return 审核结果
|
|
|
+ */
|
|
|
+ private 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);
|
|
|
+ sendFailedMsg(goods);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return taskIds;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行文本审核
|
|
|
+ * @param goods 商品信息
|
|
|
+ * @param goodsDTO 商品DTO信息
|
|
|
+ * @return 审核结果
|
|
|
+ */
|
|
|
+ private 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);
|
|
|
+ // 发送审核失败消息
|
|
|
+ sendFailedMsg(goods);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行图片审核
|
|
|
+ * @param goods 商品信息
|
|
|
+ * @param goodsDTO 商品DTO信息
|
|
|
+ * @return 审核结果
|
|
|
+ */
|
|
|
+ private 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()) {
|
|
|
+ StringBuilder failReasonBuilder = new StringBuilder();
|
|
|
+ for (String imageUrl : imageUrls) {
|
|
|
+ List<String> imgServicesList = Lists.newArrayList();
|
|
|
+ // 内容治理检测 + AIGC图片风险检测
|
|
|
+ imgServicesList.add(ImageReviewServiceEnum.TONALITY_IMPROVE.getService());
|
|
|
+ imgServicesList.add(ImageReviewServiceEnum.AIGC_CHECK.getService());
|
|
|
+// imgServicesList.add(ImageReviewServiceEnum.IMG_QUERY_SECURITY_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);
|
|
|
+ // 发送审核失败消息
|
|
|
+ sendFailedMsg(goods);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行内容审核(图片、文本和视频)
|
|
|
+ * @param goods 商品信息
|
|
|
+ * @param goodsDTO 商品DTO信息
|
|
|
+ * @return 审核结果
|
|
|
+ */
|
|
|
+ private boolean performContentReviews(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()); // 审核失败
|
|
|
+ goods.setFailedReason("文本审核不通过:" + (textCheckResult.getRiskWords() != null ? textCheckResult.getRiskWords() : "存在高风险内容"));
|
|
|
+ // 插入审核记录
|
|
|
+ createGoodsAudit(goods, textCheckResult.getRiskWords(), Constants.AuditStatus.FAILED);
|
|
|
+ // 发送审核失败消息
|
|
|
+ sendFailedMsg(goods);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 图片审核(循环处理)
|
|
|
+ List<String> imageUrls = goodsDTO.getImgUrl();
|
|
|
+ if (!StringUtils.isEmpty(goodsDTO.getHomeImage())){
|
|
|
+ imageUrls.add(goodsDTO.getHomeImage());
|
|
|
+ }
|
|
|
+ if (imageUrls != null && !imageUrls.isEmpty()) {
|
|
|
+ StringBuilder failReasonBuilder = new StringBuilder();
|
|
|
+ for (String imageUrl : imageUrls) {
|
|
|
+ List<String> imgServicesList = Lists.newArrayList();
|
|
|
+ // TODO 后续配置到数据库 中
|
|
|
+ imgServicesList.add(ImageReviewServiceEnum.TONALITY_IMPROVE.getService());
|
|
|
+ imgServicesList.add(ImageReviewServiceEnum.AIGC_CHECK.getService());
|
|
|
+// imgServicesList.add(ImageReviewServiceEnum.IMG_QUERY_SECURITY_CHECK.getService());
|
|
|
+ ImageModerationResultVO response = imageModerationUtil.productPublishCheck(imageUrl,imgServicesList);
|
|
|
+ if ("high".equals(response.getRiskLevel())) {
|
|
|
+ // 图片审核不通过或存在高风险
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode()); // 审核失败
|
|
|
+ goods.setFailedReason("图片审核不通过:图片中包含" + (response.getDescriptions() != null ? response.getDescriptions() : "高风险内容"));
|
|
|
+ // 插入审核记录
|
|
|
+ createGoodsAudit(goods, response.getDescriptions(), Constants.AuditStatus.FAILED);
|
|
|
+ // 发送审核失败消息
|
|
|
+ sendFailedMsg(goods);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 视频审核
|
|
|
+ if (videoModerationEnabled) {
|
|
|
+ List<String> videoUrls = extractVideoUrls(goodsDTO.getImgUrl());
|
|
|
+ if (!videoUrls.isEmpty()) {
|
|
|
+ // 提交视频审核任务
|
|
|
+ List<String> taskIds = new ArrayList<>();
|
|
|
+ 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);
|
|
|
+ sendFailedMsg(goods);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果成功提交了视频审核任务,设置商品状态为审核中
|
|
|
+ if (!taskIds.isEmpty()) {
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.UNDER_REVIEW.getCode()); // 审核中
|
|
|
+ goods.setVideoTaskId(taskIds.get(0)); // 保存第一个任务ID到商品表
|
|
|
+ goods.setFailedReason("");
|
|
|
+ updateById(goods);
|
|
|
+ createGoodsAudit(goods, "", SecondGoodsStatusEnum.UNDER_REVIEW.getCode());
|
|
|
+ // 审核中,记录操作历史
|
|
|
+// recordGoodsOperation(goods);
|
|
|
+ return true; // 异步处理,直接返回
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果所有审核都通过,设置为上架状态
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.LISTED.getCode()); // 上架
|
|
|
+ goods.setFailedReason("");
|
|
|
+ goods.setReleaseTime(new Date()); // 上架时间
|
|
|
+ updateById(goods);
|
|
|
+ // 插入审核记录
|
|
|
+ createGoodsAudit(goods, "", Constants.AuditStatus.PASSED);
|
|
|
+ // 发送审核成功消息
|
|
|
+ sendMessage(goods);
|
|
|
+ // 审核成功,记录操作历史
|
|
|
+ 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 = "首次发布";
|
|
|
+ }
|
|
|
+ recordGoodsOperation(goods, operationName);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从图片URL列表中提取视频URL
|
|
|
+ * @param imageUrls 图片URL列表
|
|
|
+ * @return 视频URL列表
|
|
|
+ */
|
|
|
+ private 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建商品审核记录
|
|
|
+ *
|
|
|
+ * @param goods 商品
|
|
|
+ * @param failReason 审核结果
|
|
|
+ */
|
|
|
+ private 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());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存商品图片信息
|
|
|
+ * @param savedGoodsId 保存后的商品ID
|
|
|
+ * @param goods .getimgUrl 图片URL列表
|
|
|
+ * @return 保存结果
|
|
|
+ */
|
|
|
+ private boolean saveStoreImages(Integer savedGoodsId, SecondGoodsVo goods ) {
|
|
|
+ if (CollectionUtil.isEmpty(goods.getImgUrl())) {
|
|
|
+ return true; // 如果没有图片,则返回成功
|
|
|
+ }
|
|
|
+ // 处理 第一个值为视频地址,获取视频第一帧作为商品封面图
|
|
|
+ String coverImage = getCoverImageFromVideoOrImage(goods);
|
|
|
+ if (coverImage != null) {
|
|
|
+ // 更新商品表封面图字段
|
|
|
+ SecondGoods secondGoods = new SecondGoods();
|
|
|
+ secondGoods.setId(savedGoodsId);
|
|
|
+ secondGoods.setHomeImage(coverImage);
|
|
|
+ updateById(secondGoods);
|
|
|
+ }
|
|
|
+ // 保存前先把原有的删除
|
|
|
+ storeImgMapper.delete(new LambdaUpdateWrapper<StoreImg>().eq(StoreImg::getStoreId, savedGoodsId).eq(StoreImg::getImgType, Constants.ImageType.SECOND_HAND_GOODS));
|
|
|
+ // 批量保存图片信息
|
|
|
+ for(int i = 0; i < goods.getImgUrl().size(); i++){
|
|
|
+ StoreImg storeImg = new StoreImg();
|
|
|
+ storeImg.setStoreId(savedGoodsId);
|
|
|
+ storeImg.setImgType(Constants.ImageType.SECOND_HAND_GOODS);
|
|
|
+ storeImg.setImgSort(i);
|
|
|
+ storeImg.setImgDescription("发布二手商品图片");
|
|
|
+ storeImg.setDeleteFlag(Constants.DeleteFlag.NOT_DELETED);
|
|
|
+ storeImg.setCreatedTime(new Date());
|
|
|
+ storeImg.setUpdatedTime(new Date());
|
|
|
+ storeImg.setCreatedUserId(1);
|
|
|
+ storeImg.setUpdatedUserId(1);
|
|
|
+ storeImg.setImgUrl(goods.getImgUrl().get(i));
|
|
|
+ // 保存图片 插入store_img数据库
|
|
|
+ storeImgMapper.insert(storeImg);
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送消息
|
|
|
+ * @param goods 商品信息
|
|
|
+ */
|
|
|
+ private void sendMessage(SecondGoods goods) {
|
|
|
+ // 根据 goods.getUserId() 获取用户信息
|
|
|
+ LifeUser lifeUser = lifeUserMapper.selectById(goods.getUserId());
|
|
|
+ String phone = lifeUser.getUserPhone();
|
|
|
+ // 调取feign接口 发送消息 life_notice表
|
|
|
+ LifeNotice lifeNotice = new LifeNotice();
|
|
|
+ lifeNotice.setSenderId("system");
|
|
|
+ lifeNotice.setReceiverId("user_"+ phone);
|
|
|
+ lifeNotice.setBusinessId(goods.getAuditRecordId());
|
|
|
+ lifeNotice.setTitle("商品审核通知");
|
|
|
+ JSONObject jsonObject = new JSONObject();
|
|
|
+ jsonObject.put("goodsId", goods.getId());
|
|
|
+ jsonObject.put("status", "true");
|
|
|
+ jsonObject.put("message", "恭喜您的商品已发布成功。");
|
|
|
+ lifeNotice.setContext(jsonObject.toJSONString());
|
|
|
+ lifeNotice.setNoticeType(Constants.Notice.SYSTEM_NOTICE); // 系统通知
|
|
|
+ lifeNotice.setIsRead(0);
|
|
|
+ lifeNoticeMapper.insert(lifeNotice);
|
|
|
+ sendNotice("user_"+ phone, lifeNotice);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送通知消息
|
|
|
+ * @param receiverId 接收者ID
|
|
|
+ * @param lifeNotice 通知内容
|
|
|
+ */
|
|
|
+ private void sendNotice(String receiverId, LifeNotice lifeNotice) {
|
|
|
+ try {
|
|
|
+ WebSocketVo webSocketVo = new WebSocketVo();
|
|
|
+ webSocketVo.setSenderId("system");
|
|
|
+ webSocketVo.setReceiverId(receiverId);
|
|
|
+ webSocketVo.setCategory("notice");
|
|
|
+ webSocketVo.setNoticeType("1");
|
|
|
+ webSocketVo.setIsRead(0);
|
|
|
+ webSocketVo.setText(JSONObject.from(lifeNotice).toJSONString());
|
|
|
+ alienStoreFeign.sendMsgToClientByPhoneId(receiverId, JSONObject.from(webSocketVo).toJSONString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送消息通知失败,receiverId: {}", receiverId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送审核失败消息
|
|
|
+ * @param goods 商品审核信息
|
|
|
+ */
|
|
|
+ private void sendFailedMsg(SecondGoods goods) {
|
|
|
+ // 根据 goods.getUserId() 获取用户信息
|
|
|
+ LifeUser lifeUser = lifeUserMapper.selectById(goods.getUserId());
|
|
|
+ String phone = lifeUser.getUserPhone();
|
|
|
+ // 调取feign接口 发送消息 life_notice表
|
|
|
+ LifeNotice lifeNotice = new LifeNotice();
|
|
|
+ lifeNotice.setSenderId("system");
|
|
|
+ lifeNotice.setReceiverId("user_"+ phone);
|
|
|
+ lifeNotice.setBusinessId(goods.getAuditRecordId());
|
|
|
+ lifeNotice.setTitle("商品审核通知");
|
|
|
+ // TODO: 失败原因本期为固定文案,实际原因暂不保存
|
|
|
+
|
|
|
+ JSONObject jsonObject = new JSONObject();
|
|
|
+ jsonObject.put("goodsId", goods.getId());
|
|
|
+ jsonObject.put("status", "false");
|
|
|
+ jsonObject.put("message", "抱歉您的商品发布失败,图片或文字存在违规行为,请您修改后重新发布。");
|
|
|
+ lifeNotice.setContext(jsonObject.toJSONString());
|
|
|
+ lifeNotice.setNoticeType(Constants.Notice.SYSTEM_NOTICE); // 系统通知
|
|
|
+ lifeNotice.setIsRead(0);
|
|
|
+ lifeNoticeMapper.insert(lifeNotice);
|
|
|
+ sendNotice("user_"+ phone, lifeNotice);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断并获取商品封面图(若第一个链接是视频,则取第一帧)
|
|
|
+ * @param goodsVo imgUrl 商品图片/视频链接列表
|
|
|
+ * @return 封面图URL或处理后的图像标识
|
|
|
+ */
|
|
|
+ public String getCoverImageFromVideoOrImage(SecondGoodsVo goodsVo) {
|
|
|
+ if (goodsVo.getImgUrl() == null || goodsVo.getImgUrl().isEmpty()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ String firstUrl = goodsVo.getImgUrl().get(0);
|
|
|
+ if (isVideoUrl(firstUrl)) {
|
|
|
+ // 如果是视频,获取第一帧作为封面图
|
|
|
+ return goodsVo.getHomeImage();
|
|
|
+// return extractFirstFrameAsCover(firstUrl);
|
|
|
+ } else {
|
|
|
+ // 否则直接返回第一个图片链接
|
|
|
+ return firstUrl;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断URL是否为视频地址(支持常见格式)
|
|
|
+ * @param url 输入URL
|
|
|
+ * @return 是否为视频地址
|
|
|
+ */
|
|
|
+ private 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 videoUrl 视频地址
|
|
|
+ * @return 封面图URL或Base64编码字符串
|
|
|
+ */
|
|
|
+ private String extractFirstFrameAsCover(String videoUrl) {
|
|
|
+ // 示例返回占位符
|
|
|
+ return videoUtils.getImg(videoUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取热销商品列表(前10)
|
|
|
+ * @return 热销商品列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<SecondGoods> getHotSellingRankingTop10() {
|
|
|
+ return secondGoodsMapper.getHotSellingRankingTop10();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取收藏商品列表(前10)
|
|
|
+ * @return 收藏商品列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<SecondGoods> getCollectTop10() {
|
|
|
+ return secondGoodsMapper.getCollectTop10();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取热销商品分页列表
|
|
|
+ * @param page 分页参数
|
|
|
+ * @return 热销商品分页列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoods> getHotSellingRanking(IPage<SecondGoods> page) {
|
|
|
+ return secondGoodsMapper.getHotSellingRanking(page);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据屏蔽类型获取屏蔽商品列表
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param shieldType 屏蔽类型
|
|
|
+ * @return 屏蔽商品分页列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoods> getShieldedGoodsListByType(IPage<SecondGoods> page, Integer userId, Integer shieldType) {
|
|
|
+ return secondGoodsMapper.getShieldedGoodsListByType(page, userId, shieldType);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 搜索商品列表
|
|
|
+ *
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param secondGoodsVo 搜索参数
|
|
|
+ * @return 商品列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoodsVo> searchGoodsList(IPage<SecondGoodsVo> page,Integer userId, SecondGoodsVo secondGoodsVo) {
|
|
|
+ // 获取商品屏蔽列表
|
|
|
+ List<SecondGoods> shieldedGoodsList = getShieldedGoodsList(userId);
|
|
|
+ // 提取屏蔽商品ID
|
|
|
+ List<Integer> shieldedGoodsIds = shieldedGoodsList.stream()
|
|
|
+ .map(SecondGoods::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ if (CollectionUtil.isEmpty(shieldedGoodsIds)) {
|
|
|
+ shieldedGoodsIds = Collections.emptyList();
|
|
|
+ }
|
|
|
+ // 获取拉黑列表
|
|
|
+ List<Integer> userIdList = lifeBlacklistMapper.getBlackList(userId);
|
|
|
+ if (CollectionUtil.isEmpty(userIdList)) {
|
|
|
+ userIdList = Collections.emptyList();
|
|
|
+ }
|
|
|
+ secondGoodsVo.setShieldedGoodsIds(shieldedGoodsIds);
|
|
|
+ secondGoodsVo.setUserIdList(userIdList);
|
|
|
+
|
|
|
+ // 获取搜索结果分页列表
|
|
|
+ IPage<SecondGoodsVo> searchGoodsList = getSecondGoodsVoIPage(page, secondGoodsVo, shieldedGoodsIds, userIdList);
|
|
|
+
|
|
|
+ // 判空
|
|
|
+ if (CollectionUtil.isNotEmpty(searchGoodsList.getRecords())) {
|
|
|
+ // 批量设置商品图片信息
|
|
|
+// batchSetGoodsImages(searchGoodsList);
|
|
|
+ // 批量设置用户信息
|
|
|
+ batchSetSearchUserInfo(searchGoodsList);
|
|
|
+ // 批量设置收藏状态
|
|
|
+ batchSetCollectStatus(searchGoodsList,secondGoodsVo.getUserPhone(), userId);
|
|
|
+ // 批量设置点赞状态
|
|
|
+ batchSetLikeStatus(searchGoodsList,secondGoodsVo.getUserPhone(), userId);
|
|
|
+ }
|
|
|
+ return searchGoodsList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询搜索结果
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param secondGoodsVo 查询参数
|
|
|
+ * @param shieldedGoodsIds 屏蔽商品id
|
|
|
+ * @param userIdList 屏蔽用户id
|
|
|
+ * @return IPage<SecondGoodsVo> 搜索结果
|
|
|
+ */
|
|
|
+ private IPage<SecondGoodsVo> getSecondGoodsVoIPage(IPage<SecondGoodsVo> page, SecondGoodsVo secondGoodsVo, List<Integer> shieldedGoodsIds, List<Integer> userIdList) {
|
|
|
+ QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.notIn(CollectionUtil.isNotEmpty(shieldedGoodsIds), "sg.id", shieldedGoodsIds)
|
|
|
+ .notIn(CollectionUtil.isNotEmpty(userIdList), "sg.user_id", userIdList)
|
|
|
+ .eq("sg.goods_status", SecondGoodsStatusEnum.LISTED.getCode())// 3-上架
|
|
|
+ .eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED);
|
|
|
+ // 添加对 searchData 的模糊查询
|
|
|
+ if (!StringUtils.isEmpty(secondGoodsVo.getSearchData())) {
|
|
|
+ String searchData = "%" + secondGoodsVo.getSearchData() + "%";
|
|
|
+ queryWrapper.and(wrapper -> wrapper
|
|
|
+ .like("sg.title", searchData)
|
|
|
+// .or()
|
|
|
+// .like("sg.label", searchData)
|
|
|
+// .or()
|
|
|
+// .like("sg.description", searchData)
|
|
|
+// .or()
|
|
|
+// .like("sgc1.category_name", searchData)
|
|
|
+// .or()
|
|
|
+// .like("sgc2.category_name", searchData)
|
|
|
+// .or()
|
|
|
+// .like("sg.topic", searchData)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ // 添加对 orderData 的排序 若不为空
|
|
|
+ if (!StringUtils.isEmpty(secondGoodsVo.getOrderData()) && secondGoodsVo.getOrderType() != null) {
|
|
|
+ // 正序
|
|
|
+ if (secondGoodsVo.getOrderType() == 1){
|
|
|
+ queryWrapper.orderByAsc(secondGoodsVo.getOrderData())
|
|
|
+ .orderByAsc("distance");
|
|
|
+ }
|
|
|
+ // 倒序
|
|
|
+ if (secondGoodsVo.getOrderType() == 2){
|
|
|
+ queryWrapper.orderByDesc(secondGoodsVo.getOrderData())
|
|
|
+ .orderByAsc("distance");
|
|
|
+ }
|
|
|
+ }else {
|
|
|
+ queryWrapper.orderByAsc("distance");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加对 releaseTime 的查询
|
|
|
+ if (secondGoodsVo.getReleaseTime() != null) {
|
|
|
+ queryWrapper.eq("sg.release_time", secondGoodsVo.getReleaseTime());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 返回分页结果
|
|
|
+ IPage<SecondGoodsVo> searchGoodsList = secondGoodsMapper.searchGoodsList(page, secondGoodsVo.getCurrentLatitude(), secondGoodsVo.getCurrentLongitude() ,queryWrapper);
|
|
|
+ searchGoodsList.getRecords().forEach(secondGoods -> {
|
|
|
+ secondGoods.setPosition(secondGoods.getSearchData());
|
|
|
+ });
|
|
|
+ return searchGoodsList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量设置收藏状态
|
|
|
+ * @param searchGoodsList 搜索结果列表
|
|
|
+ * @param userId 用户ID(登录用户)
|
|
|
+ * @param phone 手机号
|
|
|
+ */
|
|
|
+ private void batchSetLikeStatus(IPage<SecondGoodsVo> searchGoodsList, String phone, Integer userId) {
|
|
|
+ // 批量设置点赞状态
|
|
|
+ searchGoodsList.getRecords().forEach(goods -> {
|
|
|
+ if (userId != null) {
|
|
|
+ // 设置点赞状态
|
|
|
+ LambdaUpdateWrapper<LifeLikeRecord> updateWrapper = new LambdaUpdateWrapper<>();
|
|
|
+ updateWrapper.eq(LifeLikeRecord::getHuifuId, goods.getId())
|
|
|
+ .eq(LifeLikeRecord::getDianzanId, "user_"+phone).
|
|
|
+ eq(LifeLikeRecord::getType, Constants.LikeType.SECOND_HAND_GOODS); // 6-二手商品
|
|
|
+ if (lifeLikeRecordMapper.selectCount(updateWrapper) > 0) {
|
|
|
+ goods.setLikeStatus(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量设置收藏状态
|
|
|
+ * @param searchGoodsList 搜索结果列表
|
|
|
+ * @param userId 用户ID(登录用户)
|
|
|
+ * @param phone 手机号
|
|
|
+ */
|
|
|
+ private void batchSetCollectStatus(IPage<SecondGoodsVo> searchGoodsList,String phone, Integer userId) {
|
|
|
+ // 批量设置收藏状态
|
|
|
+ searchGoodsList.getRecords().forEach(goods -> {
|
|
|
+ if (userId != null) {
|
|
|
+ LambdaUpdateWrapper<LifeCollect> updateWrapper = new LambdaUpdateWrapper<>();
|
|
|
+ updateWrapper.eq(LifeCollect::getUserId, "user_"+phone)
|
|
|
+ .eq(LifeCollect::getBusinessType, Constants.CollectBusinessType.DEFAULT)
|
|
|
+ .eq(LifeCollect::getDeleteFlag, Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq(LifeCollect::getBusinessId, goods.getId());
|
|
|
+ if (lifeCollectMapper.selectCount(updateWrapper) > 0) {
|
|
|
+ goods.setCollectStatus(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取商品屏蔽列表
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @return 商品屏蔽列表
|
|
|
+ */
|
|
|
+ private List<SecondGoods> getShieldedGoodsList(Integer userId) {
|
|
|
+ // 调用mapper方法
|
|
|
+ return secondGoodsMapper.getShieldedGoodsList(userId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取商品详情
|
|
|
+ * @param id 商品ID
|
|
|
+ * @return 商品详情
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public SecondGoodsVo getSecondGoodsById(Integer id) {
|
|
|
+ QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("sg.id", id)
|
|
|
+ .eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED);
|
|
|
+ SecondGoodsVo secondGoods = secondGoodsMapper.getGoodsDetails(queryWrapper);
|
|
|
+ // 设置图片信息
|
|
|
+ QueryWrapper<StoreImg> query = new QueryWrapper<>();
|
|
|
+ query.lambda()
|
|
|
+ .eq(StoreImg::getImgType, Constants.ImageType.SECOND_HAND_GOODS) // 商品 图片
|
|
|
+ .eq(StoreImg::getDeleteFlag, Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .in(StoreImg::getStoreId, id);
|
|
|
+ List<StoreImg> storeImgs = storeImgMapper.selectList(query);
|
|
|
+ // 设置图片信息
|
|
|
+ if (CollectionUtil.isNotEmpty(storeImgs)) {
|
|
|
+ secondGoods.setImgUrl(storeImgs.stream().map(StoreImg::getImgUrl).collect(Collectors.toList()));
|
|
|
+ }
|
|
|
+ return secondGoods;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取屏蔽商品分页列表
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @return 屏蔽商品分页列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoodsVo> getShieldedGoodsPage(IPage<SecondGoodsVo> page, Integer userId) {
|
|
|
+ QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper
|
|
|
+ // 可以查看已删除的商品数据
|
|
|
+// .eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("ss.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("ss.user_id", userId)
|
|
|
+ .eq("ss.shield_type", 1)
|
|
|
+ .orderByDesc("ss.created_time");
|
|
|
+ return secondGoodsMapper.getShieldedGoodsPage(page, queryWrapper);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取收藏商品列表
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @return IPage<SecondGoodsVo> 收藏商品列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoodsVo> getCollectGoodsPage(IPage<SecondGoodsVo> page, int userId) {
|
|
|
+ LifeUser lifeUser = lifeUserMapper.selectById(userId);
|
|
|
+ // 获取商品屏蔽列表
|
|
|
+ List<SecondGoods> shieldedGoodsList = getShieldedGoodsList(userId);
|
|
|
+ // 提取屏蔽商品ID
|
|
|
+ List<Integer> shieldedGoodsIds = shieldedGoodsList.stream()
|
|
|
+ .map(SecondGoods::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper
|
|
|
+ // 可以查看已删除的商品数据
|
|
|
+// .eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("lc.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .notIn(CollectionUtil.isNotEmpty(shieldedGoodsIds), "sg.id", shieldedGoodsIds)
|
|
|
+ .eq("lc.user_id", "user_"+lifeUser.getUserPhone())
|
|
|
+ .orderByDesc("lc.created_time");
|
|
|
+ return secondGoodsMapper.getCollectGoodsPage(page, queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取购买商品列表
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @return IPage<SecondGoodsVo> 购买商品列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoodsVo> getBuyGoodsPage(IPage<SecondGoodsVo> page, int userId) {
|
|
|
+ QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper
|
|
|
+ // 可以查看已删除的商品数据
|
|
|
+// .eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("str.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("str.buyer_id", userId) // 买家ID
|
|
|
+ .eq("sg.goods_status", SecondGoodsStatusEnum.SOLD.getCode()) // 5-已售出
|
|
|
+ .eq("str.trade_status", Constants.TradeStatus.TRADE_SUCCESS) // 4-交易成功
|
|
|
+ .orderByDesc("str.created_time");
|
|
|
+ return secondGoodsMapper.getBuyGoodsPage(page, queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据商品状态获取商品列表 - 0:草稿 1:审核中 2:审核失败 3:已上架 4:已下架 5:已售出
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @return IPage<SecondGoodsVo> 出售商品列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoodsVo> getSellGoodsPage(IPage<SecondGoodsVo> page, SecondGoodsVo secondGoodsVo,int userId) {
|
|
|
+ QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("sg.user_id", userId)
|
|
|
+ .eq("sg.goods_status", secondGoodsVo.getGoodsStatus()); // 5-已售出 ,0-草稿
|
|
|
+ return secondGoodsMapper.getSellGoodsPage(page, queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取用户商品列表
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param userId 主页用户id
|
|
|
+ * @return IPage<SecondGoodsVo> 用户商品列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoodsVo> getUserGoodsPage(IPage<SecondGoodsVo> page, Double currentLatitude , Double currentLongitude, Integer userId) {
|
|
|
+ QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("sg.user_id", userId) // 主页用户ID
|
|
|
+ .in("sg.goods_status", SecondGoodsStatusEnum.LISTED.getCode(), SecondGoodsStatusEnum.SOLD.getCode()) // 3-上架 5-已售出
|
|
|
+ .orderByAsc("FIELD(sg.goods_status, 3, 5)")
|
|
|
+ .orderByDesc("sg.release_time");
|
|
|
+ return secondGoodsMapper.getUserGoodsPage(page, currentLatitude, currentLongitude, queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取我的商品列表
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @return IPage<SecondGoodsVo> 我的商品列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoodsVo> getMyGoodsPage(IPage<SecondGoodsVo> page, int userId) {
|
|
|
+ QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("sg.user_id", userId) // 用户ID
|
|
|
+ .in("sg.goods_status", SecondGoodsStatusEnum.REVIEW_FAILED.getCode(),
|
|
|
+ SecondGoodsStatusEnum.DELISTED.getCode(),
|
|
|
+ SecondGoodsStatusEnum.SOLD.getCode(),
|
|
|
+ SecondGoodsStatusEnum.LISTED.getCode()) // 3-上架 5-已售出
|
|
|
+ .orderByAsc("FIELD(sg.goods_status, 2, 4, 5, 3)")
|
|
|
+ .orderByDesc("sg.release_time");
|
|
|
+ return secondGoodsMapper.getMyGoodsPage(page, queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取草稿列表
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @return IPage<SecondGoodsVo> 草稿列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoodsVo> getDraftList(IPage<SecondGoodsVo> page, int userId) {
|
|
|
+ QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("sg.user_id", userId) // 用户ID
|
|
|
+ .eq("sg.goods_status", SecondGoodsStatusEnum.DRAFT.getCode())
|
|
|
+ .orderByDesc("sg.created_time");
|
|
|
+ return secondGoodsMapper.getDraftList(page, queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取点赞商品列表
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param phone 手机号
|
|
|
+ * @return IPage<SecondGoodsVo> 点赞商品列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoodsVo> getLikeGoodsPage(IPage<SecondGoodsVo> page, int userId, String phone) {
|
|
|
+ QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper
|
|
|
+ // 可以查看已删除的商品数据
|
|
|
+// .eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("lc.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("lc.dianzan_id", "user_"+phone)
|
|
|
+ .eq("lc.type", Constants.LikeType.SECOND_HAND_GOODS) // 6-二手商品
|
|
|
+ .eq("sg.user_id", userId) // 用户ID
|
|
|
+ .orderByDesc("lc.created_time");
|
|
|
+ return secondGoodsMapper.getLikeGoodsPage(page, queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取交易列表
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @return IPage<SellGoodsVo> 交易列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SellGoodsVo> getTransactionList(IPage<SellGoodsVo> page, Integer userId) {
|
|
|
+ IPage<SellGoodsVo> result = new Page<>();
|
|
|
+ QueryWrapper<SellGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper
|
|
|
+ // 可以查看已删除的商品数据
|
|
|
+// .eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .eq("str.delete_flag", Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .and(wrapper -> wrapper.eq("str.buyer_id", userId)
|
|
|
+ .or()
|
|
|
+ .eq("str.seller_id", userId))
|
|
|
+ .orderByDesc("str.created_time");
|
|
|
+ result = secondGoodsMapper.getTransactionList(page, userId, queryWrapper);
|
|
|
+ // 批量设置用户信息
|
|
|
+ batchSetSellUserInfo(result);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据用户ID和商品状态获取商品列表
|
|
|
+ * @param userId 用户ID
|
|
|
+ * @param goodsStatus 商品状态
|
|
|
+ * @return 商品列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public List<SecondGoods> getGoodsListByUserId(Integer userId, Integer goodsStatus) {
|
|
|
+ // 获取商品屏蔽列表
|
|
|
+ List<SecondGoods> shieldedGoodsList = getShieldedGoodsList(userId);
|
|
|
+ // 提取屏蔽商品ID
|
|
|
+ List<Integer> shieldedGoodsIds = shieldedGoodsList.stream()
|
|
|
+ .map(SecondGoods::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ LambdaQueryWrapper<SecondGoods> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ queryWrapper.eq(SecondGoods::getUserId, userId);
|
|
|
+ queryWrapper.eq(SecondGoods::getGoodsStatus, goodsStatus);
|
|
|
+ queryWrapper.notIn(CollectionUtil.isNotEmpty(shieldedGoodsIds), SecondGoods::getId, shieldedGoodsIds);
|
|
|
+ queryWrapper.orderByDesc(SecondGoods::getReleaseTime);
|
|
|
+ return secondGoodsMapper.selectList(queryWrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理视频审核结果
|
|
|
+ * @param task 视频审核任务
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void processVideoModerationResult(SecondVideoTask task) {
|
|
|
+ try {
|
|
|
+ // 查找关联的商品
|
|
|
+ QueryWrapper<SecondGoods> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq("video_task_id", task.getTaskId());
|
|
|
+ SecondGoods goods = getOne(queryWrapper);
|
|
|
+
|
|
|
+ if (goods == null) {
|
|
|
+ log.warn("未找到关联的商品,任务ID: {}", task.getTaskId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据审核结果更新商品状态
|
|
|
+ if ("none".equals(task.getRiskLevel())) {
|
|
|
+ log.warn("视频审核通过,任务ID: {}", task.getTaskId());
|
|
|
+ SecondGoodsVo secondGoodsVo = new SecondGoodsVo();
|
|
|
+ BeanUtils.copyProperties(goods, secondGoodsVo);
|
|
|
+ QueryWrapper<StoreImg> imgQueryWrapper = new QueryWrapper<>();
|
|
|
+ imgQueryWrapper.eq("store_id", goods.getId());
|
|
|
+ imgQueryWrapper.eq("img_type", 18);
|
|
|
+ List<StoreImg> storeImgs = storeImgMapper.selectList(imgQueryWrapper);
|
|
|
+ List<String> imgUrls = storeImgs.stream()
|
|
|
+ .map(StoreImg::getImgUrl)
|
|
|
+ .filter(imgUrl -> StringUtils.isNotBlank(imgUrl))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ secondGoodsVo.setImgUrl(imgUrls);
|
|
|
+
|
|
|
+ secondGoodsAuditService.performSecondRoundReview(goods, secondGoodsVo);
|
|
|
+//
|
|
|
+// // 审核通过
|
|
|
+// goods.setGoodsStatus(SecondGoodsStatusEnum.LISTED.getCode());
|
|
|
+// goods.setFailedReason("");
|
|
|
+// goods.setReleaseTime(new Date());
|
|
|
+// updateById(goods);
|
|
|
+//
|
|
|
+// // 更新审核记录
|
|
|
+// createGoodsAudit(goods, "", Constants.AuditStatus.PASSED);
|
|
|
+//
|
|
|
+// // 发送审核成功消息
|
|
|
+// sendMessage(goods);
|
|
|
+// // 审核成功,记录操作历史
|
|
|
+// // 审核成功,记录操作历史
|
|
|
+// String operationName = "";
|
|
|
+// QueryWrapper<SecondGoodsRecord> queryRecordWrapper = new QueryWrapper<>();
|
|
|
+// queryRecordWrapper.eq("goods_id", goods.getId());
|
|
|
+// log.info("查询操作记录开始 goods_id: {}", goods.getId());
|
|
|
+// List<SecondGoodsRecord> recordList = secondGoodsRecordMapper.selectList(queryRecordWrapper);
|
|
|
+// log.info("查询操作记录结束 recordList: {}", recordList);
|
|
|
+// if (CollectionUtil.isNotEmpty(recordList)){
|
|
|
+// operationName = "重新发布";
|
|
|
+// }else {
|
|
|
+// operationName = "首次发布";
|
|
|
+// }
|
|
|
+// recordGoodsOperation(goods, operationName);
|
|
|
+//
|
|
|
+// // 检查用户是否在24小时内发布同类商品超过阈值
|
|
|
+// if (!checkUserPublishSameCategoryLimit(goods)) {
|
|
|
+// log.warn("用户 {} 在24小时内发布同类商品次数超过限制", goods.getUserId());
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 检查用户是否在24小时内发布商品超过阈值
|
|
|
+// if (!checkUserPublishLimit(goods)) {
|
|
|
+// log.warn("用户 {} 在24小时内发布商品次数超过限制", goods.getUserId());
|
|
|
+//
|
|
|
+// }
|
|
|
+ } else {
|
|
|
+ // 审核不通过
|
|
|
+ goods.setGoodsStatus(SecondGoodsStatusEnum.REVIEW_FAILED.getCode());
|
|
|
+
|
|
|
+ // 解析审核结果,生成具体的失败原因
|
|
|
+ String failedReason = parseVideoModerationFailureReason(task);
|
|
|
+ goods.setFailedReason(failedReason);
|
|
|
+ updateById(goods);
|
|
|
+
|
|
|
+ // 更新审核记录
|
|
|
+ createGoodsAudit(goods, failedReason, Constants.AuditStatus.FAILED);
|
|
|
+
|
|
|
+ // 记录操作历史
|
|
|
+ recordGoodsOperation(goods, "视频审核失败");
|
|
|
+ // 发送审核失败消息
|
|
|
+ sendFailedMsg(goods);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理视频审核结果时发生异常,任务ID: {}", task.getTaskId(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析视频审核失败原因
|
|
|
+ * @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();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量设置用户信息(用于SecondGoodsVo列表)
|
|
|
+ * @param searchGoodsList 搜索结果
|
|
|
+ */
|
|
|
+ private void batchSetSearchUserInfo(IPage<SecondGoodsVo> searchGoodsList) {
|
|
|
+ batchSetUserInfo(searchGoodsList,
|
|
|
+ SecondGoodsVo::getUserId,
|
|
|
+ (goods, userInfo) -> {
|
|
|
+ goods.setUserName(userInfo.getUserName());
|
|
|
+ goods.setRealName(userInfo.getRealName());
|
|
|
+ goods.setUserImage(userInfo.getUserImage());
|
|
|
+ goods.setUserId(userInfo.getId());
|
|
|
+ goods.setUserPhone("user_"+userInfo.getUserPhone());
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量设置用户信息(用于SellGoodsVo列表)
|
|
|
+ * @param sellGoodsList 搜索结果
|
|
|
+ */
|
|
|
+ private void batchSetSellUserInfo(IPage<SellGoodsVo> sellGoodsList) {
|
|
|
+ batchSetUserInfo(sellGoodsList,
|
|
|
+ SellGoodsVo::getUserId,
|
|
|
+ (goods, userInfo) -> {
|
|
|
+ goods.setUserName(userInfo.getUserName());
|
|
|
+ goods.setRealName(userInfo.getRealName());
|
|
|
+ goods.setUserImage(userInfo.getUserImage());
|
|
|
+ goods.setUserId(userInfo.getId());
|
|
|
+ goods.setUserPhone(userInfo.getUserPhone());
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量设置商品图片信息
|
|
|
+ * @param searchGoodsList 商品列表
|
|
|
+ */
|
|
|
+ private void batchSetGoodsImages(IPage<SecondGoodsVo> searchGoodsList) {
|
|
|
+ // 批量获取图片信息
|
|
|
+ if (CollectionUtil.isNotEmpty(searchGoodsList.getRecords())) {
|
|
|
+ List<Integer> goodsIds = searchGoodsList.getRecords().stream()
|
|
|
+ .map(SecondGoodsVo::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ // 批量获取图片信息
|
|
|
+ QueryWrapper<StoreImg> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.lambda()
|
|
|
+ .eq(StoreImg::getImgType, Constants.ImageType.SECOND_HAND_GOODS) // 商品 图片
|
|
|
+ .eq(StoreImg::getDeleteFlag, Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .in(StoreImg::getStoreId, goodsIds);
|
|
|
+ List<StoreImg> imagesList= storeImgMapper.getImgsByGoodsIds(queryWrapper);
|
|
|
+ // 集合根據商品id进行分组返回map
|
|
|
+ Map<Integer, List<StoreImg>> imagesMap = imagesList.stream().collect(Collectors.groupingBy(StoreImg::getStoreId));
|
|
|
+ // 遍历
|
|
|
+ for (SecondGoodsVo goods : searchGoodsList.getRecords()) {
|
|
|
+ // 提取图片url
|
|
|
+ List<StoreImg> images = imagesMap.get(goods.getId());
|
|
|
+ if (images != null) {
|
|
|
+ goods.setImgUrl(images.stream()
|
|
|
+ .map(StoreImg::getImgUrl)
|
|
|
+ .collect(Collectors.toList()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量设置商品图片信息(用于管理后台)
|
|
|
+ * @param goodsList 商品列表
|
|
|
+ */
|
|
|
+ private void batchSetGoodsImagesForAdmin(IPage<SecondGoodsVo> goodsList) {
|
|
|
+ // 批量获取图片信息
|
|
|
+ if (CollectionUtil.isNotEmpty(goodsList.getRecords())) {
|
|
|
+ List<Integer> goodsIds = goodsList.getRecords().stream()
|
|
|
+ .map(SecondGoodsVo::getId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ // 批量获取图片信息
|
|
|
+ QueryWrapper<StoreImg> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.lambda()
|
|
|
+ .eq(StoreImg::getImgType, Constants.ImageType.SECOND_HAND_GOODS) // 商品 图片
|
|
|
+ .eq(StoreImg::getDeleteFlag, Constants.DeleteFlag.NOT_DELETED)
|
|
|
+ .in(StoreImg::getStoreId, goodsIds);
|
|
|
+ List<StoreImg> imagesList= storeImgMapper.getImgsByGoodsIds(queryWrapper);
|
|
|
+ // 集合根據商品id进行分组返回map
|
|
|
+ Map<Integer, List<StoreImg>> imagesMap = imagesList.stream().collect(Collectors.groupingBy(StoreImg::getStoreId));
|
|
|
+ // 遍历
|
|
|
+ for (SecondGoodsVo goods : goodsList.getRecords()) {
|
|
|
+ // 提取图片url
|
|
|
+ List<StoreImg> images = imagesMap.get(goods.getId());
|
|
|
+ if (images != null) {
|
|
|
+ goods.setImgUrl(images.stream()
|
|
|
+ .map(StoreImg::getImgUrl)
|
|
|
+ .collect(Collectors.toList()));
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置状态名称
|
|
|
+ if (goods.getDeleteFlag() != null && goods.getDeleteFlag().equals(Constants.DeleteFlag.DELETED)) {
|
|
|
+ goods.setGoodsStatusName("已删除");
|
|
|
+ } else if (goods.getGoodsStatus() != null) {
|
|
|
+ SecondGoodsStatusEnum statusEnum = SecondGoodsStatusEnum.fromCode(goods.getGoodsStatus());
|
|
|
+ if (statusEnum != null) {
|
|
|
+ goods.setGoodsStatusName(statusEnum.getDescription());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量设置用户信息
|
|
|
+ * @param <T> 任意商品VO类型
|
|
|
+ * @param goodsList 商品列表
|
|
|
+ * @param userIdMapper 从商品VO获取用户ID的函数
|
|
|
+ * @param userSetter 从用户信息设置到商品VO的函数
|
|
|
+ */
|
|
|
+ private <T> void batchSetUserInfo(IPage<T> goodsList,
|
|
|
+ java.util.function.Function<T, Integer> userIdMapper,
|
|
|
+ java.util.function.BiConsumer<T, LifeUserVo> userSetter) {
|
|
|
+ if (CollectionUtil.isNotEmpty(goodsList.getRecords())) {
|
|
|
+ List<Integer> userIds = goodsList.getRecords().stream()
|
|
|
+ .map(userIdMapper)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ QueryWrapper<LifeUserVo> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.in("id", userIds)
|
|
|
+ .eq("delete_flag", Constants.DeleteFlag.NOT_DELETED);
|
|
|
+
|
|
|
+ List<LifeUserVo> userInfoList = lifeUserMapper.getUserByIds(queryWrapper);
|
|
|
+ Map<Integer, LifeUserVo> userInfoMap = userInfoList.stream()
|
|
|
+ .collect(Collectors.toMap(LifeUserVo::getId, lifeUserVo -> lifeUserVo));
|
|
|
+
|
|
|
+ for (T goods : goodsList.getRecords()) {
|
|
|
+ LifeUserVo userInfo = userInfoMap.get(userIdMapper.apply(goods));
|
|
|
+ if (userInfo != null && userSetter != null) {
|
|
|
+ userSetter.accept(goods, userInfo);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取管理后台商品列表
|
|
|
+ * @param page 分页参数
|
|
|
+ * @param queryDTO 查询参数DTO
|
|
|
+ * @return 商品列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public IPage<SecondGoodsVo> getAdminGoodsList(IPage<SecondGoodsVo> page, SecondGoodsAdminQueryDTO queryDTO) {
|
|
|
+ // 构建查询条件
|
|
|
+ QueryWrapper<SecondGoodsVo> queryWrapper = new QueryWrapper<>();
|
|
|
+
|
|
|
+ // 商品名称模糊查询
|
|
|
+ queryWrapper.like(org.apache.commons.lang3.StringUtils.isNotBlank(queryDTO.getTitle()), "sg.title", queryDTO.getTitle());
|
|
|
+ // 过滤草稿
|
|
|
+ queryWrapper.ne("sg.goods_status",SecondGoodsStatusEnum.DRAFT.getCode());
|
|
|
+ // 状态查询分为两种情况处理
|
|
|
+ if (queryDTO.getGoodsStatus() != null) {
|
|
|
+ if (queryDTO.getGoodsStatus() == 6) {
|
|
|
+ // 当状态为6时,查询删除标记为1的数据
|
|
|
+ queryWrapper.eq("sg.delete_flag", Constants.DeleteFlag.DELETED);
|
|
|
+ } else {
|
|
|
+ // 其他状态按照商品状态进行查询,并且删除标记为0
|
|
|
+ queryWrapper.eq("sg.goods_status", queryDTO.getGoodsStatus())
|
|
|
+ .eq("sg.delete_flag", Constants.DeleteFlag.NOT_DELETED);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发布时间范围查询
|
|
|
+ queryWrapper.ge(queryDTO.getReleaseStartTime() != null, "sg.release_time", queryDTO.getReleaseStartTime())
|
|
|
+ .le(queryDTO.getReleaseEndTime() != null, "sg.release_time", queryDTO.getReleaseEndTime());
|
|
|
+ queryWrapper.orderByDesc("sg.id");
|
|
|
+ // 查询数据
|
|
|
+ IPage<SecondGoodsVo> result = secondGoodsMapper.getAdminGoodsList(page, queryWrapper);
|
|
|
+
|
|
|
+ // 批量设置商品图片信息
|
|
|
+ batchSetGoodsImagesForAdmin(result);
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据风控记录中的JSON数据批量下架商品
|
|
|
+ * @param goodsIds 商品ID集合
|
|
|
+ * @return 是否下架成功
|
|
|
+ */
|
|
|
+ public boolean batchShelveGoodsByIds(List<Integer> goodsIds) {
|
|
|
+ if (goodsIds == null || goodsIds.isEmpty()) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 批量更新商品状态为下架状态
|
|
|
+ LambdaUpdateWrapper<SecondGoods> updateWrapper = new LambdaUpdateWrapper<>();
|
|
|
+ updateWrapper.in(SecondGoods::getId, goodsIds)
|
|
|
+ .set(SecondGoods::getGoodsStatus, SecondGoodsStatusEnum.DELISTED.getCode());
|
|
|
+
|
|
|
+ boolean updateResult = update(updateWrapper);
|
|
|
+
|
|
|
+ if (updateResult) {
|
|
|
+ // 批量查询更新后的商品信息
|
|
|
+ Collection<SecondGoods> updatedGoodsList = listByIds(goodsIds);
|
|
|
+
|
|
|
+ // 为每个商品记录操作历史并发送通知
|
|
|
+ for (SecondGoods goods : updatedGoodsList) {
|
|
|
+ recordGoodsOperation(goods, "风控下架");
|
|
|
+ sendShelveMessage(goods);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return updateResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据风控记录中的JSON数据批量下架商品
|
|
|
+ * @param ruleType 风控规则类型
|
|
|
+ * @param businessId 业务ID
|
|
|
+ * @return 是否下架成功
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean batchShelveGoodsByRiskControlRecord(Integer ruleType, String businessId) {
|
|
|
+ try {
|
|
|
+ List<Integer> goodsIdList = Lists.newArrayList();
|
|
|
+ // 获取相同类型的风控数据
|
|
|
+ List<SecondRiskControlRecord> riskControlRecordList = riskControlService.getSameTypeRiskControlRecords(ruleType, businessId);
|
|
|
+ // 获取风控数据详情
|
|
|
+ List<String> detailInfoList = riskControlRecordList.stream()
|
|
|
+ .map(SecondRiskControlRecord::getDetailInfo)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 声明所有被风控的商品ID
|
|
|
+ List<Integer> idList = Lists.newArrayList();
|
|
|
+
|
|
|
+ // 循环情信息json 转换为集合
|
|
|
+ for (String detailInfo : detailInfoList){
|
|
|
+ // 解析JSON获取商品ID列表
|
|
|
+ List<Integer> ids = JSON.parseArray(detailInfo, Integer.class);
|
|
|
+ idList.addAll(ids);
|
|
|
+ }
|
|
|
+ // goodsIdList 去重
|
|
|
+ idList = idList.stream().distinct().collect(Collectors.toList());
|
|
|
+ // 交易欺诈
|
|
|
+ if (ruleType.equals(RiskControlRuleTypeEnum.TRANSACTION_FRAUD.getRuleType())){
|
|
|
+ // 根据商品记录id集合查询商品Id 集合
|
|
|
+ QueryWrapper<SecondGoodsRecord> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.in("id", idList);
|
|
|
+ List<SecondGoodsRecord> goodsRecordList = secondGoodsRecordMapper.selectList(queryWrapper);
|
|
|
+ // 获取商品Id集合
|
|
|
+ List<Integer> goodsIds = goodsRecordList.stream().map(SecondGoodsRecord::getGoodsId).collect(Collectors.toList());
|
|
|
+ // 去重
|
|
|
+ goodsIds = goodsIds.stream().distinct().collect(Collectors.toList());
|
|
|
+ goodsIdList.addAll(goodsIds);
|
|
|
+ // 异常发布
|
|
|
+ } else if (RiskControlRuleTypeEnum.ABNORMAL_PUBLISH.getRuleType().equals(ruleType)) {
|
|
|
+ goodsIdList.addAll(idList);
|
|
|
+ }
|
|
|
+ // 调用批量下架方法
|
|
|
+ return batchShelveGoodsByIds(goodsIdList);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("根据风控记录批量下架商品失败,详情: {}", ruleType, e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下架商品
|
|
|
+ * @param secondGoods 商品信息
|
|
|
+ * @return 是否下架成功
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public boolean shelveSecondGoods(SecondGoodsVo secondGoods) {
|
|
|
+ log.info("SecondGoodsServiceImpl.shelveSecondGoods?secondGoods={}", secondGoods.toString());
|
|
|
+ // 修改商品状态为4 - 已下架
|
|
|
+ secondGoods.setGoodsStatus(4);
|
|
|
+ boolean updateResult = updateById(secondGoods);
|
|
|
+ if (updateResult) {
|
|
|
+ // 获取最新的商品信息并记录操作历史
|
|
|
+ SecondGoods updatedGoods = getById(secondGoods.getId());
|
|
|
+ recordGoodsOperation(updatedGoods, "下架商品");
|
|
|
+ // 发送系统通知
|
|
|
+ sendShelveMessage(updatedGoods);
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送商品下架消息
|
|
|
+ * @param goods 商品信息
|
|
|
+ */
|
|
|
+ private void sendShelveMessage(SecondGoods goods) {
|
|
|
+ try {
|
|
|
+ // 根据 goods.getUserId() 获取用户信息
|
|
|
+ LifeUser lifeUser = lifeUserMapper.selectById(goods.getUserId());
|
|
|
+ String phone = lifeUser.getUserPhone();
|
|
|
+ // 调取feign接口 发送消息 life_notice表
|
|
|
+ LifeNotice lifeNotice = new LifeNotice();
|
|
|
+ lifeNotice.setSenderId("system");
|
|
|
+ lifeNotice.setReceiverId("user_"+ phone);
|
|
|
+ lifeNotice.setBusinessId(goods.getId());
|
|
|
+ lifeNotice.setTitle("商品下架通知");
|
|
|
+ JSONObject jsonObject = new JSONObject();
|
|
|
+ jsonObject.put("goodsId", goods.getId());
|
|
|
+// jsonObject.put("status", "true");
|
|
|
+ jsonObject.put("message", "您的商品:"+ goods.getTitle() + "已下架");
|
|
|
+ lifeNotice.setContext(jsonObject.toJSONString());
|
|
|
+ lifeNotice.setNoticeType(Constants.Notice.SYSTEM_NOTICE); // 系统通知
|
|
|
+ lifeNotice.setIsRead(0);
|
|
|
+ lifeNoticeMapper.insert(lifeNotice);
|
|
|
+ sendShelveNotice("user_"+ phone, lifeNotice);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送消息通知失败,goods: {}", goods, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送下架通知
|
|
|
+ * @param receiverId 接收者ID
|
|
|
+ * @param lifeNotice 通知内容
|
|
|
+ */
|
|
|
+ private void sendShelveNotice(String receiverId, LifeNotice lifeNotice) {
|
|
|
+ try {
|
|
|
+ WebSocketVo webSocketVo = new WebSocketVo();
|
|
|
+ webSocketVo.setSenderId("system");
|
|
|
+ webSocketVo.setReceiverId(receiverId);
|
|
|
+ webSocketVo.setCategory("notice");
|
|
|
+ webSocketVo.setNoticeType("1");
|
|
|
+ webSocketVo.setIsRead(0);
|
|
|
+ webSocketVo.setText(JSONObject.from(lifeNotice).toJSONString());
|
|
|
+ alienStoreFeign.sendMsgToClientByPhoneId(receiverId, JSONObject.from(webSocketVo).toJSONString());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送消息通知失败,receiverId: {}", receiverId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|