| | |
| | | import cn.lili.modules.member.entity.dos.Member; |
| | | import cn.lili.modules.member.service.FootprintService; |
| | | import cn.lili.modules.member.service.MemberService; |
| | | import cn.lili.modules.search.entity.dos.EsGoodsIndex; |
| | | import cn.lili.rocketmq.RocketmqSendCallbackBuilder; |
| | | import cn.lili.rocketmq.tags.CommentTagsEnum; |
| | | import cn.lili.rocketmq.tags.VideoTagsEnum; |
| | |
| | | import org.apache.commons.collections4.CollectionUtils; |
| | | import org.apache.commons.collections4.ListUtils; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.lucene.search.join.ScoreMode; |
| | | import org.apache.rocketmq.spring.core.RocketMQTemplate; |
| | | import org.elasticsearch.index.query.*; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Qualifier; |
| | | import org.springframework.data.domain.PageRequest; |
| | | import org.springframework.data.domain.Pageable; |
| | | import org.springframework.data.domain.Sort; |
| | | import org.springframework.data.elasticsearch.core.ElasticsearchOperations; |
| | | import org.springframework.data.elasticsearch.core.SearchHits; |
| | | import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; |
| | | import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; |
| | | import org.springframework.stereotype.Service; |
| | | import lombok.RequiredArgsConstructor; |
| | | import cn.lili.utils.PageUtil; |
| | |
| | | private final RocketmqCustomProperties rocketmqCustomProperties; |
| | | private final RocketMQTemplate rocketMQTemplate; |
| | | private final ThumbsUpRecordService thumbsUpRecordService; |
| | | private final ElasticsearchOperations restTemplate; |
| | | |
| | | @Qualifier("videoEsServiceImpl") |
| | | private final EsService esService; |
| | | |
| | | |
| | | /** |
| | |
| | | // 5. 构建es中数据,mq异步处理 |
| | | VideoIndex videoIndex = new VideoIndex(); |
| | | BeanUtils.copyProperties(video, videoIndex); |
| | | videoIndex.setAuthorName(UserContext.getCurrentUser().getNickName()); |
| | | videoIndex.setAuthorAvatar(UserContext.getCurrentUser().getFace()); |
| | | videoIndex.setCoverFileKey(video.getCoverUrl()); |
| | | List<VideoGoodsDetailVO> esGoodsList = videoGoods.stream().map(goods -> { |
| | | VideoGoodsDetailVO vo = new VideoGoodsDetailVO(); |
| | |
| | | // 5. 更新es中的数据,mq异步处理 |
| | | VideoIndex videoIndex = new VideoIndex(); |
| | | BeanUtils.copyProperties(video, videoIndex); |
| | | videoIndex.setAuthorName(UserContext.getCurrentUser().getNickName()); |
| | | videoIndex.setAuthorAvatar(UserContext.getCurrentUser().getFace()); |
| | | videoIndex.setCoverFileKey(video.getCoverUrl()); |
| | | List<VideoGoodsDetailVO> esGoodsList = videoGoods.stream().map(goods -> { |
| | | VideoGoodsDetailVO vo = new VideoGoodsDetailVO(); |
| | |
| | | // 推荐算法: 1. 根据用户的收藏视频的标签 2. 根据用户关注的作者的其它视频 3. 根据用户的观看记录(观看时长较长的、重复观看次数较多的) 4. 基于相似用户的观看行为来给该用户推荐 |
| | | IPage<WxVideoVO> page = PageUtil.getPage(query, WxVideoVO.class); |
| | | switch (query.getVideoFrom()) { |
| | | case "recommend": |
| | | case "recommend":// 加载推荐视频 |
| | | baseMapper.recommendVideo(page, query); |
| | | break; |
| | | case "author": |
| | | case "author": // 加载视频主页我发布的视频 |
| | | AuthorVideoQuery query1 = new AuthorVideoQuery(); |
| | | BeanUtils.copyProperties(query, query1); |
| | | query1.setAuthorId(query.getAuthorId()); |
| | | baseMapper.getAuthorVideoPage(page, query1); |
| | | break; |
| | | case "collect": |
| | | case "collect": // 加载视频主页收藏视频 |
| | | AuthorVideoQuery query2 = new AuthorVideoQuery(); |
| | | BeanUtils.copyProperties(query, query2); |
| | | query2.setAuthorId(query.getAuthorId()); |
| | | baseMapper.getAuthorCollectVideoPage(page, query2); |
| | | break; |
| | | case "like": |
| | | case "like": // 加载视频主页点赞视频 |
| | | AuthorVideoQuery query3 = new AuthorVideoQuery(); |
| | | BeanUtils.copyProperties(query, query3); |
| | | query3.setAuthorId(query.getAuthorId()); |
| | | baseMapper.getAuthorLikeVideoPage(page, query3); |
| | | break; |
| | | case "search": // 加载es搜索视频 |
| | | VideoEsQuery videoEsQuery = new VideoEsQuery(); |
| | | BeanUtils.copyProperties(query, videoEsQuery); |
| | | videoEsQuery.setPageNumber((int) query.getPageNumber()); |
| | | videoEsQuery.setPageSize((int) query.getPageSize()); |
| | | return this.esSearch(videoEsQuery); |
| | | case "goodsSimilarly": // 悬挂相同商品的推荐视频 |
| | | GoodsSimilarlyQuery goodsSimilarlyQuery = new GoodsSimilarlyQuery(); |
| | | BeanUtils.copyProperties(query, goodsSimilarlyQuery); |
| | | baseMapper.goodsSimilarlyPage(page, goodsSimilarlyQuery); |
| | | break; |
| | | case "history": |
| | | VideoHistoryQuery videoHistoryQuery = new VideoHistoryQuery(); |
| | | BeanUtils.copyProperties(query, videoHistoryQuery); |
| | | videoHistoryQuery.setUserId(UserContext.getCurrentUserId()); |
| | | baseMapper.getHistoryPage(page, videoHistoryQuery); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | |
| | | v.setCommentNum(this.getCommentNum(v.getId(), v.getCommentNum())); |
| | | v.setCollectNum(this.getCollectNum(v.getId(), v.getCollectNum())); |
| | | v.setThumbsUpNum(this.getThumbsUpNum(v.getId(), v.getThumbsUpNum())); |
| | | v.setAuthorAvatar(cosUtil.getPreviewUrl(v.getAuthorAvatar())); |
| | | if (VideoContentTypeEnum.VIDEO.getValue().equals(v.getVideoContentType())) { |
| | | v.setVideoUrl(cosUtil.getPreviewUrl(v.getVideoFileKey())); |
| | | v.setCoverUrl(cosUtil.getPreviewUrl(v.getCoverFileKey())); |
| | |
| | | public Result healthRecommendVideo(WxHealthVideoQuery query) { |
| | | IPage<WxVideoVO> page = PageUtil.getPage(query, WxVideoVO.class); |
| | | //获取大健康视频列表 |
| | | baseMapper.recommendHealthVideo(page,query); |
| | | baseMapper.recommendHealthVideo(page,query); |
| | | if (page.getTotal() > 0) { |
| | | page.getRecords().forEach(v -> { |
| | | v.setAuthorAvatar(cosUtil.getPreviewUrl(v.getAuthorAvatar())); |
| | | if (VideoContentTypeEnum.VIDEO.getValue().equals(v.getVideoContentType())) { |
| | | v.setVideoUrl(cosUtil.getPreviewUrl(v.getVideoFileKey())); |
| | | v.setCoverUrl(cosUtil.getPreviewUrl(v.getCoverFileKey())); |
| | |
| | | IPage<WxVideoVO> page = PageUtil.getPage(query, WxVideoVO.class); |
| | | baseMapper.wxKitchenVideoQuery(page, query); |
| | | page.getRecords().forEach(v -> { |
| | | v.setAuthorAvatar(cosUtil.getPreviewUrl(v.getAuthorAvatar())); |
| | | if (VideoContentTypeEnum.VIDEO.getValue().equals(v.getVideoContentType())) { |
| | | v.setVideoUrl(cosUtil.getPreviewUrl(v.getVideoFileKey())); |
| | | v.setCoverUrl(cosUtil.getPreviewUrl(v.getCoverFileKey())); |
| | |
| | | .in(Video::getId, chunk.stream().map(CollectTypeNumVO::getId).collect(Collectors.toList())) |
| | | .set(Video::getCollectNumJob, Boolean.FALSE) |
| | | .update(); |
| | | // 更新es的收藏数 |
| | | for (CollectTypeNumVO vo : chunk) { |
| | | Map<String, Object> fields = new HashMap<>(1); |
| | | fields.put("collectNum", vo.getCountNum()); |
| | | esService.updateSomeField(EsSuffix.VIDEO_INDEX_NAME, vo.getId(), fields); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | .in(Video::getId, chunk.stream().map(CollectTypeNumVO::getId).collect(Collectors.toList())) |
| | | .set(Video::getCommentNumJob, Boolean.FALSE) |
| | | .update(); |
| | | // 更新es的评论数 |
| | | for (CollectTypeNumVO vo : chunk) { |
| | | Map<String, Object> fields = new HashMap<>(1); |
| | | fields.put("commentNum", vo.getCountNum()); |
| | | esService.updateSomeField(EsSuffix.VIDEO_INDEX_NAME, vo.getId(), fields); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public Result saveViewRecord(VideoFootPrintForm form) { |
| | | FootPrint footPrint = new FootPrint(); |
| | | footPrint.setViewType(ViewTypeEnum.VIDEO.getValue()); |
| | | footPrint.setRefId(form.getVideoId()); |
| | | footPrint.setMemberId(UserContext.getCurrentUserId()); |
| | | footPrint.setViewDuration(form.getViewDuration()); |
| | | footPrint.setPlayAt(form.getPlayAt()); |
| | | footprintService.saveFootprint(footPrint); |
| | | FootPrint one = new LambdaQueryChainWrapper<>(footprintService.getBaseMapper()) |
| | | .eq(FootPrint::getRefId, form.getVideoId()) |
| | | .eq(FootPrint::getMemberId, UserContext.getCurrentUserId()) |
| | | .eq(FootPrint::getViewType, ViewTypeEnum.VIDEO.getValue()) |
| | | .one(); |
| | | if (Objects.nonNull(one)) { |
| | | one.setViewDuration(one.getViewDuration() + form.getViewDuration()); |
| | | one.setPlayAt(form.getPlayAt()); |
| | | footprintService.updateById(one); |
| | | } else { |
| | | FootPrint footPrint = new FootPrint(); |
| | | footPrint.setViewType(ViewTypeEnum.VIDEO.getValue()); |
| | | footPrint.setRefId(form.getVideoId()); |
| | | footPrint.setMemberId(UserContext.getCurrentUserId()); |
| | | footPrint.setViewDuration(form.getViewDuration()); |
| | | footPrint.setPlayAt(form.getPlayAt()); |
| | | footprintService.saveFootprint(footPrint); |
| | | } |
| | | return Result.ok(); |
| | | } |
| | | |
| | |
| | | v.setCommentNum(this.getCommentNum(v.getId(), v.getCommentNum())); |
| | | v.setCollectNum(this.getCollectNum(v.getId(), v.getCollectNum())); |
| | | v.setThumbsUpNum(this.getThumbsUpNum(v.getId(), v.getThumbsUpNum())); |
| | | v.setAuthorAvatar(cosUtil.getPreviewUrl(v.getAuthorAvatar())); |
| | | if (VideoContentTypeEnum.VIDEO.getValue().equals(v.getVideoContentType())) { |
| | | v.setVideoUrl(cosUtil.getPreviewUrl(v.getVideoFileKey())); |
| | | v.setCoverUrl(cosUtil.getPreviewUrl(v.getCoverFileKey())); |
| | |
| | | v.setCommentNum(this.getCommentNum(v.getId(), v.getCommentNum())); |
| | | v.setCollectNum(this.getCollectNum(v.getId(), v.getCollectNum())); |
| | | v.setThumbsUpNum(this.getThumbsUpNum(v.getId(), v.getThumbsUpNum())); |
| | | v.setAuthorAvatar(cosUtil.getPreviewUrl(v.getAuthorAvatar())); |
| | | if (VideoContentTypeEnum.VIDEO.getValue().equals(v.getVideoContentType())) { |
| | | v.setVideoUrl(cosUtil.getPreviewUrl(v.getVideoFileKey())); |
| | | v.setCoverUrl(cosUtil.getPreviewUrl(v.getCoverFileKey())); |
| | |
| | | v.setCommentNum(this.getCommentNum(v.getId(), v.getCommentNum())); |
| | | v.setCollectNum(this.getCollectNum(v.getId(), v.getCollectNum())); |
| | | v.setThumbsUpNum(this.getThumbsUpNum(v.getId(), v.getThumbsUpNum())); |
| | | v.setAuthorAvatar(cosUtil.getPreviewUrl(v.getAuthorAvatar())); |
| | | if (VideoContentTypeEnum.VIDEO.getValue().equals(v.getVideoContentType())) { |
| | | v.setVideoUrl(cosUtil.getPreviewUrl(v.getVideoFileKey())); |
| | | v.setCoverUrl(cosUtil.getPreviewUrl(v.getCoverFileKey())); |
| | |
| | | .in(Video::getId, chunk.stream().map(CollectTypeNumVO::getId).collect(Collectors.toList())) |
| | | .set(Video::getThumbsUpNumJob, Boolean.FALSE) |
| | | .update(); |
| | | // 更新es的点赞数 |
| | | for (CollectTypeNumVO vo : chunk) { |
| | | Map<String, Object> fields = new HashMap<>(1); |
| | | fields.put("thumbsUpNum", vo.getCountNum()); |
| | | esService.updateSomeField(EsSuffix.VIDEO_INDEX_NAME, vo.getId(), fields); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public Result esSearch(VideoEsQuery q) { |
| | | // 判断商品索引是否存在 |
| | | if (!restTemplate.indexOps(VideoIndex.class).exists()) { |
| | | return Result.ok(); |
| | | } |
| | | q.setPageNumber(q.getPageNumber() - 1); // 前端保持统一从第一页开始,但是es从0页开始,所以减一 |
| | | // 根据点赞数排序 |
| | | Pageable pageable = PageRequest.of(q.getPageNumber(), q.getPageSize(), Sort.by(Sort.Direction.DESC, "thumbsUpNum")); |
| | | |
| | | NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); |
| | | queryBuilder.withPageable(pageable); |
| | | |
| | | if (StringUtils.isNotBlank(q.getKeyword())) { |
| | | // 1. 构建主布尔查询 |
| | | BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); |
| | | |
| | | // 2. 添加标题匹配(非嵌套字段) |
| | | boolQuery.should(QueryBuilders.matchQuery("title", q.getKeyword())); |
| | | |
| | | // 3. 添加嵌套标签匹配 |
| | | NestedQueryBuilder tagQuery = QueryBuilders.nestedQuery( |
| | | "tagList", |
| | | QueryBuilders.matchQuery("tagList.tagName", q.getKeyword()), |
| | | ScoreMode.Total // 使用总分模式 |
| | | ); |
| | | boolQuery.should(tagQuery); |
| | | |
| | | // 4. 添加嵌套商品匹配 |
| | | NestedQueryBuilder goodsQuery = QueryBuilders.nestedQuery( |
| | | "goodsList", |
| | | QueryBuilders.matchQuery("goodsList.goodsName", q.getKeyword()), |
| | | ScoreMode.Total |
| | | ); |
| | | boolQuery.should(goodsQuery); |
| | | |
| | | // 5. 设置至少匹配一个条件(OR逻辑) |
| | | boolQuery.minimumShouldMatch(1); |
| | | |
| | | // 6. 状态为已发布的 |
| | | boolQuery.must(QueryBuilders.termQuery("status", VideoStatusEnum.PUBLISHED.getValue())); |
| | | queryBuilder.withQuery(boolQuery); |
| | | } else { |
| | | return Result.ok().data(new ArrayList<>()).total(0); |
| | | } |
| | | NativeSearchQuery query = queryBuilder.build(); |
| | | SearchHits<VideoIndex> searchHits = restTemplate.search(query, VideoIndex.class); |
| | | if (CollectionUtils.isEmpty(searchHits.getSearchHits())) { |
| | | return Result.ok().data(new ArrayList<>()).total(0); |
| | | } |
| | | List<VideoIndex> data = searchHits.stream().map(hit -> hit.getContent()).collect(Collectors.toList()); |
| | | List<String> videoIds = data.stream().map(VideoIndex::getId).collect(Collectors.toList()); |
| | | // 对象转换 |
| | | Map<String, List<SimpleMyCollectVO>> collectMap = myCollectService.getCollectsByVideoIds(videoIds) |
| | | .stream() |
| | | .collect(Collectors.groupingBy(SimpleMyCollectVO::getRefId)); |
| | | Map<String, List<SimpleMyThumbsUpVO>> thumbsUpMap = thumbsUpRecordService.getThumbssByVideoIds(videoIds) |
| | | .stream() |
| | | .collect(Collectors.groupingBy(SimpleMyThumbsUpVO::getRefId)); |
| | | List<String> subscribes = mySubscribeService.getSubscribesByUserId(UserContext.getCurrentUserId()); |
| | | List<WxVideoVO> vos = data.stream().map(videoIndex -> { |
| | | WxVideoVO wxVideoVO = new WxVideoVO(); |
| | | BeanUtils.copyProperties(videoIndex, wxVideoVO); |
| | | // 判断是否关注作者、是否点赞、是否收藏 |
| | | wxVideoVO.setCollected(CollectionUtils.isNotEmpty(collectMap.get(wxVideoVO.getId()))); |
| | | wxVideoVO.setThumbsUp(CollectionUtils.isNotEmpty(thumbsUpMap.get(wxVideoVO.getId()))); |
| | | wxVideoVO.setAuthorAvatar(cosUtil.getPreviewUrl(wxVideoVO.getAuthorAvatar())); |
| | | if (UserContext.getCurrentUserId().equals(wxVideoVO.getAuthorId())) { |
| | | wxVideoVO.setSubscribeThisAuthor(Boolean.TRUE); |
| | | } else { |
| | | wxVideoVO.setSubscribeThisAuthor(subscribes.contains(wxVideoVO.getAuthorId())); |
| | | } |
| | | if (VideoContentTypeEnum.VIDEO.getValue().equals(wxVideoVO.getVideoContentType())) { |
| | | wxVideoVO.setCoverUrl(cosUtil.getPreviewUrl(wxVideoVO.getCoverFileKey())); |
| | | wxVideoVO.setVideoUrl(cosUtil.getPreviewUrl(wxVideoVO.getVideoFileKey())); |
| | | } else if (VideoContentTypeEnum.IMG.getValue().equals(wxVideoVO.getVideoContentType()) && StringUtils.isNotBlank(wxVideoVO.getVideoImgs())) { |
| | | wxVideoVO.setImgs(JSON.parseArray(wxVideoVO.getVideoImgs(), String.class).stream().map(fileKey -> cosUtil.getPreviewUrl(fileKey)).collect(Collectors.toList())); |
| | | wxVideoVO.setCoverUrl(wxVideoVO.getImgs().get(0)); |
| | | } |
| | | return wxVideoVO; |
| | | }).collect(Collectors.toList()); |
| | | return Result.ok().data(vos).total(searchHits.getTotalHits()); |
| | | } |
| | | |
| | | @Override |
| | | public Result getHistoryPage(VideoHistoryQuery query) { |
| | | query.setUserId(UserContext.getCurrentUserId()); |
| | | IPage<VideoHistoryVO> page = PageUtil.getPage(query, VideoHistoryVO.class); |
| | | baseMapper.getHistoryPage(page, query); |
| | | if (CollectionUtils.isNotEmpty(page.getRecords())) { |
| | | if (page.getTotal() > 0) { |
| | | List<String> videoIds = page.getRecords().stream().map(VideoHistoryVO::getId).collect(Collectors.toList()); |
| | | Map<String, List<SimpleVideoTagVO>> tagMap = videoTagRefService.getTagsByVideoIds(videoIds) |
| | | .stream() |
| | | .collect(Collectors.groupingBy(SimpleVideoTagVO::getVideoId)); |
| | | Map<String, List<SimpleMyCollectVO>> collectMap = myCollectService.getCollectsByVideoIds(videoIds) |
| | | .stream() |
| | | .collect(Collectors.groupingBy(SimpleMyCollectVO::getRefId)); |
| | | Map<String, List<SimpleMyThumbsUpVO>> thumbsUpMap = thumbsUpRecordService.getThumbssByVideoIds(videoIds) |
| | | .stream() |
| | | .collect(Collectors.groupingBy(SimpleMyThumbsUpVO::getRefId)); |
| | | List<String> subscribes = mySubscribeService.getSubscribesByUserId(UserContext.getCurrentUserId()); |
| | | // 3. 获取视频临时访问地址、设置视频标签、我是否收藏、是否点赞、作者是否关注 |
| | | page.getRecords().forEach(v -> { |
| | | v.setTagList(tagMap.get(v.getId())); |
| | | v.setCollected(CollectionUtils.isNotEmpty(collectMap.get(v.getId()))); |
| | | v.setThumbsUp(CollectionUtils.isNotEmpty(thumbsUpMap.get(v.getId()))); |
| | | v.setCommentNum(this.getCommentNum(v.getId(), v.getCommentNum())); |
| | | v.setCollectNum(this.getCollectNum(v.getId(), v.getCollectNum())); |
| | | v.setThumbsUpNum(this.getThumbsUpNum(v.getId(), v.getThumbsUpNum())); |
| | | v.setAuthorAvatar(cosUtil.getPreviewUrl(v.getAuthorAvatar())); |
| | | if (VideoContentTypeEnum.VIDEO.getValue().equals(v.getVideoContentType())) { |
| | | v.setVideoUrl(cosUtil.getPreviewUrl(v.getVideoFileKey())); |
| | | v.setCoverUrl(cosUtil.getPreviewUrl(v.getCoverFileKey())); |
| | | } else if (VideoContentTypeEnum.IMG.getValue().equals(v.getVideoContentType()) && StringUtils.isNotBlank(v.getVideoImgs())) { |
| | | v.setImgs(JSON.parseArray(v.getVideoImgs(), String.class).stream().map(fileKey -> cosUtil.getPreviewUrl(fileKey)).collect(Collectors.toList())); |
| | | } |
| | | if (CollectionUtils.isNotEmpty(v.getGoodsList())) { |
| | | v.getGoodsList().stream().forEach(goods -> { |
| | | goods.setThumbnail(cosUtil.getPreviewUrl(goods.getThumbnail())); |
| | | }); |
| | | } |
| | | v.setSubscribeThisAuthor(subscribes.contains(v.getAuthorId())); |
| | | }); |
| | | } |
| | | } |
| | | return Result.ok().data(page.getRecords()).total(page.getTotal()); |
| | | } |
| | | } |