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,16 @@ 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); } @GetMapping("/es/search") @ApiOperation(value = "视频搜索", notes = "视频搜索") public Result esSearch(VideoEsQuery query) { return videoService.esSearch(query); } } buyer-api/src/main/java/cn/lili/controller/order/CartController.java
@@ -74,6 +74,27 @@ throw new ServiceException(ResultCode.CART_ERROR); } } @ApiOperation(value = "向购物车中覆盖添加一个产品") @PostMapping("/addCard") @ApiImplicitParams({ @ApiImplicitParam(name = "skuId", value = "产品ID", required = true, dataType = "Long", paramType = "query"), @ApiImplicitParam(name = "num", value = "此产品的购买数量", required = true, dataType = "int", paramType = "query"), @ApiImplicitParam(name = "cartType", value = "购物车类型,默认加入购物车", paramType = "query") }) public ResultMessage<Object> addCard(@NotNull(message = "产品id不能为空") String skuId, @NotNull(message = "购买数量不能为空") @Min(value = 1, message = "加入购物车数量必须大于0") Integer num) { try { //读取选中的列表 cartService.add(skuId, num, CartTypeEnum.CART.name(), true); return ResultUtil.success(); } catch (ServiceException se) { log.info(se.getMsg(), se); throw se; } catch (Exception e) { log.error(ResultCode.CART_ERROR.message(), e); throw new ServiceException(ResultCode.CART_ERROR); } } @ApiOperation(value = "获取购物车页面购物车详情") 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,110 @@ 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.Date; 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; /** 视频收藏数 */ @Field(type = FieldType.Integer) private Integer collectNum; /** 视频点赞数 */ @Field(type = FieldType.Integer) private Integer thumbsUpNum; /** 视频评论数 */ @Field(type = FieldType.Integer) private Integer commentNum; /** 视频发布时间(审核通过时间) */ @Field(type = FieldType.Date) private Date publishTime; } framework/src/main/java/cn/lili/modules/lmk/domain/form/HealthVideoForm.java
@@ -42,13 +42,17 @@ /** 视频长度(秒) */ // @NotNull(message = "视频长度不能为空", groups = {Add.class, Update.class}) @NotNull(message = "视频长度不能为空", groups = {Add.class, Update.class}) private Long videoDuration; /** 视频标题 */ @NotBlank(message = "标题不能为空", groups = {Add.class, Update.class}) private String title; @NotBlank(message = "视频填充模式不能为空",groups = {Add.class, Update.class}) /** 视频填充模式 */ private String videoFit; } framework/src/main/java/cn/lili/modules/lmk/domain/form/KitchenVideoForm.java
@@ -10,6 +10,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.util.List; @@ -41,7 +42,7 @@ /** 视频长度(秒) */ // @NotNull(message = "视频长度不能为空", groups = {Add.class, Update.class}) @NotNull(message = "视频长度不能为空", groups = {Add.class, Update.class}) private Long videoDuration; /** 视频标题 */ @@ -52,6 +53,9 @@ @Size(min = 1,max = 5, message = "标签列表不能为空",groups = {Add.class, Update.class}) private List<String> checkKitchenType; @NotBlank(message = "视频填充模式不能为空",groups = {Add.class, Update.class}) /** 视频填充模式 */ private String videoFit; framework/src/main/java/cn/lili/modules/lmk/domain/query/VideoEsQuery.java
New file @@ -0,0 +1,24 @@ package cn.lili.modules.lmk.domain.query; import cn.lili.base.AbsQuery; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @author:xp * @date:2025/7/2 10:08 */ @Data @ApiModel("视频搜索") public class VideoEsQuery { @ApiModelProperty("关键词") private String keyword; @ApiModelProperty(value = "当前页", required = true) private int pageNumber = 0; @ApiModelProperty(value = "每页条数", required = true) private int pageSize = 10; } 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; /** @@ -32,5 +32,8 @@ */ @ApiModelProperty("视频类型:视频、大健康、神厨,默认不传查视频") private String videoType = VideoTypeEnum.VIDEO.getValue(); @ApiModelProperty("搜索视频的关键词") private String keyword; } 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 @@ -104,6 +112,14 @@ * @param query */ IPage getAuthorCollectVideoPage(IPage page, @Param("query") AuthorVideoQuery query); /** * 获取视频主页作者点赞的视频分页 * * @param page * @param query */ IPage getAuthorLikeVideoPage(IPage page, @Param("query") AuthorVideoQuery query); /** * 小程序-视频详情 @@ -147,4 +163,13 @@ VideoFootInfoVo getVideoFootInfo(String id); List<VideoFootVO> videoFoot(String id); /** * 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,41 @@ * @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); /** * 视频搜索-es * * @param query * @return */ Result esSearch(VideoEsQuery query); } 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,10 @@ 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 cn.lili.utils.COSUtil; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -26,6 +34,18 @@ 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; @@ -62,33 +82,14 @@ 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; private final ElasticsearchOperations restTemplate; /** * 修改 * @param form * @return */ @Override public Result update(WxVideoForm form) { Video entity = baseMapper.selectById(form.getId()); @Qualifier("videoEsServiceImpl") private final EsService esService; // 为空抛IllegalArgumentException,做全局异常处理 Assert.notNull(entity, "记录不存在"); BeanUtils.copyProperties(form, entity); baseMapper.updateById(entity); return Result.ok("修改成功"); } /** * 批量删除 @@ -107,8 +108,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("删除成功"); } @@ -140,6 +151,11 @@ } 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(vo.getGoodsList())) { vo.getGoodsList().stream().forEach(goods -> { goods.setThumbnail(cosUtil.getPreviewUrl(goods.getThumbnail())); }); } return Result.ok().data(vo); } @@ -165,11 +181,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()); @@ -189,14 +207,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. 处理选择的商品 if (CollectionUtils.isNotEmpty(form.getGoodsList())) { List<VideoGoods> videoGoods = new ArrayList<>(2); if (CollectionUtils.isNotEmpty(form.getGoodsList())) { for (int i = 0; i < form.getGoodsList().size(); i++) { VideoGoods e = new VideoGoods(); e.setVideoId(video.getId()); @@ -204,10 +227,25 @@ 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.setAuthorName(UserContext.getCurrentUser().getNickName()); videoIndex.setAuthorAvatar(UserContext.getCurrentUser().getFace()); 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("发布成功,视频审核中~"); } @@ -231,6 +269,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()); @@ -250,6 +289,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); @@ -259,8 +303,8 @@ new LambdaUpdateChainWrapper<>(videoGoodsService.getBaseMapper()) .eq(VideoGoods::getVideoId, video.getId()) .remove(); if (CollectionUtils.isNotEmpty(form.getGoodsList())) { List<VideoGoods> videoGoods = new ArrayList<>(2); if (CollectionUtils.isNotEmpty(form.getGoodsList())) { for (int i = 0; i < form.getGoodsList().size(); i++) { VideoGoods e = new VideoGoods(); e.setVideoId(video.getId()); @@ -272,6 +316,21 @@ } videoGoodsService.saveBatch(videoGoods); } // 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(); 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("发布成功,视频审核中~"); } @@ -300,6 +359,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("设置成功"); } @@ -319,34 +387,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("下架成功"); } @@ -356,6 +454,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("下架成功"); } @@ -364,19 +470,33 @@ // 推荐算法: 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": // 加载视频主页点赞视频 AuthorVideoQuery query3 = new AuthorVideoQuery(); BeanUtils.copyProperties(query, query3); query3.setAuthorId(query.getAuthorId()); baseMapper.getAuthorLikeVideoPage(page, query3); break; case "search": // 加载es搜索视频 VideoEsQuery query4 = new VideoEsQuery(); BeanUtils.copyProperties(query, query4); query4.setPageNumber((int) query.getPageNumber()); query4.setPageSize((int) query.getPageSize()); return this.esSearch(query4); default: break; } @@ -388,20 +508,29 @@ 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())); } 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())); }); } @@ -437,6 +566,23 @@ if (Objects.isNull(redisNum)) { // redis中没有就把数据库的写到redis中 cache.put(CachePrefix.VIDEO_COLLECT_NUM.getPrefixWithId(videoId), mysqlNum, RedisKeyExpireConstant.COLLECT_NUM_EXPIRE, RedisKeyExpireConstant.EXPIRE_DAY); return mysqlNum; } 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; @@ -482,28 +628,40 @@ @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) .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); } } } @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) .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); } } } @@ -538,16 +696,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())); } vo.setOptions(VideoSupportOpEnum.getVideoOpByStatus(vo.getStatus())); 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())); } } return Result.ok().data(page.getRecords()).total(page.getTotal()); } @@ -556,14 +737,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())); } vo.setCollected(Boolean.TRUE); 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()); } @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()); } @@ -571,6 +810,9 @@ @Override public Result getGoodsDetail(String videoId) { List<VideoGoodsDetailVO> goodsList = baseMapper.getVideoGoods(videoId); goodsList.stream().forEach(goods -> { goods.setThumbnail(cosUtil.getPreviewUrl(goods.getThumbnail())); }); return Result.ok().data(goodsList); } @@ -621,7 +863,7 @@ video.setAuthorId(UserContext.getCurrentUserId()); video.setVideoType(VideoTypeEnum.HEALTH.getValue()); //设置填充模式 保持比例,完整显示 video.setVideoFit("contain"); video.setVideoFit(form.getVideoFit()); video.setVideoContentType(VideoContentTypeEnum.VIDEO.getValue()); video.setStatus(VideoStatusEnum.PUBLISHED.getValue()); baseMapper.insert(video); @@ -678,7 +920,7 @@ video.setAuthorId(UserContext.getCurrentUserId()); video.setVideoType(VideoTypeEnum.COOK.getValue()); //设置填充模式 保持比例,完整显示 video.setVideoFit("contain"); video.setVideoFit(form.getVideoFit()); video.setVideoContentType(VideoContentTypeEnum.VIDEO.getValue()); video.setStatus(VideoStatusEnum.PUBLISHED.getValue()); baseMapper.insert(video); @@ -802,4 +1044,170 @@ .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(); // 更新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()))); 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()); } } 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,53 @@ { "properties": { "id": {"type": "keyword"}, "title": { "type": "text", "analyzer": "ik_max_word" }, "authorId": {"type": "keyword"}, "authorName": { "type": "text", "analyzer": "ik_max_word" }, "authorAvatar": {"type": "keyword"}, "publishTime": {"type": "date"}, "recommend": {"type": "boolean"}, "collectNum": {"type": "integer"}, "thumbsUpNum": {"type": "integer"}, "commentNum": {"type": "integer"}, "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" /> @@ -71,6 +72,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" /> @@ -92,6 +94,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" /> @@ -115,6 +118,7 @@ LV.play_num, LV.collect_num, LV.comment_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -146,6 +150,7 @@ LV.play_num, LV.collect_num, LV.comment_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -177,6 +182,7 @@ LV.play_num, LV.collect_num, LV.comment_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -219,6 +225,7 @@ LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -252,6 +259,7 @@ LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -284,6 +292,7 @@ LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -327,6 +336,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=")"> @@ -379,6 +402,7 @@ LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -417,6 +441,7 @@ LV.play_num, LV.comment_num, LV.collect_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -432,6 +457,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> @@ -480,6 +540,7 @@ LV.play_num, LV.collect_num, LV.comment_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -521,6 +582,7 @@ LV.play_num, LV.collect_num, LV.comment_num, LV.thumbs_up_num, LV.weight, LV.audit_pass_time, LV.update_time, @@ -651,4 +713,84 @@ LFP.member_id </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="audit_pass_time" property="publishTime" /> <result column="video_content_type" property="videoContentType" /> <result column="video_type" property="videoType" /> <result column="video_imgs" property="videoImgs" /> <result column="collect_num" property="collectNum" /> <result column="thumbs_up_num" property="thumbsUpNum" /> <result column="comment_num" property="commentNum" /> <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, CASE WHEN LV.audit_pass_time IS NOT NULL THEN LV.audit_pass_time ELSE LV.create_time END as audit_pass_time, LV.update_time, LV.create_time, LV.video_content_type, LV.video_type, LV.video_imgs, LV.id, CASE WHEN LM.nick_name IS NOT NULL THEN LM.nick_name WHEN LM.nick_name IS NULL THEN (SELECT nick_name FROM li_admin_user WHERE id = LV.author_id) ELSE '' END as authorName, CASE WHEN LM.face IS NOT NULL THEN LM.face WHEN LM.face IS NULL THEN (SELECT avatar FROM li_admin_user WHERE id = LV.author_id) ELSE '' END 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(); } }