buyer-api/src/main/java/cn/lili/controller/lmk/VideoController.java
@@ -4,6 +4,8 @@ import cn.lili.group.Update; import cn.lili.group.Add; import cn.lili.modules.lmk.domain.form.VideoFootPrintForm; import cn.lili.modules.lmk.domain.form.VideoHomePageInfoForm; import cn.lili.modules.lmk.domain.query.AuthorVideoQuery; import cn.lili.modules.member.entity.dos.FootPrint; import org.springframework.validation.annotation.Validated; import org.springframework.security.access.prepost.PreAuthorize; @@ -68,7 +70,7 @@ @GetMapping("/recommend") @ApiOperation(value = "视频推荐", notes = "视频推荐") public Result recommendVideo(AbsQuery query) { public Result recommendVideo(VideoQuery query) { return videoService.recommendVideo(query); } @@ -77,4 +79,28 @@ public Result saveViewRecord(@RequestBody VideoFootPrintForm form) { return videoService.saveViewRecord(form); } @GetMapping("/author-info/{authorId}") @ApiOperation(value = "获取视频主页作者信息", notes = "获取视频主页作者信息") public Result getAuthorInfo(@PathVariable("authorId") String authorId) { return videoService.getAuthorInfo(authorId); } @GetMapping("/author-video-page") @ApiOperation(value = "获取视频主页作者视频分页", notes = "获取视频主页作者视频分页") public Result getAuthorVideoPage(AuthorVideoQuery query) { return videoService.getAuthorVideoPage(query); } @GetMapping("/author-collect-video-page") @ApiOperation(value = "获取视频主页作者收藏的视频分页", notes = "获取视频主页作者收藏的视频分页") public Result getAuthorCollectVideoPage(AuthorVideoQuery query) { return videoService.getAuthorCollectVideoPage(query); } @PostMapping("/home-page-info-edit") @ApiOperation(value = "保存视频主页的个人信息修改", notes = "保存视频主页的个人信息修改") public Result homePageInfoEdit(@RequestBody @Validated VideoHomePageInfoForm form) { return videoService.homePageInfoEdit(form); } } framework/src/main/java/cn/lili/modules/lmk/domain/entity/VideoAccount.java
New file @@ -0,0 +1,30 @@ package cn.lili.modules.lmk.domain.entity; import cn.lili.mybatis.BaseEntity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import lombok.Data; /** * 视频账号信息 * * @author xp * @since 2025-06-03 */ @Data @TableName("lmk_video_account") public class VideoAccount extends BaseEntity { private static final long serialVersionUID = 1L; @TableField("user_id") /** memberId */ private String userId; @TableField("motto") /** 座右铭 */ private String motto; } framework/src/main/java/cn/lili/modules/lmk/domain/form/VideoForm.java
@@ -38,6 +38,10 @@ @ApiModelProperty("标题") private String title; @ApiModelProperty("视频封面") @NotBlank(message = "视频封面不能为空", groups = {Add.class, Update.class}) private String cover; @ApiModelProperty("视频标签") @Length(max = 5, message = "最多只能添加五个标签") private List<WxVideoTagForm> tags = new ArrayList<>(2); framework/src/main/java/cn/lili/modules/lmk/domain/form/VideoHomePageInfoForm.java
New file @@ -0,0 +1,28 @@ package cn.lili.modules.lmk.domain.form; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.NotBlank; /** * @author:xp * @date:2025/6/3 20:09 */ @Data @ApiModel("视频主页个人信息修改") public class VideoHomePageInfoForm { @ApiModelProperty("个性签名") private String motto; @NotBlank(message = "昵称不能为空") @ApiModelProperty("昵称") private String nickName; @NotBlank(message = "头像不能为空") @ApiModelProperty("头像") private String avatar; } framework/src/main/java/cn/lili/modules/lmk/domain/query/AuthorVideoQuery.java
New file @@ -0,0 +1,22 @@ 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 * @since 2025-05-16 */ @Data @ApiModel(value = "Video查询参数", description = "视频内容查询参数") public class AuthorVideoQuery extends AbsQuery { @ApiModelProperty("作者id") private String authorId; } framework/src/main/java/cn/lili/modules/lmk/domain/query/VideoQuery.java
@@ -18,5 +18,12 @@ @Data @ApiModel(value = "Video查询参数", description = "视频内容查询参数") public class VideoQuery extends AbsQuery { @ApiModelProperty("作者id") private String authorId; @ApiModelProperty("视频来源:recommend推荐、author某作者的视频、collect某作者收藏的视频") private String videoFrom; } framework/src/main/java/cn/lili/modules/lmk/domain/vo/VideoAccountVO.java
New file @@ -0,0 +1,60 @@ package cn.lili.modules.lmk.domain.vo; import cn.lili.base.AbsVo; import cn.lili.modules.lmk.domain.entity.VideoAccount; import java.util.List; import org.springframework.lang.NonNull; import org.springframework.beans.BeanUtils; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.util.Date; /** * 视频账号信息展示 * * @author xp * @since 2025-06-03 */ @Data @ApiModel(value = "视频账号信息响应数据", description = "视频账号信息响应数据") public class VideoAccountVO extends AbsVo { /** memberId */ @ApiModelProperty("memberId") private String userId; @ApiModelProperty("昵称") private String nickName; @ApiModelProperty("头像") private String avatar; /** 座右铭 */ @ApiModelProperty("座右铭") private String motto; @ApiModelProperty("粉丝数") private Long fansNum; @ApiModelProperty("关注数") private Long subNum; @ApiModelProperty("获赞数") private Long likeNum; @ApiModelProperty("是否是自己查看自己的主页") private Boolean self; @ApiModelProperty("是否关注了") private Boolean hasSub; public static VideoAccountVO getVoByEntity(@NonNull VideoAccount entity, VideoAccountVO vo) { if(vo == null) { vo = new VideoAccountVO(); } BeanUtils.copyProperties(entity, vo); return vo; } } framework/src/main/java/cn/lili/modules/lmk/domain/vo/WxVideoVO.java
@@ -35,6 +35,7 @@ /** 图片封面 */ @ApiModelProperty("图片封面") private String coverUrl; private String coverFileKey; /** 视频地址 */ @ApiModelProperty("视频地址") framework/src/main/java/cn/lili/modules/lmk/mapper/VideoAccountMapper.java
New file @@ -0,0 +1,21 @@ package cn.lili.modules.lmk.mapper; import cn.lili.modules.lmk.domain.entity.VideoAccount; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import cn.lili.modules.lmk.domain.vo.VideoAccountVO; import java.util.List; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; /** * 视频账号信息 Mapper 接口 * * @author xp * @since 2025-06-03 */ @Mapper public interface VideoAccountMapper extends BaseMapper<VideoAccount> { } framework/src/main/java/cn/lili/modules/lmk/mapper/VideoMapper.java
@@ -1,8 +1,11 @@ package cn.lili.modules.lmk.mapper; import cn.lili.modules.lmk.domain.entity.Video; import cn.lili.modules.lmk.domain.query.AuthorVideoQuery; import cn.lili.modules.lmk.domain.query.ManagerVideoQuery; import cn.lili.modules.lmk.domain.vo.CollectTypeNumVO; import cn.lili.modules.lmk.domain.vo.VideoAccountVO; import cn.lili.modules.lmk.domain.vo.WxVideoVO; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import cn.lili.modules.lmk.domain.vo.VideoVO; @@ -61,4 +64,44 @@ * @param numList */ void updateCommentNumBatch(@Param("list") List<CollectTypeNumVO> numList); /** * 视频主页作者信息 * * @param authorId * @return */ VideoAccountVO getAuthorInfo(@Param("authorId") String authorId, @Param("currentUserId") String currentUserId); /** * 获取作者的所有视频id * * @param authorId * @return */ List<String> getVideoIdsByAuthor(@Param("authorId") String authorId); /** * 获取作者所有视频的收藏数之和 * * @param videoIds * @return */ Long countAuthorVideoCollectNum(@Param("videoIds") List<String> videoIds); /** * 获取视频主页-作者视频的分页 * * @param page * @param query */ IPage getAuthorVideoPage(IPage page, @Param("query") AuthorVideoQuery query); /** * 获取视频主页作者收藏的视频分页 * * @param page * @param query */ IPage getAuthorCollectVideoPage(IPage page, @Param("query") AuthorVideoQuery query); } framework/src/main/java/cn/lili/modules/lmk/service/VideoAccountService.java
New file @@ -0,0 +1,17 @@ package cn.lili.modules.lmk.service; import cn.lili.modules.lmk.domain.entity.VideoAccount; import com.baomidou.mybatisplus.extension.service.IService; import cn.lili.base.Result; import java.util.List; /** * 视频账号信息 服务类 * * @author xp * @since 2025-06-03 */ public interface VideoAccountService extends IService<VideoAccount> { } framework/src/main/java/cn/lili/modules/lmk/service/VideoService.java
@@ -1,8 +1,8 @@ package cn.lili.modules.lmk.service; import cn.lili.base.AbsQuery; import cn.lili.modules.lmk.domain.entity.Video; import cn.lili.modules.lmk.domain.form.*; import cn.lili.modules.lmk.domain.query.AuthorVideoQuery; import cn.lili.modules.lmk.domain.query.ManagerVideoQuery; import cn.lili.modules.lmk.domain.vo.CollectTypeNumVO; import com.baomidou.mybatisplus.extension.service.IService; @@ -118,8 +118,9 @@ * 小程序端的视频推荐接口 * * @return * @param query */ Result recommendVideo(AbsQuery query); Result recommendVideo(VideoQuery query); /** * 批量更新视频收藏数量 @@ -142,4 +143,36 @@ * @return */ Result saveViewRecord(VideoFootPrintForm form); /** * 获取视频主页作者信息 * * @param authorId * @return */ Result getAuthorInfo(String authorId); /** * 获取视频主页作者视频分页 * * @param query * @return */ Result getAuthorVideoPage(AuthorVideoQuery query); /** * 获取视频主页作者收藏视频分页 * * @param query * @return */ Result getAuthorCollectVideoPage(AuthorVideoQuery query); /** * 保存视频主页的个人信息修改 * * @param form * @return */ Result homePageInfoEdit(VideoHomePageInfoForm form); } framework/src/main/java/cn/lili/modules/lmk/service/impl/VideoAccountServiceImpl.java
New file @@ -0,0 +1,22 @@ package cn.lili.modules.lmk.service.impl; import cn.lili.modules.lmk.domain.entity.VideoAccount; import cn.lili.modules.lmk.mapper.VideoAccountMapper; import cn.lili.modules.lmk.service.VideoAccountService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; /** * 视频账号信息 服务实现类 * * @author xp * @since 2025-06-03 */ @Service @RequiredArgsConstructor public class VideoAccountServiceImpl extends ServiceImpl<VideoAccountMapper, VideoAccount> implements VideoAccountService { private final VideoAccountMapper videoAccountMapper; } framework/src/main/java/cn/lili/modules/lmk/service/impl/VideoServiceImpl.java
@@ -1,11 +1,9 @@ package cn.lili.modules.lmk.service.impl; import cn.lili.base.AbsQuery; import cn.lili.common.security.context.UserContext; import cn.lili.modules.lmk.domain.entity.VideoAuditRecord; import cn.lili.modules.lmk.domain.entity.VideoTag; import cn.lili.modules.lmk.domain.entity.VideoTagRef; import cn.lili.modules.lmk.domain.entity.*; import cn.lili.modules.lmk.domain.form.*; import cn.lili.modules.lmk.domain.query.AuthorVideoQuery; import cn.lili.modules.lmk.domain.query.ManagerVideoQuery; import cn.lili.modules.lmk.domain.vo.*; import cn.lili.modules.lmk.enums.general.TagCreateTypeEnum; @@ -13,10 +11,11 @@ import cn.lili.modules.lmk.enums.general.ViewTypeEnum; import cn.lili.modules.lmk.service.*; import cn.lili.modules.member.entity.dos.FootPrint; 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.utils.COSUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import cn.lili.modules.lmk.domain.entity.Video; import cn.lili.modules.lmk.mapper.VideoMapper; import cn.lili.base.Result; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; @@ -55,6 +54,8 @@ private final COSUtil cosUtil; private final FootprintService footprintService; private final MySubscribeService mySubscribeService; private final MemberService memberService; private final VideoAccountService videoAccountService; /** * 添加 @@ -153,6 +154,7 @@ Video video = VideoForm.getEntityByForm(form, null); video.setAuthorId(UserContext.getCurrentUserId()); video.setStatus(VideoStatusEnum.AUDITING.getValue()); video.setCoverUrl(form.getCover()); baseMapper.insert(video); // 2.处理标签 List<VideoTagRef> videoTagRefs = form.getTags().stream().map(tag -> { @@ -196,7 +198,7 @@ // 3. 获取视频临时访问地址、设置视频标签 page.getRecords().forEach(v -> { v.setTagList(tagMap.get(v.getId())); // v.setVideoUrl(cosUtil.getPreviewUrl(v.getVideoFileKey())); v.setCoverUrl(cosUtil.getPreviewUrl(v.getCoverUrl())); }); } return Result.ok().data(page.getRecords()).total(page.getTotal()); @@ -259,10 +261,26 @@ } @Override public Result recommendVideo(AbsQuery query) { public Result recommendVideo(VideoQuery query) { // 推荐算法: 1. 根据用户的收藏视频的标签 2. 根据用户关注的作者的其它视频 3. 根据用户的观看记录(观看时长较长的、重复观看次数较多的) 4. 基于相似用户的观看行为来给该用户推荐 IPage<WxVideoVO> page = PageUtil.getPage(query, WxVideoVO.class); baseMapper.recommendVideo(page); switch (query.getVideoFrom()) { case "recommend": baseMapper.recommendVideo(page); break; case "author": AuthorVideoQuery query1 = new AuthorVideoQuery(); query1.setAuthorId(query.getAuthorId()); baseMapper.getAuthorVideoPage(page, query1); break; case "collect": AuthorVideoQuery query2 = new AuthorVideoQuery(); query2.setAuthorId(query.getAuthorId()); baseMapper.getAuthorVideoPage(page, query2); break; default: break; } if (page.getTotal() > 0) { List<String> videoIds = page.getRecords().stream().map(WxVideoVO::getId).collect(Collectors.toList()); Map<String, List<SimpleVideoTagVO>> tagMap = videoTagRefService.getTagsByVideoIds(videoIds) @@ -278,6 +296,7 @@ v.setTagList(tagMap.get(v.getId())); v.setCollected(CollectionUtils.isNotEmpty(collectMap.get(v.getId()))); v.setVideoUrl(cosUtil.getPreviewUrl(v.getVideoFileKey())); v.setCoverUrl(cosUtil.getPreviewUrl(v.getCoverFileKey())); v.setSubscribeThisAuthor(subscribes.contains(v.getAuthorId())); }); } @@ -315,4 +334,56 @@ footprintService.saveFootprint(footPrint); return Result.ok(); } @Override public Result getAuthorInfo(String authorId) { VideoAccountVO vo = baseMapper.getAuthorInfo(authorId, UserContext.getCurrentUserId()); vo.setSelf(authorId.equals(UserContext.getCurrentUserId())); // 查询获赞数 List<String> videoIds = baseMapper.getVideoIdsByAuthor(authorId); if (CollectionUtils.isNotEmpty(videoIds)) { vo.setLikeNum(baseMapper.countAuthorVideoCollectNum(videoIds)); } else { vo.setLikeNum(0L); } return Result.ok().data(vo); } @Override public Result getAuthorVideoPage(AuthorVideoQuery query) { IPage<WxVideoVO> page = PageUtil.getPage(query, WxVideoVO.class); baseMapper.getAuthorVideoPage(page, query); for (WxVideoVO vo : page.getRecords()) { vo.setCoverUrl(cosUtil.getPreviewUrl(vo.getCoverFileKey())); vo.setVideoUrl(cosUtil.getPreviewUrl(vo.getVideoUrl())); } return Result.ok().data(page.getRecords()).total(page.getTotal()); } @Override public Result getAuthorCollectVideoPage(AuthorVideoQuery query) { IPage<WxVideoVO> page = PageUtil.getPage(query, WxVideoVO.class); baseMapper.getAuthorCollectVideoPage(page, query); for (WxVideoVO vo : page.getRecords()) { vo.setCoverUrl(cosUtil.getPreviewUrl(vo.getCoverFileKey())); vo.setVideoUrl(cosUtil.getPreviewUrl(vo.getVideoUrl())); vo.setCollected(Boolean.TRUE); } return Result.ok().data(page.getRecords()).total(page.getTotal()); } @Override @Transactional(rollbackFor = Exception.class) public Result homePageInfoEdit(VideoHomePageInfoForm form) { new LambdaUpdateChainWrapper<>(memberService.getBaseMapper()) .eq(Member::getId, UserContext.getCurrentUserId()) .set(Member::getNickName, form.getNickName()) .set(Member::getFace, form.getAvatar()) .update(); new LambdaUpdateChainWrapper<>(videoAccountService.getBaseMapper()) .eq(VideoAccount::getUserId, UserContext.getCurrentUserId()) .set(VideoAccount::getMotto, form.getMotto()) .update(); return Result.ok("保存成功"); } } framework/src/main/java/cn/lili/modules/member/serviceimpl/MemberServiceImpl.java
@@ -22,6 +22,8 @@ import cn.lili.modules.connect.entity.Connect; import cn.lili.modules.connect.entity.dto.ConnectAuthUser; import cn.lili.modules.connect.service.ConnectService; import cn.lili.modules.lmk.domain.entity.VideoAccount; import cn.lili.modules.lmk.service.VideoAccountService; import cn.lili.modules.member.aop.annotation.PointLogPoint; import cn.lili.modules.member.entity.dos.Member; import cn.lili.modules.member.entity.dto.*; @@ -47,6 +49,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; @@ -105,6 +108,9 @@ */ @Autowired private Cache cache; @Autowired private VideoAccountService videoAccountService; @Override public Member findByUsername(String userName) { @@ -310,6 +316,11 @@ member.setId(SnowFlake.getIdStr()); //保存会员 this.save(member); // 同时新增一个视频账号 VideoAccount videoAccount = new VideoAccount(); videoAccount.setUserId(member.getId()); videoAccountService.save(videoAccount); UserContext.settingInviter(member.getId(), cache); // 发送会员注册信息 applicationEventPublisher.publishEvent(new TransactionCommitSendMQEvent("new member register", rocketmqCustomProperties.getMemberTopic(), @@ -844,4 +855,4 @@ } } } } framework/src/main/resources/mapper/lmk/VideoAccountMapper.xml
New file @@ -0,0 +1,41 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.lili.modules.lmk.mapper.VideoAccountMapper"> <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="cn.lili.modules.lmk.domain.vo.VideoAccountVO"> <id column="id" property="id"/> <result column="user_id" property="userId" /> <result column="motto" property="motto" /> </resultMap> <select id="getById" resultMap="BaseResultMap"> SELECT LVA.user_id, LVA.motto, LVA.id FROM lmk_video_account LVA WHERE LVA.id = #{id} AND LVA.delete_flag = 0 </select> <select id="getPage" resultMap="BaseResultMap"> SELECT LVA.user_id, LVA.motto, LVA.id FROM lmk_video_account LVA WHERE LVA.delete_flag = 0 </select> </mapper> framework/src/main/resources/mapper/lmk/VideoMapper.xml
@@ -31,7 +31,7 @@ <result column="author_id" property="authorId" /> <result column="authorName" property="authorName" /> <result column="authorAvatar" property="authorAvatar" /> <result column="cover_url" property="coverUrl" /> <result column="cover_url" property="coverFileKey" /> <result column="video_file_key" property="videoFileKey" /> <result column="video_fit" property="videoFit" /> <result column="title" property="title" /> @@ -197,4 +197,97 @@ </foreach> </update> <select id="getAuthorInfo" resultType="cn.lili.modules.lmk.domain.vo.VideoAccountVO"> SELECT LM.id as userId, LM.nick_name as nickName, LM.face as avatar, LVA.motto, (SELECT COUNT(*) FROM lmk_my_subscribe WHERE subscribe_user_id = #{authorId} AND delete_flag = 0) as fansNum, (SELECT COUNT(*) FROM lmk_my_subscribe WHERE user_id = #{authorId} AND delete_flag = 0) as subNum, (SELECT CASE WHEN id IS NOT NULL THEN 1 ELSE 0 END FROM lmk_my_subscribe WHERE user_id = #{currentUserId} AND subscribe_user_id = #{authorId} AND delete_flag = 0) as hasSub FROM li_member LM LEFT JOIN lmk_video_account LVA ON LM.id = LVA.user_id WHERE LM.id = #{authorId} AND LM.delete_flag = 0 </select> <select id="getVideoIdsByAuthor" parameterType="string" resultType="string"> SELECT id FROM lmk_video WHERE author_id = #{authorId} AND delete_flag = 0 AND status = '1' </select> <select id="countAuthorVideoCollectNum" resultType="long"> SELECT COUNT(*) FROM lmk_my_collect WHERE collect_type = 'video' AND delete_flag = 0 AND ref_id IN <foreach collection="videoIds" open="(" close=")" separator="," item="videoId">#{videoId}</foreach> </select> <select id="getAuthorVideoPage" resultMap="WxResultMap"> SELECT LV.author_id, LV.cover_url, LV.video_fit, LV.video_duration, LV.video_file_key, LV.title, LV.goods_id, LV.goods_view_num, LV.goods_order_num, LV.recommend, LV.status, LV.play_num, LV.comment_num, LV.collect_num, LV.weight, LV.audit_pass_time, LV.update_time, 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 AND LV.status = '1' AND LV.author_id = #{query.authorId} ORDER BY LV.collect_num DESC </select> <select id="getAuthorCollectVideoPage" resultMap="WxResultMap"> SELECT LV.author_id, LV.cover_url, LV.video_fit, LV.video_duration, LV.video_file_key, LV.title, LV.goods_id, LV.goods_view_num, LV.goods_order_num, LV.recommend, LV.status, LV.play_num, LV.comment_num, LV.collect_num, LV.weight, LV.audit_pass_time, LV.update_time, LV.id, LM.nick_name as authorName, LM.face as authorAvatar FROM lmk_my_collect 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.collect_type = 'video' ORDER BY LMC.create_time DESC </select> </mapper>