buyer-api/src/main/java/cn/lili/controller/lmk/VideoController.java
@@ -2,6 +2,7 @@ import cn.lili.group.Update; import cn.lili.group.Add; import cn.lili.modules.lmk.domain.form.ThumbsUpRecordForm; import cn.lili.modules.lmk.domain.form.VideoFootPrintForm; import cn.lili.modules.lmk.domain.form.VideoHomePageInfoForm; import cn.lili.modules.lmk.domain.form.WxVideoForm; @@ -38,7 +39,7 @@ } @PutMapping @ApiOperation(value = "修改", notes = "修改") @ApiOperation(value = "修改视频", notes = "修改视频") public Result update(@RequestBody @Validated(Update.class) WxVideoForm form) { return videoService.updatePublish(form); } @@ -118,6 +119,12 @@ return videoService.getAuthorCollectVideoPage(query); } @GetMapping("/author-like-video-page") @ApiOperation(value = "获取视频主页作者点赞的视频分页", notes = "获取视频主页作者点赞的视频分页") public Result getAuthorLikeVideoPage(AuthorVideoQuery query) { return videoService.getAuthorLikeVideoPage(query); } @PostMapping("/home-page-info-edit") @ApiOperation(value = "保存视频主页的个人信息修改", notes = "保存视频主页的个人信息修改") public Result homePageInfoEdit(@RequestBody @Validated VideoHomePageInfoForm form) { @@ -129,4 +136,10 @@ public Result wxDetail(@PathVariable("id") String id) { return videoService.wxDetail(id); } @PostMapping("/change/thumbs-up") @ApiOperation(value = "点赞/取消点赞视频", notes = "点赞/取消点赞视频") public Result changeThumbsUp(@RequestBody @Validated(Add.class) ThumbsUpRecordForm form) { return videoService.changeThumbsUp(form); } } consumer/src/main/java/cn/lili/listener/VideoMessageListener.java
@@ -1,9 +1,13 @@ package cn.lili.listener; import cn.lili.cache.Cache; import cn.lili.elasticsearch.EsSuffix; import cn.lili.modules.lmk.domain.dto.VideoEsUpdateDTO; import cn.lili.modules.lmk.domain.entity.MyCollect; import cn.lili.modules.lmk.domain.entity.ThumbsUpRecord; import cn.lili.modules.lmk.domain.es.VideoIndex; import cn.lili.modules.lmk.domain.form.ThumbsUpRecordForm; import cn.lili.modules.lmk.service.EsService; import cn.lili.modules.lmk.service.ThumbsUpRecordService; import cn.lili.modules.lmk.service.VideoCommentService; import cn.lili.modules.lmk.service.VideoService; @@ -16,6 +20,7 @@ import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.core.RocketMQListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; /** @@ -33,20 +38,35 @@ private VideoService videoService; @Autowired @Qualifier("videoEsServiceImpl") private EsService esService; @Autowired private Cache<Object> cache; @Override public void onMessage(MessageExt messageExt) { try { String msg = new String(messageExt.getBody()); if (StringUtils.isBlank(msg)) { log.error("video msg is null, cant not consumer"); return; } switch (VideoTagsEnum.valueOf(messageExt.getTags())) { case COLLECT: this.collect(msg); break; case THUMBS_UP: this.changeThumbsUp(msg); break; case ES_RECREATE: this.recreateVideoIndex(); break; case ES_DOC_ADD_OR_UPDATE: this.addOrUpdateEsVideo(msg); break; case ES_DOC_UPDATE_SOME_FIELD: this.updateEsVideoSomeField(msg); break; case ES_DOC_DEL: this.delEsVideo(msg); default: log.error("video msg not match correct tag, consumer err"); break; @@ -66,4 +86,46 @@ videoService.mqCollectChange(collect); } /** * 重建视频索引 * */ public void recreateVideoIndex() { esService.recreateIndex(EsSuffix.VIDEO_INDEX_NAME, "/es/video.json"); } /** * 新增es视频数据/更新 * * @param msg */ public void addOrUpdateEsVideo(String msg) { VideoIndex videoIndex = JSON.parseObject(msg, VideoIndex.class); esService.addOrUpdateDocument(videoIndex); } /** * 更新es视频的某些字段 * * @param msg */ public void updateEsVideoSomeField(String msg) { VideoEsUpdateDTO dto = JSON.parseObject(msg, VideoEsUpdateDTO.class); esService.updateSomeField(EsSuffix.VIDEO_INDEX_NAME, dto.getId(), dto.getFields()); } /** * 根据id删除es中的视频 * * @param id */ public void delEsVideo(String id) { esService.deleteDocument(EsSuffix.VIDEO_INDEX_NAME, id); } public void changeThumbsUp(String msg) { ThumbsUpRecord thumbsUpRecord = JSON.parseObject(msg, ThumbsUpRecord.class); videoService.mqChangeThumbsUp(thumbsUpRecord); } } framework/src/main/java/cn/lili/cache/CachePrefix.java
@@ -528,6 +528,11 @@ */ VIDEO_COLLECT_NUM, /** * 视频点赞数量 */ VIDEO_THUMBS_UP_NUM, /** * 扫码登录 framework/src/main/java/cn/lili/elasticsearch/EsSuffix.java
@@ -14,6 +14,11 @@ public static final String GOODS_INDEX_NAME = "goods"; /** * 视频索引后缀 */ public static final String VIDEO_INDEX_NAME = "video"; /** * 日志索引后缀 */ public static final String LOGS_INDEX_NAME = "logs"; framework/src/main/java/cn/lili/modules/lmk/constant/RedisKeyExpireConstant.java
@@ -25,6 +25,11 @@ */ public static final Long COLLECT_NUM_EXPIRE = 15l; /** * 视频点赞数过期时间 */ public static final Long VIDEO_THUMBS_UP_EXPIRE = 15l; /** * 过期时间单位 framework/src/main/java/cn/lili/modules/lmk/domain/dto/VideoEsUpdateDTO.java
New file @@ -0,0 +1,24 @@ package cn.lili.modules.lmk.domain.dto; import lombok.Data; import java.util.Map; /** * @author:xp * @date:2025/7/1 10:01 */ @Data public class VideoEsUpdateDTO { /** * 视频id */ private String id; /** * 修改哪些字段 */ private Map<String, Object> fields; } framework/src/main/java/cn/lili/modules/lmk/domain/entity/Video.java
@@ -85,10 +85,18 @@ /** 评论数 */ private Integer commentNum; @TableField("thumbs_up_num") /** 点赞数 */ private Integer thumbsUpNum; @TableField("collect_num_job") /** 是否需要定时任务统计收藏数 */ private Boolean collectNumJob; @TableField("thumbs_up_num_job") /** 是否需要定时任务统计点赞数 */ private Boolean thumbsUpNumJob; @TableField("comment_num_job") /** 是否需要定时任务统计评论数 */ private Boolean commentNumJob; framework/src/main/java/cn/lili/modules/lmk/domain/es/VideoIndex.java
New file @@ -0,0 +1,94 @@ package cn.lili.modules.lmk.domain.es; import cn.lili.elasticsearch.EsSuffix; import cn.lili.modules.lmk.domain.vo.SimpleVideoTagVO; import cn.lili.modules.lmk.domain.vo.VideoGoodsDetailVO; import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import java.util.List; /** * es的视频document(存储单元) * * @author:xp * @date:2025/6/30 14:50 */ @Data @Document(indexName = "#{@elasticsearchProperties.indexPrefix}_" + EsSuffix.VIDEO_INDEX_NAME, createIndex = false) public class VideoIndex { /** 视频id */ @Id private String id; /** 视频标题 */ @Field(type = FieldType.Text, searchAnalyzer = "ik_max_word") private String title; /** 作者id */ @Field(type = FieldType.Keyword) private String authorId; /** 作者名称 */ @Field(type = FieldType.Text, searchAnalyzer = "ik_max_word") private String authorName; /** 作者头像 */ @Field(type = FieldType.Keyword) private String authorAvatar; /** 封面 */ @Field(type = FieldType.Keyword) private String coverFileKey; /** 视频地址 */ @Field(type = FieldType.Keyword) private String videoFileKey; /** * 视频内容类型:视频、图片 * @see cn.lili.modules.lmk.enums.general.VideoContentTypeEnum */ @Field(type = FieldType.Keyword) private String videoContentType; /** * 视频类型:视频、大健康、神厨 * @see cn.lili.modules.lmk.enums.general.VideoTypeEnum */ @Field(type = FieldType.Keyword) private String videoType; /** 图集-json数组 */ @Field(type = FieldType.Keyword) private String videoImgs; /** 视频标签 */ @Field(type = FieldType.Nested) private List<SimpleVideoTagVO> tagList; /** 视频时长:秒 */ @Field(type = FieldType.Keyword) private Long videoDuration; /** 视频填充模式 */ @Field(type = FieldType.Keyword) private String videoFit; /** 视频状态 */ @Field(type = FieldType.Keyword) private String status; /** 商品信息 */ @Field(type = FieldType.Nested) private List<VideoGoodsDetailVO> goodsList; /** 是否推荐视频 */ @Field(type = FieldType.Keyword) private boolean recommend = false; } framework/src/main/java/cn/lili/modules/lmk/domain/query/VideoQuery.java
@@ -24,7 +24,7 @@ @ApiModelProperty("作者id") private String authorId; @ApiModelProperty("视频来源:recommend推荐、author某作者的视频、collect某作者收藏的视频") @ApiModelProperty("视频来源:recommend推荐、author某作者的视频、collect某作者收藏的视频、like某作者点赞的视频") private String videoFrom; /** framework/src/main/java/cn/lili/modules/lmk/domain/vo/KitchenVideoVO.java
@@ -97,6 +97,10 @@ @ApiModelProperty("评论数") private Integer commentNum; /** 点赞数 */ @ApiModelProperty("点赞数") private Integer thumbsUpNum; /** 权重 */ @ApiModelProperty("权重") private double weight; framework/src/main/java/cn/lili/modules/lmk/domain/vo/SimpleMyThumbsUpVO.java
New file @@ -0,0 +1,24 @@ package cn.lili.modules.lmk.domain.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * 我的点赞展示 * * @author xp * @since 2025-05-22 */ @Data @ApiModel(value = "我的点赞响应数据", description = "我的点赞响应数据") public class SimpleMyThumbsUpVO { private String id; /** 点赞对应的id,视频id、评论id,根据点赞类型定 */ @ApiModelProperty("点赞对应的id,视频id、评论id,根据点赞类型定") private String refId; } framework/src/main/java/cn/lili/modules/lmk/domain/vo/SimpleVideoTagVO.java
@@ -6,6 +6,8 @@ import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.springframework.beans.BeanUtils; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.lang.NonNull; /** @@ -19,13 +21,16 @@ public class SimpleVideoTagVO { @ApiModelProperty("标签id") @Field(type = FieldType.Keyword) private String id; /** 标签名称 */ @ApiModelProperty("标签名称") @Field(type = FieldType.Text, searchAnalyzer = "ik_max_word") private String tagName; @ApiModelProperty(hidden = true) @Field(type = FieldType.Keyword) private String videoId; framework/src/main/java/cn/lili/modules/lmk/domain/vo/VideoGoodsDetailVO.java
@@ -4,6 +4,8 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; /** * @author:xp @@ -14,20 +16,26 @@ public class VideoGoodsDetailVO { @ApiModelProperty("商品id") @Field(type = FieldType.Keyword) private String goodsId; @ApiModelProperty("商品skuid") @Field(type = FieldType.Keyword) private String id; @ApiModelProperty("商品名称") @Field(type = FieldType.Text, searchAnalyzer = "ik_max_word") private String goodsName; @ApiModelProperty("价格") @Field(type = FieldType.Keyword) private String price; @ApiModelProperty("缩略图") @Field(type = FieldType.Keyword) private String thumbnail; @ApiModelProperty("商品数量") @Field(type = FieldType.Keyword) private Integer goodsNum; } framework/src/main/java/cn/lili/modules/lmk/domain/vo/VideoVO.java
@@ -89,6 +89,10 @@ @ApiModelProperty("评论数") private Integer commentNum; /** 点赞数 */ @ApiModelProperty("点赞数") private Integer thumbsUpNum; /** 权重 */ @ApiModelProperty("权重") private double weight; framework/src/main/java/cn/lili/modules/lmk/domain/vo/WxVideoVO.java
@@ -81,6 +81,9 @@ @ApiModelProperty("当前用户是否收藏了该视频") private Boolean collected = Boolean.FALSE; @ApiModelProperty("当前用户是否点赞了该视频") private Boolean thumbsUp = Boolean.FALSE; /** 收藏数 */ @ApiModelProperty("收藏数") private Integer collectNum; @@ -89,6 +92,10 @@ @ApiModelProperty("评论数") private Integer commentNum; /** 点赞数 */ @ApiModelProperty("点赞数") private Integer thumbsUpNum; /** 视频拥有的操作 */ @ApiModelProperty("视频支持的操作") private List<VideoOption> options; framework/src/main/java/cn/lili/modules/lmk/enums/general/ThumbsUpTypeEnum.java
@@ -13,6 +13,7 @@ public enum ThumbsUpTypeEnum { VIDEO_COMMENT("video_comment", "视频评论"), VIDEO("video", "视频"), ; private final String value; framework/src/main/java/cn/lili/modules/lmk/mapper/ThumbsUpRecordMapper.java
@@ -2,6 +2,7 @@ import cn.lili.modules.lmk.domain.entity.ThumbsUpRecord; import cn.lili.modules.lmk.domain.vo.CollectTypeNumVO; import cn.lili.modules.lmk.domain.vo.SimpleMyThumbsUpVO; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import cn.lili.modules.lmk.domain.vo.ThumbsUpRecordVO; @@ -38,4 +39,20 @@ * @return */ List<CollectTypeNumVO> countNumGroupByComment(); /** * 统计视频点赞数量 * * @return */ List<CollectTypeNumVO> countNumGroupByVideo(); /** * 根据视频id查询我的点赞 * * @param videoIds * @param userId * @return */ List<SimpleMyThumbsUpVO> getThumbssByVideoIds(@Param("videoIds") List<String> videoIds, @Param("userId") String userId); } framework/src/main/java/cn/lili/modules/lmk/mapper/VideoMapper.java
@@ -1,6 +1,7 @@ package cn.lili.modules.lmk.mapper; import cn.lili.modules.lmk.domain.entity.Video; import cn.lili.modules.lmk.domain.es.VideoIndex; import cn.lili.modules.lmk.domain.query.*; import cn.lili.modules.lmk.domain.vo.*; import com.baomidou.mybatisplus.core.mapper.BaseMapper; @@ -66,6 +67,13 @@ void updateCommentNumBatch(@Param("list") List<CollectTypeNumVO> numList); /** * 批量更新视频点赞数 * * @param numList */ void updateThumbsUpNumBatch(@Param("list") List<CollectTypeNumVO> numList); /** * 视频主页作者信息 * * @param authorId @@ -106,6 +114,14 @@ IPage getAuthorCollectVideoPage(IPage page, @Param("query") AuthorVideoQuery query); /** * 获取视频主页作者点赞的视频分页 * * @param page * @param query */ IPage getAuthorLikeVideoPage(IPage page, @Param("query") AuthorVideoQuery query); /** * 小程序-视频详情 * * @param id @@ -130,4 +146,13 @@ * @return */ List<VideoGoodsDetailVO> getVideoGoods(@Param("id") String videoId); /** * es同步查询视频数据 * * @param start 开始位置 * @param pageSize 每页条数 * @return */ List<VideoIndex> getEsPage(@Param("start") int start, @Param("pageSize") int pageSize); } framework/src/main/java/cn/lili/modules/lmk/service/EsService.java
New file @@ -0,0 +1,68 @@ package cn.lili.modules.lmk.service; import java.util.Map; /** * es处理 * * @author:xp * @date:2025/6/30 14:47 */ public interface EsService { /** * 获取索引的完整名称 * * @param indexName * @return */ String getIndexFullName(String indexName); /** * 创建索引 * * @param indexName 索引名称 * @param mappingJsonPath json文件位置,相对于resource目录,例如:/es/video.json */ void createIndex(String indexName, String mappingJsonPath); /** * 重建索引,并重新添加索引数据 * * @param indexName 索引名称 * @param mappingJsonPath json文件位置,相对于resource目录,例如:/es/video.json */ void recreateIndex(String indexName, String mappingJsonPath); /** * 添加/修改 文档,如果是修改,则是整条数据更新 * * @param data 数据对象 */ void addOrUpdateDocument(Object data); /** * 更新某些字段的值 * * @param indexName 索引名称 * @param id 数据id * @param updateList 更新哪些字段,key 字段 value要修改的值 */ void updateSomeField(String indexName, String id, Map<String, Object> updateList); /** * 删除文档 * @param indexName 索引名称 * @param id es主键,可传业务主键 */ void deleteDocument(String indexName, String id); /** * 索引是否存在 * * @param indexName * @return */ boolean indexExist(String indexName); } framework/src/main/java/cn/lili/modules/lmk/service/ThumbsUpRecordService.java
@@ -5,8 +5,10 @@ import cn.lili.modules.lmk.domain.form.ThumbsUpRecordForm; import cn.lili.modules.lmk.domain.query.ThumbsUpRecordQuery; import cn.lili.modules.lmk.domain.vo.CollectTypeNumVO; import cn.lili.modules.lmk.domain.vo.SimpleMyThumbsUpVO; import com.baomidou.mybatisplus.extension.service.IService; import java.util.Collection; import java.util.List; /** @@ -57,4 +59,19 @@ * @return */ List<CollectTypeNumVO> countNumGroupByComment(); /** * 统计视频的点赞数 * * @return */ List<CollectTypeNumVO> countNumGroupByVideo(); /** * 根据视频id查询我的点赞 * * @param videoIds * @return */ List<SimpleMyThumbsUpVO> getThumbssByVideoIds(List<String> videoIds); } framework/src/main/java/cn/lili/modules/lmk/service/VideoService.java
@@ -2,6 +2,7 @@ import cn.lili.group.Add; import cn.lili.modules.lmk.domain.entity.MyCollect; import cn.lili.modules.lmk.domain.entity.ThumbsUpRecord; import cn.lili.modules.lmk.domain.entity.Video; import cn.lili.modules.lmk.domain.form.*; import cn.lili.modules.lmk.domain.query.*; @@ -20,20 +21,6 @@ * @since 2025-05-16 */ public interface VideoService extends IService<Video> { /** * 添加 * @param form * @return */ Result add(WxVideoForm form); /** * 修改 * @param form * @return */ Result update(WxVideoForm form); /** * 批量删除 @@ -178,6 +165,14 @@ Result getAuthorCollectVideoPage(AuthorVideoQuery query); /** * 获取视频主页作者点赞视频分页 * * @param query * @return */ Result getAuthorLikeVideoPage(AuthorVideoQuery query); /** * 保存视频主页的个人信息修改 * * @param form @@ -265,4 +260,34 @@ * @param collect */ void mqCollectChange(MyCollect collect); /** * 重建视频es索引 * * @return */ Result recreateEsIndex(); /** * 修改视频点赞状态 * * @param form * @return */ Result changeThumbsUp(ThumbsUpRecordForm form); /** * mq修改视频点赞状态 * * @param thumbsUpRecord */ void mqChangeThumbsUp(ThumbsUpRecord thumbsUpRecord); /** * 批量更新视频点赞数量 * * @param numList */ void updateThumbsUpNumBatch(List<CollectTypeNumVO> numList); } framework/src/main/java/cn/lili/modules/lmk/service/impl/ThumbsUpRecordServiceImpl.java
@@ -1,6 +1,8 @@ package cn.lili.modules.lmk.service.impl; import cn.lili.common.security.context.UserContext; import cn.lili.modules.lmk.domain.vo.CollectTypeNumVO; import cn.lili.modules.lmk.domain.vo.SimpleMyThumbsUpVO; import com.baomidou.mybatisplus.core.metadata.IPage; import cn.lili.modules.lmk.domain.entity.ThumbsUpRecord; import cn.lili.modules.lmk.mapper .ThumbsUpRecordMapper; @@ -94,4 +96,14 @@ public List<CollectTypeNumVO> countNumGroupByComment() { return baseMapper.countNumGroupByComment(); } @Override public List<CollectTypeNumVO> countNumGroupByVideo() { return baseMapper.countNumGroupByVideo(); } @Override public List<SimpleMyThumbsUpVO> getThumbssByVideoIds(List<String> videoIds) { return baseMapper.getThumbssByVideoIds(videoIds, UserContext.getCurrentUserId()); } } framework/src/main/java/cn/lili/modules/lmk/service/impl/VideoEsServiceImpl.java
New file @@ -0,0 +1,214 @@ package cn.lili.modules.lmk.service.impl; import cn.lili.elasticsearch.BaseElasticsearchService; import cn.lili.elasticsearch.EsSuffix; import cn.lili.elasticsearch.config.ElasticsearchProperties; import cn.lili.modules.lmk.domain.entity.Video; import cn.lili.modules.lmk.domain.es.VideoIndex; import cn.lili.modules.lmk.enums.general.VideoStatusEnum; import cn.lili.modules.lmk.mapper.VideoMapper; import cn.lili.modules.lmk.service.EsService; import cn.lili.modules.search.repository.EsVideoIndexRepository; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.indices.CreateIndexRequest; import org.elasticsearch.client.indices.CreateIndexResponse; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.stereotype.Service; import org.springframework.util.FileCopyUtils; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.concurrent.*; /** * 视频es * * @author:xp * @date:2025/6/30 15:54 */ @Slf4j @RequiredArgsConstructor @Service("videoEsServiceImpl") public class VideoEsServiceImpl extends BaseElasticsearchService implements EsService { private final ElasticsearchProperties elasticsearchProperties; private final VideoMapper videoMapper; private final EsVideoIndexRepository esVideoIndexRepository; @Override public String getIndexFullName(String indexName) { return elasticsearchProperties.getIndexPrefix() + "_" + indexName; } @Override public void createIndex(String indexName, String mappingJsonPath) { if (! indexName.startsWith(elasticsearchProperties.getIndexPrefix())) { indexName = this.getIndexFullName(indexName); } if (this.indexExist(indexName)) { throw new RuntimeException(String.format("索引:%s已经存在,无法创建", indexName)); } CreateIndexRequest request = new CreateIndexRequest(indexName); // 1. 配置索引 request.settings(Settings.builder() .put("index.number_of_shards", elasticsearchProperties.getIndex().getNumberOfShards()) .put("index.number_of_replicas", elasticsearchProperties.getIndex().getNumberOfReplicas()) .put("index.max_result_window", 100000) //最大查询结果数 .put("index.mapping.total_fields.limit", 2000)); // 2. 配置mapping String mapping; try (InputStream inputStream = this.getClass().getResourceAsStream(mappingJsonPath)) { byte[] bytes = FileCopyUtils.copyToByteArray(inputStream); mapping = new String(bytes, StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(String.format("读取es映射json文件:%s异常", mappingJsonPath), e); } request.mapping(mapping, XContentType.JSON); // 3. 创建索引 try { CreateIndexResponse createIndexResponse = client.indices().create(request, COMMON_OPTIONS); } catch (IOException e) { throw new RuntimeException(String.format("es创建索引失败:%s", indexName), e); } } @Override public void recreateIndex(String indexName, String mappingJsonPath) { indexName = this.getIndexFullName(indexName); // 1. 如果索引存在,先删除索引,再创建索引 if (this.indexExist(indexName)) { DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName); try { AcknowledgedResponse deleteRes = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT); this.createIndex(indexName, mappingJsonPath); } catch (IOException e) { log.error("删除索引失败", e); throw new RuntimeException("删除索引失败"); } } else { this.createIndex(indexName, mappingJsonPath); } // 2. 多线程查询视频数据,构建文档对象 Long totalVideo = new LambdaQueryChainWrapper<>(videoMapper) .count(); int totalThreads = (int) Math.ceil((double) totalVideo / 200); // 计算需要多少个线程 CountDownLatch latch = new CountDownLatch(totalThreads); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 4, 10, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); BlockingQueue<VideoIndex> dataList = new LinkedBlockingQueue<>(); for (int page = 0; page < totalThreads; page++) { final int currentPage = page; threadPoolExecutor.execute(() -> { try { List<VideoIndex> pageData = videoMapper.getEsPage(currentPage * 200, 200); dataList.addAll(pageData); } catch (Exception e) { log.error("第{}页数据查询失败", currentPage, e); } finally { latch.countDown(); // 线程执行完成 -1 } }); } try { latch.await(); // 等待所有线程执行完成 // 3. 添加es数据 // BulkRequest bulkRequest = new BulkRequest(); // String finalIndexName = indexName; // dataList.forEach(data -> { // IndexRequest indexRequest = new IndexRequest(finalIndexName) // .id(data.getId()) // .source(data); // bulkRequest.add(indexRequest); // }); // client.bulk(bulkRequest, RequestOptions.DEFAULT); esVideoIndexRepository.saveAll(dataList); } catch (InterruptedException e) { log.error("多线程读取视频数据异常", e); } finally { threadPoolExecutor.shutdown(); } } @Override public void addOrUpdateDocument(Object data) { VideoIndex videoIndex = (VideoIndex) data; esVideoIndexRepository.save(videoIndex); // indexName = this.getIndexFullName(indexName); // IndexRequest request = new IndexRequest(indexName); // request.id(id).source(data); // try { // client.index(request, RequestOptions.DEFAULT); // } catch (IOException e) { // throw new RuntimeException("es文档添加/修改失败", e); // } } @Override public void updateSomeField(String indexName, String id, Map<String, Object> updateList) { indexName = this.getIndexFullName(indexName); // 构建更新请求 UpdateRequest request = new UpdateRequest(indexName, id); try { // 构建更新内容 XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); for (Map.Entry<String, Object> entry : updateList.entrySet()) { builder.field(entry.getKey(), entry.getValue()); } builder.endObject(); request.doc(builder); // 设置部分更新内容 // 可选配置 request.retryOnConflict(2); // 冲突重试次数 // request.fetchSource(true); // 返回更新后的文档 client.update(request, RequestOptions.DEFAULT); } catch (IOException e) { e.printStackTrace(); } } @Override public void deleteDocument(String indexName, String id) { indexName = this.getIndexFullName(indexName); DeleteRequest request = new DeleteRequest(indexName, id); try { client.delete(request, RequestOptions.DEFAULT); } catch (IOException e) { throw new RuntimeException("删除es文档失败:" + id, e); } } @Override public boolean indexExist(String indexName) { if (!indexName.startsWith(elasticsearchProperties.getIndexPrefix())) { indexName = this.getIndexFullName(indexName); } return super.indexExist(indexName); } } framework/src/main/java/cn/lili/modules/lmk/service/impl/VideoServiceImpl.java
@@ -2,9 +2,13 @@ import cn.lili.cache.Cache; import cn.lili.cache.CachePrefix; import cn.lili.common.properties.RocketmqCustomProperties; import cn.lili.common.security.context.UserContext; import cn.lili.elasticsearch.EsSuffix; import cn.lili.modules.lmk.constant.RedisKeyExpireConstant; import cn.lili.modules.lmk.domain.dto.VideoEsUpdateDTO; import cn.lili.modules.lmk.domain.entity.*; import cn.lili.modules.lmk.domain.es.VideoIndex; import cn.lili.modules.lmk.domain.form.*; import cn.lili.modules.lmk.domain.query.*; import cn.lili.modules.lmk.domain.vo.*; @@ -14,6 +18,9 @@ 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.rocketmq.RocketmqSendCallbackBuilder; import cn.lili.rocketmq.tags.CommentTagsEnum; import cn.lili.rocketmq.tags.VideoTagsEnum; import cn.lili.utils.COSUtil; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -26,6 +33,8 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; import cn.lili.utils.PageUtil; @@ -62,33 +71,10 @@ private final KitchenTypeService kitchenTypeService; private final Cache cache; /** * 添加 * @param form * @return */ @Override public Result add(WxVideoForm form) { Video entity = WxVideoForm.getEntityByForm(form, null); baseMapper.insert(entity); return Result.ok("添加成功"); } private final RocketmqCustomProperties rocketmqCustomProperties; private final RocketMQTemplate rocketMQTemplate; private final ThumbsUpRecordService thumbsUpRecordService; /** * 修改 * @param form * @return */ @Override public Result update(WxVideoForm form) { Video entity = baseMapper.selectById(form.getId()); // 为空抛IllegalArgumentException,做全局异常处理 Assert.notNull(entity, "记录不存在"); BeanUtils.copyProperties(form, entity); baseMapper.updateById(entity); return Result.ok("修改成功"); } /** * 批量删除 @@ -107,8 +93,18 @@ * @return */ @Override @Transactional(rollbackFor = Exception.class) public Result removeById(String id) { baseMapper.deleteById(id); new LambdaUpdateChainWrapper<>(videoGoodsService.getBaseMapper()) .eq(VideoGoods::getVideoId, id) .remove(); new LambdaUpdateChainWrapper<>(videoTagRefService.getBaseMapper()) .eq(VideoTagRef::getVideoId, id) .remove(); // mq异步删除es数据 String destination = rocketmqCustomProperties.getVideoTopic() + ":" + VideoTagsEnum.ES_DOC_DEL.name(); rocketMQTemplate.asyncSend(destination, id, RocketmqSendCallbackBuilder.commonCallback()); return Result.ok("删除成功"); } @@ -170,11 +166,13 @@ video.setStatus(VideoStatusEnum.AUDITING.getValue()); video.setCoverUrl(form.getCover()); video.setVideoType(VideoTypeEnum.VIDEO.getValue()); video.setRecommend(Boolean.FALSE); if (VideoContentTypeEnum.IMG.getValue().equals(form.getVideoContentType())) { video.setVideoImgs(JSON.toJSONString(form.getVideoImgs())); } baseMapper.insert(video); // 2.处理标签 List<SimpleVideoTagVO> esTagList = new ArrayList<>(2); List<VideoTagRef> videoTagRefs = form.getTags().stream().map(tag -> { VideoTagRef videoTagRef = new VideoTagRef(); videoTagRef.setVideoId(video.getId()); @@ -194,14 +192,19 @@ } else { videoTagRef.setVideoTagId(tag.getId()); } SimpleVideoTagVO esTag = new SimpleVideoTagVO(); esTag.setVideoId(video.getId()); esTag.setTagName(tag.getTagName()); esTag.setId(tag.getId()); esTagList.add(esTag); return videoTagRef; }).collect(Collectors.toList()); videoTagRefService.saveBatch(videoTagRefs); // 3. 保存视频文件信息 lmkFileService.addByForm(form.getFileInfo()); // 4. 处理选择的商品 List<VideoGoods> videoGoods = new ArrayList<>(2); if (CollectionUtils.isNotEmpty(form.getGoodsList())) { List<VideoGoods> videoGoods = new ArrayList<>(2); for (int i = 0; i < form.getGoodsList().size(); i++) { VideoGoods e = new VideoGoods(); e.setVideoId(video.getId()); @@ -209,10 +212,23 @@ e.setGoodsSkuId(form.getGoodsList().get(i).getGoodsSkuId()); e.setGoodsNum(form.getGoodsList().get(i).getGoodsNum()); e.setOrderNum(i); videoGoods.add(e); videoGoodsService.save(e); } videoGoodsService.saveBatch(videoGoods); } // 5. 构建es中数据,mq异步处理 VideoIndex videoIndex = new VideoIndex(); BeanUtils.copyProperties(video, videoIndex); videoIndex.setCoverFileKey(video.getCoverUrl()); List<VideoGoodsDetailVO> esGoodsList = videoGoods.stream().map(goods -> { VideoGoodsDetailVO vo = new VideoGoodsDetailVO(); BeanUtils.copyProperties(goods, vo); return vo; }).collect(Collectors.toList()); videoIndex.setGoodsList(esGoodsList); videoIndex.setTagList(esTagList); String destination = rocketmqCustomProperties.getVideoTopic() + ":" + VideoTagsEnum.ES_DOC_ADD_OR_UPDATE.name(); rocketMQTemplate.asyncSend(destination, JSON.toJSONString(videoIndex), RocketmqSendCallbackBuilder.commonCallback()); return Result.ok("发布成功,视频审核中~"); } @@ -236,6 +252,7 @@ new LambdaUpdateChainWrapper<>(videoTagRefService.getBaseMapper()) .eq(VideoTagRef::getVideoId, video.getId()) .remove(); List<SimpleVideoTagVO> esTagList = new ArrayList<>(2); List<VideoTagRef> videoTagRefs = form.getTags().stream().map(tag -> { VideoTagRef videoTagRef = new VideoTagRef(); videoTagRef.setVideoId(video.getId()); @@ -255,6 +272,11 @@ } else { videoTagRef.setVideoTagId(tag.getId()); } SimpleVideoTagVO esTag = new SimpleVideoTagVO(); esTag.setVideoId(video.getId()); esTag.setTagName(tag.getTagName()); esTag.setId(tag.getId()); esTagList.add(esTag); return videoTagRef; }).collect(Collectors.toList()); videoTagRefService.saveBatch(videoTagRefs); @@ -264,8 +286,8 @@ new LambdaUpdateChainWrapper<>(videoGoodsService.getBaseMapper()) .eq(VideoGoods::getVideoId, video.getId()) .remove(); List<VideoGoods> videoGoods = new ArrayList<>(2); if (CollectionUtils.isNotEmpty(form.getGoodsList())) { List<VideoGoods> videoGoods = new ArrayList<>(2); for (int i = 0; i < form.getGoodsList().size(); i++) { VideoGoods e = new VideoGoods(); e.setVideoId(video.getId()); @@ -277,6 +299,19 @@ } videoGoodsService.saveBatch(videoGoods); } // 5. 更新es中的数据,mq异步处理 VideoIndex videoIndex = new VideoIndex(); BeanUtils.copyProperties(video, videoIndex); videoIndex.setCoverFileKey(video.getCoverUrl()); List<VideoGoodsDetailVO> esGoodsList = videoGoods.stream().map(goods -> { VideoGoodsDetailVO vo = new VideoGoodsDetailVO(); BeanUtils.copyProperties(goods, vo); return vo; }).collect(Collectors.toList()); videoIndex.setGoodsList(esGoodsList); videoIndex.setTagList(esTagList); String destination = rocketmqCustomProperties.getVideoTopic() + ":" + VideoTagsEnum.ES_DOC_ADD_OR_UPDATE.name(); rocketMQTemplate.asyncSend(destination, JSON.toJSONString(videoIndex), RocketmqSendCallbackBuilder.commonCallback()); return Result.ok("发布成功,视频审核中~"); } @@ -305,6 +340,15 @@ .eq(Video::getId, form.getId()) .set(Video::getRecommend, form.getRecommend()) .update(); // mq异步更新es Map<String, Object> fields = new HashMap<>(2); fields.put("recommend", form.getRecommend()); VideoEsUpdateDTO dto = new VideoEsUpdateDTO(); dto.setId(form.getId()); dto.setFields(fields); String destination = rocketmqCustomProperties.getVideoTopic() + ":" + VideoTagsEnum.ES_DOC_UPDATE_SOME_FIELD.name(); rocketMQTemplate.asyncSend(destination, JSON.toJSONString(dto), RocketmqSendCallbackBuilder.commonCallback()); return Result.ok("设置成功"); } @@ -324,34 +368,64 @@ } videoAuditRecordService.save(auditRecord); // 2. 修改视频状态 Map<String, Object> fields = new HashMap<>(2); if (form.getResult()) { video.setStatus(VideoStatusEnum.PUBLISHED.getValue()); video.setAuditPassTime(new Date()); fields.put("status", VideoStatusEnum.PUBLISHED.getValue()); } else { video.setStatus(VideoStatusEnum.REJECT.getValue()); fields.put("status", VideoStatusEnum.REJECT.getValue()); } baseMapper.updateById(video); // 3. mq异步更新es VideoEsUpdateDTO dto = new VideoEsUpdateDTO(); dto.setId(video.getId()); dto.setFields(fields); String destination = rocketmqCustomProperties.getVideoTopic() + ":" + VideoTagsEnum.ES_DOC_UPDATE_SOME_FIELD.name(); rocketMQTemplate.asyncSend(destination, JSON.toJSONString(dto), RocketmqSendCallbackBuilder.commonCallback()); return Result.ok(); } @Override public Result up(String id) { // 1. 更新数据库 new LambdaUpdateChainWrapper<>(baseMapper) .eq(Video::getId, id) .set(Video::getStatus, VideoStatusEnum.PUBLISHED.getValue()) .update(); // 2. mq异步更新es Map<String, Object> fields = new HashMap<>(2); fields.put("status", VideoStatusEnum.PUBLISHED.getValue()); VideoEsUpdateDTO dto = new VideoEsUpdateDTO(); dto.setId(id); dto.setFields(fields); String destination = rocketmqCustomProperties.getVideoTopic() + ":" + VideoTagsEnum.ES_DOC_UPDATE_SOME_FIELD.name(); rocketMQTemplate.asyncSend(destination, JSON.toJSONString(dto), RocketmqSendCallbackBuilder.commonCallback()); return Result.ok("上架成功"); } @Override public Result down(VideoDownForm form) { // 1. 更新数据库 new LambdaUpdateChainWrapper<>(baseMapper) .eq(Video::getId, form.getId()) .set(Video::getStatus, VideoStatusEnum.DISABLE.getValue()) .update(); // 2. mq异步更新es Map<String, Object> fields = new HashMap<>(2); fields.put("status", VideoStatusEnum.DISABLE.getValue()); VideoEsUpdateDTO dto = new VideoEsUpdateDTO(); dto.setId(form.getId()); dto.setFields(fields); String destination = rocketmqCustomProperties.getVideoTopic() + ":" + VideoTagsEnum.ES_DOC_UPDATE_SOME_FIELD.name(); rocketMQTemplate.asyncSend(destination, JSON.toJSONString(dto), RocketmqSendCallbackBuilder.commonCallback()); // TODO 将下架原因以通知的方式告知用户 return Result.ok("下架成功"); } @@ -361,6 +435,14 @@ .eq(Video::getId, id) .set(Video::getStatus, VideoStatusEnum.DISABLE.getValue()) .update(); // 2. mq异步更新es Map<String, Object> fields = new HashMap<>(2); fields.put("status", VideoStatusEnum.DISABLE.getValue()); VideoEsUpdateDTO dto = new VideoEsUpdateDTO(); dto.setId(id); dto.setFields(fields); String destination = rocketmqCustomProperties.getVideoTopic() + ":" + VideoTagsEnum.ES_DOC_UPDATE_SOME_FIELD.name(); rocketMQTemplate.asyncSend(destination, JSON.toJSONString(dto), RocketmqSendCallbackBuilder.commonCallback()); return Result.ok("下架成功"); } @@ -382,6 +464,10 @@ query2.setAuthorId(query.getAuthorId()); baseMapper.getAuthorCollectVideoPage(page, query2); break; case "like": AuthorVideoQuery query3 = new AuthorVideoQuery(); query3.setAuthorId(query.getAuthorId()); baseMapper.getAuthorLikeVideoPage(page, query3); default: break; } @@ -390,16 +476,21 @@ Map<String, List<SimpleVideoTagVO>> tagMap = videoTagRefService.getTagsByVideoIds(videoIds) .stream() .collect(Collectors.groupingBy(SimpleVideoTagVO::getVideoId)); Map<String, List<SimpleMyCollectVO>> collectMap =myCollectService.getCollectsByVideoIds(videoIds) 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. 获取视频临时访问地址、设置视频标签、我是否收藏、作者是否关注 // 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())); if (VideoContentTypeEnum.VIDEO.getValue().equals(v.getVideoContentType())) { v.setVideoUrl(cosUtil.getPreviewUrl(v.getVideoFileKey())); v.setCoverUrl(cosUtil.getPreviewUrl(v.getCoverFileKey())); @@ -451,6 +542,23 @@ return (Integer) redisNum; } /** * 从redis中获取点赞数量,如果redis中没有则将mysql中的数量写入到redis * * @param videoId * @param mysqlNum * @return */ private Integer getThumbsUpNum(String videoId, Integer mysqlNum) { Object redisNum = cache.get(CachePrefix.VIDEO_THUMBS_UP_NUM.getPrefixWithId(videoId)); if (Objects.isNull(redisNum)) { // redis中没有就把数据库的写到redis中 cache.put(CachePrefix.VIDEO_THUMBS_UP_NUM.getPrefixWithId(videoId), mysqlNum, RedisKeyExpireConstant.VIDEO_THUMBS_UP_EXPIRE, RedisKeyExpireConstant.EXPIRE_DAY); return mysqlNum; } return (Integer) redisNum; } @Override public Result healthRecommendVideo(WxHealthVideoQuery query) { IPage<WxVideoVO> page = PageUtil.getPage(query, WxVideoVO.class); @@ -491,8 +599,8 @@ @Override @Transactional(rollbackFor = Exception.class) public void updateCollectNumBatch(List<CollectTypeNumVO> numList) { // 按500条数据进行拆分 List<List<CollectTypeNumVO>> chunks = ListUtils.partition(numList, 500); // 按200条数据进行拆分 List<List<CollectTypeNumVO>> chunks = ListUtils.partition(numList, 200); for (List<CollectTypeNumVO> chunk : chunks) { baseMapper.updateCollectNumBatch(chunk); new LambdaUpdateChainWrapper<>(baseMapper) @@ -505,8 +613,8 @@ @Override @Transactional(rollbackFor = Exception.class) public void updateCommentNumBatch(List<CollectTypeNumVO> numList) { // 按500条数据进行拆分 List<List<CollectTypeNumVO>> chunks = ListUtils.partition(numList, 500); // 按200条数据进行拆分 List<List<CollectTypeNumVO>> chunks = ListUtils.partition(numList, 200); for (List<CollectTypeNumVO> chunk : chunks) { baseMapper.updateCommentNumBatch(chunk); new LambdaUpdateChainWrapper<>(baseMapper) @@ -547,16 +655,39 @@ IPage<WxVideoVO> page = PageUtil.getPage(query, WxVideoVO.class); query.setAuthorSelf(UserContext.getCurrentUserId().equals(query.getAuthorId())); baseMapper.getAuthorVideoPage(page, query); for (WxVideoVO vo : page.getRecords()) { if (VideoContentTypeEnum.VIDEO.getValue().equals(vo.getVideoContentType())) { vo.setVideoUrl(cosUtil.getPreviewUrl(vo.getVideoFileKey())); vo.setCoverUrl(cosUtil.getPreviewUrl(vo.getCoverFileKey())); } else if (VideoContentTypeEnum.IMG.getValue().equals(vo.getVideoContentType()) && StringUtils.isNotBlank(vo.getVideoImgs())) { vo.setImgs(JSON.parseArray(vo.getVideoImgs(), String.class).stream().map(fileKey -> cosUtil.getPreviewUrl(fileKey)).collect(Collectors.toList())); List<String> videoIds = page.getRecords().stream().map(WxVideoVO::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()); if (CollectionUtils.isNotEmpty(page.getRecords())) { for (WxVideoVO v : page.getRecords()) { 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())); 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())); } v.setOptions(VideoSupportOpEnum.getVideoOpByStatus(v.getStatus())); if (CollectionUtils.isNotEmpty(v.getGoodsList())) { v.getGoodsList().stream().forEach(goods -> { goods.setThumbnail(cosUtil.getPreviewUrl(goods.getThumbnail())); }); } v.setSubscribeThisAuthor(subscribes.contains(v.getAuthorId())); } vo.setOptions(VideoSupportOpEnum.getVideoOpByStatus(vo.getStatus())); } return Result.ok().data(page.getRecords()).total(page.getTotal()); } @@ -565,14 +696,72 @@ public Result getAuthorCollectVideoPage(AuthorVideoQuery query) { IPage<WxVideoVO> page = PageUtil.getPage(query, WxVideoVO.class); baseMapper.getAuthorCollectVideoPage(page, query); for (WxVideoVO vo : page.getRecords()) { if (VideoContentTypeEnum.VIDEO.getValue().equals(vo.getVideoContentType())) { vo.setVideoUrl(cosUtil.getPreviewUrl(vo.getVideoFileKey())); vo.setCoverUrl(cosUtil.getPreviewUrl(vo.getCoverFileKey())); } else if (VideoContentTypeEnum.IMG.getValue().equals(vo.getVideoContentType()) && StringUtils.isNotBlank(vo.getVideoImgs())) { vo.setImgs(JSON.parseArray(vo.getVideoImgs(), String.class).stream().map(fileKey -> cosUtil.getPreviewUrl(fileKey)).collect(Collectors.toList())); if (CollectionUtils.isNotEmpty(page.getRecords())) { List<String> videoIds = page.getRecords().stream().map(WxVideoVO::getId).collect(Collectors.toList()); Map<String, List<SimpleVideoTagVO>> tagMap = videoTagRefService.getTagsByVideoIds(videoIds) .stream() .collect(Collectors.groupingBy(SimpleVideoTagVO::getVideoId)); Map<String, List<SimpleMyThumbsUpVO>> thumbsUpMap = thumbsUpRecordService.getThumbssByVideoIds(videoIds) .stream() .collect(Collectors.groupingBy(SimpleMyThumbsUpVO::getRefId)); List<String> subscribes = mySubscribeService.getSubscribesByUserId(UserContext.getCurrentUserId()); for (WxVideoVO v : page.getRecords()) { v.setTagList(tagMap.get(v.getId())); v.setCollected(Boolean.TRUE); 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())); 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())); } vo.setCollected(Boolean.TRUE); } return Result.ok().data(page.getRecords()).total(page.getTotal()); } @Override public Result getAuthorLikeVideoPage(AuthorVideoQuery query) { IPage<WxVideoVO> page = PageUtil.getPage(query, WxVideoVO.class); baseMapper.getAuthorLikeVideoPage(page, query); if (CollectionUtils.isNotEmpty(page.getRecords())) { List<String> videoIds = page.getRecords().stream().map(WxVideoVO::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)); List<String> subscribes = mySubscribeService.getSubscribesByUserId(UserContext.getCurrentUserId()); for (WxVideoVO v : page.getRecords()) { v.setTagList(tagMap.get(v.getId())); v.setCollected(CollectionUtils.isNotEmpty(collectMap.get(v.getId()))); v.setThumbsUp(Boolean.TRUE); v.setCommentNum(this.getCommentNum(v.getId(), v.getCommentNum())); v.setCollectNum(this.getCollectNum(v.getId(), v.getCollectNum())); v.setThumbsUpNum(this.getThumbsUpNum(v.getId(), v.getThumbsUpNum())); 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()); } @@ -814,4 +1003,81 @@ .update(); } } @Override public Result recreateEsIndex() { String destination = rocketmqCustomProperties.getVideoTopic() + ":" + VideoTagsEnum.ES_RECREATE.name(); // 消息体不能为空,随便传一个1 rocketMQTemplate.asyncSend(destination, "1", RocketmqSendCallbackBuilder.commonCallback()); return Result.ok("已成功发起构建请求,稍作等待后便会自动完成"); } @Override public Result changeThumbsUp(ThumbsUpRecordForm form) { // mq异步处理 ThumbsUpRecord thumbsUp = new ThumbsUpRecord(); thumbsUp.setRefId(form.getRefId()); thumbsUp.setThumbsUpType(ThumbsUpTypeEnum.VIDEO.getValue()); thumbsUp.setUserId(UserContext.getCurrentUserId()); String destination = rocketmqCustomProperties.getVideoTopic() + ":" + VideoTagsEnum.THUMBS_UP.name(); rocketMQTemplate.asyncSend(destination, JSON.toJSONString(thumbsUp), RocketmqSendCallbackBuilder.commonCallback()); return Result.ok(); } @Override @Transactional(rollbackFor = Exception.class) public void mqChangeThumbsUp(ThumbsUpRecord thumbsUpRecord) { ThumbsUpRecord exists = new LambdaQueryChainWrapper<>(thumbsUpRecordService.getBaseMapper()) .eq(ThumbsUpRecord::getUserId, thumbsUpRecord.getUserId()) .eq(ThumbsUpRecord::getRefId, thumbsUpRecord.getRefId()) .eq(ThumbsUpRecord::getThumbsUpType, thumbsUpRecord.getThumbsUpType()) .one(); boolean add = false; if (Objects.nonNull(exists)) { // 取消点赞 thumbsUpRecordService.removeById(exists.getId()); } else { // 点赞 thumbsUpRecordService.save(thumbsUpRecord); add = true; } // 处理缓存 Video video = baseMapper.selectById(thumbsUpRecord.getRefId()); if (cache.exist(CachePrefix.VIDEO_THUMBS_UP_NUM.getPrefixWithId(thumbsUpRecord.getRefId()))) { if (add) { cache.incr(CachePrefix.VIDEO_THUMBS_UP_NUM.getPrefixWithId(thumbsUpRecord.getRefId())); } else { cache.decr(CachePrefix.VIDEO_THUMBS_UP_NUM.getPrefixWithId(thumbsUpRecord.getRefId())); } } else { if (Objects.nonNull(video)) { cache.put(CachePrefix.VIDEO_THUMBS_UP_NUM.getPrefixWithId(video.getId()), video.getThumbsUpNum() + (add ? 1 : -1), RedisKeyExpireConstant.VIDEO_THUMBS_UP_EXPIRE, RedisKeyExpireConstant.EXPIRE_DAY); } } // 标识该视频需要通过定时任务统计收藏数 if (Objects.nonNull(video) && ! video.getCollectNumJob()) { new LambdaUpdateChainWrapper<>(baseMapper) .eq(Video::getId, video.getId()) .set(Video::getThumbsUpNumJob, Boolean.TRUE) .update(); } } @Override @Transactional(rollbackFor = Exception.class) public void updateThumbsUpNumBatch(List<CollectTypeNumVO> numList) { // 按200条数据进行拆分 List<List<CollectTypeNumVO>> chunks = ListUtils.partition(numList, 200); for (List<CollectTypeNumVO> chunk : chunks) { baseMapper.updateThumbsUpNumBatch(chunk); new LambdaUpdateChainWrapper<>(baseMapper) .in(Video::getId, chunk.stream().map(CollectTypeNumVO::getId).collect(Collectors.toList())) .set(Video::getThumbsUpNumJob, Boolean.FALSE) .update(); } } } framework/src/main/java/cn/lili/modules/search/repository/EsVideoIndexRepository.java
New file @@ -0,0 +1,15 @@ package cn.lili.modules.search.repository; import cn.lili.modules.lmk.domain.es.VideoIndex; import cn.lili.modules.search.entity.dos.EsGoodsIndex; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; /** * 视频索引 * * @author paulG * @since 2020/10/15 **/ public interface EsVideoIndexRepository extends ElasticsearchRepository<VideoIndex, String> { } framework/src/main/java/cn/lili/rocketmq/tags/VideoTagsEnum.java
@@ -12,6 +12,11 @@ * 收藏 */ COLLECT("收藏"), THUMBS_UP("点赞"), ES_RECREATE("重建视频索引"), ES_DOC_ADD_OR_UPDATE("新增或全量修改视频"), ES_DOC_UPDATE_SOME_FIELD("修改视频某些字段"), ES_DOC_DEL("删除视频"), ; framework/src/main/resources/es/video.json
New file @@ -0,0 +1,49 @@ { "properties": { "id": {"type": "keyword"}, "title": { "type": "text", "analyzer": "ik_max_word" }, "authorId": {"type": "keyword"}, "authorName": { "type": "text", "analyzer": "ik_max_word" }, "authorAvatar": {"type": "keyword"}, "recommend": {"type": "boolean"}, "coverFileKey": {"type": "keyword"}, "videoFileKey": {"type": "keyword"}, "videoContentType": {"type": "keyword"}, "videoType": {"type": "keyword"}, "videoImgs": {"type": "keyword"}, "tagList": { "type": "nested", "properties": { "id": {"type": "keyword"}, "videoId": {"type": "keyword"}, "tagName": { "type": "text", "analyzer": "ik_max_word" } } }, "videoDuration": {"type": "keyword"}, "videoFit": {"type": "keyword"}, "status": {"type": "keyword"}, "goodsList": { "type": "nested", "properties": { "id": {"type": "keyword"}, "goodsId": {"type": "keyword"}, "price": {"type": "keyword"}, "thumbnail": {"type": "keyword"}, "goodsNum": {"type": "keyword"}, "goodsName": { "type": "text", "analyzer": "ik_max_word" } } } } } framework/src/main/resources/mapper/lmk/ThumbsUpRecordMapper.xml
@@ -56,4 +56,31 @@ LTUR.ref_id </select> <select id="countNumGroupByVideo" resultType="cn.lili.modules.lmk.domain.vo.CollectTypeNumVO"> SELECT LV.id as id, count(LTUR.ref_id) as countNum FROM lmk_video LV LEFT JOIN lmk_thumbs_up_record LTUR ON LTUR.ref_id = LV.id AND LTUR.thumbs_up_type = 'video' AND LTUR.delete_flag = 0 WHERE LV.thumbs_up_num_job = 1 AND LV.delete_flag = 0 GROUP BY LTUR.ref_id </select> <select id="getThumbssByVideoIds" resultType="cn.lili.modules.lmk.domain.vo.SimpleMyThumbsUpVO"> SELECT id, ref_id as refId FROM lmk_thumbs_up_record WHERE user_id = #{userId} AND delete_flag = 0 AND thumbs_up_type = 'video' AND ref_id IN <foreach collection="videoIds" open="(" item="videoId" close=")" separator=",">#{videoId}</foreach> </select> </mapper> framework/src/main/resources/mapper/lmk/VideoMapper.xml
@@ -17,6 +17,7 @@ <result column="play_num" property="playNum" /> <result column="collect_num" property="collectNum" /> <result column="comment_num" property="commentNum" /> <result column="thumbs_up_num" property="thumbsUpNum" /> <result column="weight" property="weight" /> <result column="audit_pass_time" property="auditPassTime" /> <result column="update_time" property="updateTime" /> @@ -70,6 +71,7 @@ <result column="play_num" property="playNum" /> <result column="collect_num" property="collectNum" /> <result column="comment_num" property="commentNum" /> <result column="thumbs_up_num" property="thumbsUpNum" /> <result column="weight" property="weight" /> <result column="audit_pass_time" property="auditPassTime" /> <result column="update_time" property="updateTime" /> @@ -91,6 +93,7 @@ <result column="title" property="title" /> <result column="collect_num" property="collectNum" /> <result column="comment_num" property="commentNum" /> <result column="thumbs_up_num" property="thumbsUpNum" /> <result column="status" property="status" /> <result column="video_content_type" property="videoContentType" /> <result column="video_type" property="videoType" /> @@ -114,6 +117,7 @@ LV.play_num, LV.collect_num, LV.comment_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -145,6 +149,7 @@ LV.play_num, LV.collect_num, LV.comment_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -176,6 +181,7 @@ LV.play_num, LV.collect_num, LV.comment_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -218,6 +224,7 @@ LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -251,6 +258,7 @@ LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -283,6 +291,7 @@ LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -326,6 +335,20 @@ WHEN #{video.id} THEN #{video.countNum} </foreach> ELSE comment_num END WHERE id IN <foreach collection="list" item="video" open="(" separator="," close=")"> #{video.id} </foreach> </update> <update id="updateThumbsUpNumBatch"> UPDATE lmk_video SET thumbs_up_num = CASE id <foreach collection="list" item="video"> WHEN #{video.id} THEN #{video.countNum} </foreach> ELSE thumbs_up_num END WHERE id IN <foreach collection="list" item="video" open="(" separator="," close=")"> @@ -378,6 +401,7 @@ LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -416,6 +440,7 @@ LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -431,6 +456,41 @@ LEFT JOIN li_member LM ON LV.author_id = LM.id WHERE LMC.delete_flag = 0 AND LMC.user_id = #{query.authorId} AND LMC.collect_type = 'video' AND LV.video_type = #{query.videoType} ORDER BY LMC.create_time DESC </select> <select id="getAuthorLikeVideoPage" resultMap="WxResultMap"> SELECT LV.author_id, LV.cover_url, LV.video_fit, LV.video_duration, LV.video_file_key, LV.title, LV.goods_view_num, LV.goods_order_num, LV.recommend, LV.status, LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, LV.video_content_type, LV.video_type, LV.video_imgs, LV.id, LM.nick_name as authorName, LM.face as authorAvatar FROM lmk_thumbs_up_record LMC INNER JOIN lmk_video LV ON LMC.ref_id = LV.id AND LV.delete_flag = 0 AND LV.status = '1' LEFT JOIN li_member LM ON LV.author_id = LM.id WHERE LMC.delete_flag = 0 AND LMC.user_id = #{query.authorId} AND LMC.thumbs_up_type = 'video' AND LV.video_type = #{query.videoType} ORDER BY LMC.create_time DESC </select> @@ -479,6 +539,7 @@ LV.play_num, LV.collect_num, LV.comment_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -520,6 +581,7 @@ LV.play_num, LV.collect_num, LV.comment_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -545,4 +607,69 @@ <if test="query.authorId != null and query.authorId != ''">AND LV.author_id = #{query.authorId}</if> <if test="query.status != null and query.status != ''">AND LV.status = #{query.status}</if> </select> <resultMap id="EsResultMap" type="cn.lili.modules.lmk.domain.es.VideoIndex"> <id column="id" property="id"/> <result column="author_id" property="authorId" /> <result column="authorName" property="authorName" /> <result column="authorAvatar" property="authorAvatar" /> <result column="cover_url" property="coverFileKey" /> <result column="video_file_key" property="videoFileKey" /> <result column="video_fit" property="videoFit" /> <result column="video_duration" property="videoDuration" /> <result column="title" property="title" /> <result column="status" property="status" /> <result column="recommend" property="recommend" /> <result column="video_content_type" property="videoContentType" /> <result column="video_type" property="videoType" /> <result column="video_imgs" property="videoImgs" /> <collection property="goodsList" column="id" select="getVideoGoods" ofType="cn.lili.modules.lmk.domain.vo.VideoGoodsDetailVO"/> <collection property="tagList" column="id" select="getVideoTags" ofType="cn.lili.modules.lmk.domain.vo.SimpleVideoTagVO"/> </resultMap> <select id="getVideoTags" resultType="cn.lili.modules.lmk.domain.vo.SimpleVideoTagVO"> SELECT LVT.id, LVT.tag_name as tagName FROM lmk_video_tag_ref LVTR INNER JOIN lmk_video_tag LVT ON LVTR.video_tag_id = LVT.id AND LVT.delete_flag = 0 WHERE LVTR.video_id = #{id} </select> <select id="getEsPage" parameterType="int" resultMap="EsResultMap"> SELECT LV.author_id, LV.cover_url, LV.video_fit, LV.video_duration, LV.video_file_key, LV.title, LV.goods_view_num, LV.goods_order_num, LV.recommend, LV.status, LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, LV.create_time, LV.video_content_type, LV.video_type, LV.video_imgs, LV.id, LM.nick_name as authorName, LM.face as authorAvatar FROM lmk_video LV LEFT JOIN li_member LM ON LV.author_id = LM.id WHERE LV.delete_flag = 0 LIMIT #{start}, #{pageSize} </select> </mapper> lmk-job/src/main/java/cn/lili/job/VideoJob.java
@@ -72,4 +72,19 @@ } } /** * 视频点赞数统计 * * @throws Exception */ @XxlJob("videoThumbsUpNumJob") public void videoThumbsUpNumJob() throws Exception { XxlJobHelper.log("开始执行:视频点赞数统计"); List<CollectTypeNumVO> numList = thumbsUpRecordService.countNumGroupByVideo(); if (CollectionUtils.isNotEmpty(numList)) { videoService.updateThumbsUpNumBatch(numList); } } } manager-api/src/main/java/cn/lili/controller/lmk/VideoController.java
@@ -1,5 +1,6 @@ package cn.lili.controller.lmk; import cn.lili.elasticsearch.EsSuffix; import cn.lili.group.Update; import cn.lili.group.Add; import cn.lili.modules.lmk.domain.form.VideoAuditingForm; @@ -7,6 +8,8 @@ import cn.lili.modules.lmk.domain.form.VideoRecommendForm; import cn.lili.modules.lmk.domain.form.WxVideoForm; import cn.lili.modules.lmk.domain.query.ManagerVideoQuery; import cn.lili.modules.lmk.service.EsService; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.validation.annotation.Validated; import lombok.RequiredArgsConstructor; import java.util.List; @@ -32,17 +35,8 @@ private final VideoService videoService; @PostMapping @ApiOperation(value = "添加", notes = "添加") public Result add(@RequestBody @Validated(Add.class) WxVideoForm form) { return videoService.add(form); } @PutMapping @ApiOperation(value = "修改", notes = "修改") public Result update(@RequestBody @Validated(Update.class) WxVideoForm form) { return videoService.update(form); } @Qualifier("videoEsServiceImpl") private final EsService esService; @DeleteMapping("/{id}") @ApiOperation(value = "ID删除", notes = "ID删除") @@ -97,4 +91,10 @@ public Result down(@RequestBody @Validated VideoDownForm form) { return videoService.down(form); } @PostMapping("/recreate/es/index") @ApiOperation(value = "重建es索引", notes = "重建es索引") public Result recreateEsIndex() { return videoService.recreateEsIndex(); } }