[fix]修改vlog不登陆获取热点数据

This commit is contained in:
wangqx 2025-08-29 11:08:45 +08:00
parent 832689d040
commit 5586e8cee3
18 changed files with 446 additions and 446 deletions

View File

@ -54,11 +54,6 @@ public class AppVlogController {
@PostMapping("/indexList")
public R<Page<IndexVlogVO>> indexList(@RequestBody IndexListBO bo, @RequestBody Page page) {
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) {
throw new ServiceException("用户未登录");
}
bo.setUserId(String.valueOf(loginUser.getUserId()));
Page<IndexVlogVO> pages = pullService.page(page);
return R.ok(pages);
}
@ -176,13 +171,13 @@ public class AppVlogController {
vlogService.userUnLikeVlog(userId, vlogId);
return R.ok();
}
@GetMapping("/read/{vlogId}")
public R<Void> read(@PathVariable String vlogId) {
@GetMapping("/read")
public R<Void> read(@RequestBody VlogBO vlogBO) {
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) {
throw new ServiceException("用户未登录");
}
vlogService.readVlog(loginUser.getUserId(), vlogId);
vlogService.readVlog(loginUser.getUserId(), vlogBO.getId());
return R.ok();
}

View File

@ -47,4 +47,10 @@ public interface GlobalConstants {
*/
String GLOBAL_OFFSET = GLOBAL_REDIS_KEY + "offset:";
/**
* 热点视频 redis key
*/
String HOT_VLOG_TAG = "HOT";
}

View File

@ -11,53 +11,27 @@ import org.springframework.context.ApplicationEvent;
@Getter
public class VlogPushEvent extends ApplicationEvent {
private final Long userId;
private final String tag;
private final String userIdStr;
/**
* 使用 Long 类型用户 ID 创建消息事件
*/
public VlogPushEvent(Object source, Long userId) {
public VlogPushEvent(Object source, String tag) {
super(source);
this.userId = userId;
this.userIdStr = userId != null ? String.valueOf(userId) : null;
this.tag = tag;
this.userIdStr = tag;
}
/**
* 使用 Long 类型用户 ID 创建消息事件
*/
public VlogPushEvent(Long userId) {
super(null);
this.userId = userId;
public VlogPushEvent(String tag) {
super(tag);
this.tag = tag;
this.userIdStr = null;
}
/**
* 使用 String 类型用户 ID 创建消息事件
*/
public VlogPushEvent(Object source, String userIdStr) {
super(source);
this.userIdStr = userIdStr;
// 尝试转换为 Long 类型
Long parsedUserId = null;
try {
if (userIdStr != null) {
parsedUserId = Long.parseLong(userIdStr);
}
} catch (NumberFormatException e) {
// 无法解析为 Long 类型保持 userId null
}
this.userId = parsedUserId;
}
/**
* 创建 MessageEvent 实例支持 String 类型用户ID
*
* @param source 事件源
* @param userIdStr 用户ID (String类型)
* @return MessageEvent 实例
*/
public static VlogPushEvent createWithStringUserId(Object source, String userIdStr) {
return new VlogPushEvent(source, userIdStr);
}
}

View File

@ -65,11 +65,16 @@ public class LoginHelper {
*/
@SuppressWarnings("unchecked cast")
public static <T extends LoginUser> T getLoginUser() {
SaSession session = StpUtil.getTokenSession();
if (ObjectUtil.isNull(session)) {
try {
SaSession session = StpUtil.getTokenSession();
if (ObjectUtil.isNull(session)) {
return null;
}
return (T) session.get(LOGIN_USER_KEY);
}catch (Exception e){
return null;
}
return (T) session.get(LOGIN_USER_KEY);
}
/**

View File

@ -3,7 +3,10 @@ package com.wzj.soopin.content.controller;
import com.wzj.soopin.content.domain.base.BaseInfoProperties;
import com.wzj.soopin.content.domain.bo.VlogBO;
import com.wzj.soopin.content.domain.bo.*;
import com.wzj.soopin.content.domain.po.Article;
import com.wzj.soopin.content.domain.vo.ArticleVO;
import com.wzj.soopin.content.domain.vo.IndexVlogVO;
import com.wzj.soopin.content.enums.YesOrNo;
import com.wzj.soopin.content.service.IVlogPushService;
import org.dromara.common.core.domain.R;
@ -21,9 +24,6 @@ import com.wzj.soopin.content.utils.RedisOperator;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.tags.Tag;
import com.wzj.soopin.content.domain.bo.IndexListBO;
import com.wzj.soopin.content.domain.bo.MyListBO;
import com.wzj.soopin.content.domain.bo.SimpleListBO;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.*;
@ -48,7 +48,12 @@ public class VlogController extends BaseInfoProperties {
public RedisOperator redis;
@Autowired
private IVlogPushService vlogPushService;
@Tag(name = "查询列表")
@PostMapping("/page")
public R<IPage<IndexVlogVO>> page(@RequestBody VlogBO bo, @RequestBody Page page) {
Page<IndexVlogVO> pages = vlogService.getIndexVlogList(bo ,page);
return R.ok(pages);
}
@PostMapping("/vodCallBack")
public R<Void> vodCallBack(@RequestBody Map<String, Object> callbackData) {
try {
@ -189,21 +194,13 @@ public class VlogController extends BaseInfoProperties {
return R.ok();
}
@PostMapping("/totalLikedCounts")
public R<Integer> totalLikedCounts(@RequestParam String vlogId) {
return R.ok(vlogService.getVlogBeLikedCounts(vlogId));
}
@GetMapping("/push/hot")
public R pushHot(@RequestParam int count) {
return R.ok(vlogPushService.cacheTopLikedVlogs(count));
}
@GetMapping("/push/member")
public R pushMember(@RequestParam int count,@RequestParam Long memberId) {
return R.ok(vlogPushService.pushVlogToMember(count,memberId));
@GetMapping("/push")
public R pushMember(@RequestParam int count,@RequestParam String tag) {
return R.ok(vlogPushService.pushVlogToMq(count,tag));
}
}

View File

@ -43,217 +43,6 @@ public class VlogUploadController extends BaseInfoProperties {
@Autowired
private IFansService fansService;
@ApiOperation("获取腾讯云点播视频列表")
@PostMapping("/list")
public R<IPage<Map<String, Object>>> getVodList(@RequestBody VlogBO vlogBO) {
try {
// 验证分页参数
if (vlogBO.getCurrent() < 1) {
vlogBO.setCurrent(1L);
}
if (vlogBO.getSize() < 1 || vlogBO.getSize() > 50) {
vlogBO.setSize(10L);
}
// 如果有模糊查询条件时间区间条件或者有排序条件则只查询数据库
if (StringUtils.isNotBlank(vlogBO.getMobile())
|| StringUtils.isNotBlank(vlogBO.getNickname())
|| StringUtils.isNotBlank(vlogBO.getTitleQuery())
|| vlogBO.getStartTime() != null
|| vlogBO.getEndTime() != null
|| vlogBO.getStatus() != null
|| StringUtils.isNotBlank(vlogBO.getColumn())) {
// 创建分页对象
Page<Map<String, Object>> page = new Page<>(vlogBO.getCurrent(), vlogBO.getSize());
// 调用服务层方法进行查询
IPage<Map<String, Object>> result = vlogService.getVlogListByMobile(page, vlogBO);
return R.ok(result);
}
// 否则查询腾讯云点播
// 验证密钥配置
String secretId = tencentCloudUtil.getSecretId();
String secretKey = tencentCloudUtil.getSecretKey();
if (secretId == null || secretId.isEmpty() || secretKey == null || secretKey.isEmpty()) {
log.error("腾讯云密钥未配置");
return R.fail("腾讯云密钥未配置");
}
// 计算实际的offset
long offset = (long) ((vlogBO.getCurrent() - 1) * vlogBO.getSize());
if (offset < 0) {
offset = 0;
}
// 打印分页参数
log.info("分页参数 - 当前页码: {}, 每页记录数: {}, 偏移量: {}", vlogBO.getCurrent(), vlogBO.getSize(), offset);
// 创建请求对象
SearchMediaRequest req = new SearchMediaRequest();
// 设置分页参数
req.setOffset(offset);
req.setLimit(vlogBO.getSize());
// 设置排序
SortBy sort = new SortBy();
sort.setField("CreateTime");
sort.setOrder("Desc");
req.setSort(sort);
// 调用腾讯云API获取视频列表
log.info("开始调用腾讯云 SearchMedia API");
SearchMediaResponse resp = vlogUploadService.searchMedia(req);
log.info("腾讯云 SearchMedia API 调用成功,总记录数: {}", resp.getTotalCount());
// 处理响应结果
Map<String, Object> result = new HashMap<>();
result.put("total", resp.getTotalCount());
result.put("current", vlogBO.getCurrent());
result.put("size", vlogBO.getSize());
result.put("pages", (resp.getTotalCount() + vlogBO.getSize() - 1) / vlogBO.getSize()); // 总页数
// 批量获取所有视频的统计信息避免重复查询
Map<String, String> fileIdToVlogIdMap = new HashMap<>();
List<String> allVlogIds = new ArrayList<>();
if (resp.getMediaInfoSet() != null) {
for (MediaInfo mediaInfo : resp.getMediaInfoSet()) {
String fileId = mediaInfo.getFileId();
Map<String, Object> statistics = vlogService.getVlogStatistics(fileId);
String vlogId = (String) statistics.get("vlogId");
if (StringUtils.isNotBlank(vlogId)) {
fileIdToVlogIdMap.put(fileId, vlogId);
allVlogIds.add(vlogId);
}
}
}
// 批量获取所有点赞数据
Map<String, Integer> vlogLikeCounts = new HashMap<>();
Map<String, Integer> vlogCommentCounts = new HashMap<>();
long totalLikeCounts = 0;
long totalCommentCounts = 0;
if (!allVlogIds.isEmpty()) {
// 批量获取所有点赞键
String likePattern = REDIS_USER_LIKE_VLOG + ":*";
Collection<String> allLikeKeys = RedisUtils.keys(likePattern);
if (allLikeKeys != null && !allLikeKeys.isEmpty()) {
// 逐个获取点赞值由于RedisUtils没有批量方法
Map<String, String> likeKeyValueMap = new HashMap<>();
for (String likeKey : allLikeKeys) {
String likeValue = RedisUtils.getCacheObject(likeKey);
if ("1".equals(likeValue)) {
likeKeyValueMap.put(likeKey, likeValue);
}
}
// 按vlogId分组统计点赞数
for (String vlogId : allVlogIds) {
int likeCount = 0;
for (String likeKey : likeKeyValueMap.keySet()) {
if (likeKey.endsWith(":" + vlogId)) {
likeCount++;
}
}
vlogLikeCounts.put(vlogId, likeCount);
totalLikeCounts += likeCount;
}
}
// 批量获取所有评论数
for (String vlogId : allVlogIds) {
String commentKey = REDIS_VLOG_COMMENT_COUNTS + ":" + vlogId;
String commentCountsStr = RedisUtils.getCacheObject(commentKey);
int commentCount = 0;
if (StringUtils.isNotBlank(commentCountsStr)) {
try {
commentCount = Integer.valueOf(commentCountsStr);
} catch (NumberFormatException e) {
log.warn("Redis中视频{}的评论数格式错误: {}", vlogId, commentCountsStr);
}
}
vlogCommentCounts.put(vlogId, commentCount);
totalCommentCounts += commentCount;
}
}
// 将总的点赞数和评论数添加到结果中
result.put("totalLikeCounts", totalLikeCounts);
result.put("totalCommentCounts", totalCommentCounts);
List<Map<String, Object>> mediaList = new ArrayList<>();
if (resp.getMediaInfoSet() != null) {
for (MediaInfo mediaInfo : resp.getMediaInfoSet()) {
Map<String, Object> mediaMap = new HashMap<>();
// 基础信息
MediaBasicInfo basicInfo = mediaInfo.getBasicInfo();
mediaMap.put("fileId", mediaInfo.getFileId());
mediaMap.put("name", basicInfo.getName());
mediaMap.put("description", basicInfo.getDescription());
mediaMap.put("createTime", basicInfo.getCreateTime());
mediaMap.put("updateTime", basicInfo.getUpdateTime());
mediaMap.put("expireTime", basicInfo.getExpireTime());
mediaMap.put("classId", basicInfo.getClassId());
mediaMap.put("className", basicInfo.getClassName());
mediaMap.put("classPath", basicInfo.getClassPath());
mediaMap.put("coverUrl", basicInfo.getCoverUrl());
mediaMap.put("type", basicInfo.getType());
mediaMap.put("mediaUrl", basicInfo.getMediaUrl());
mediaMap.put("status", basicInfo.getStatus());
mediaMap.put("storageRegion", basicInfo.getStorageRegion());
mediaMap.put("category", basicInfo.getCategory());
mediaMap.put("storageClass", basicInfo.getStorageClass());
mediaMap.put("tagSet", basicInfo.getTagSet());
// 获取视频统计信息
Map<String, Object> statistics = vlogService.getVlogStatistics(mediaInfo.getFileId());
mediaMap.putAll(statistics);
// 从statistics中获取vlogId用于Redis查询
String vlogId = (String) statistics.get("vlogId");
log.info("处理视频 fileId: {}, vlogId: {}", mediaInfo.getFileId(), vlogId);
// 使用批量获取的数据避免重复查询Redis
int videoLikeCounts = 0;
int videoCommentCounts = 0;
if (StringUtils.isNotBlank(vlogId)) {
videoLikeCounts = vlogLikeCounts.getOrDefault(vlogId, 0);
videoCommentCounts = vlogCommentCounts.getOrDefault(vlogId, 0);
}
mediaMap.put("likeCounts", videoLikeCounts);
mediaMap.put("commentCounts", videoCommentCounts);
// 获取视频上传者信息
Map<String, String> uploaderInfo = vlogService.getVlogUploaderInfo(mediaInfo.getFileId());
if (uploaderInfo != null) {
mediaMap.put("nickname", uploaderInfo.get("name"));
mediaMap.put("mobile", uploaderInfo.get("phone"));
}
mediaList.add(mediaMap);
}
}
result.put("records", mediaList);
// 构建Page对象
Page<Map<String, Object>> page = new Page<>();
page.setCurrent(vlogBO.getCurrent());
page.setSize(vlogBO.getSize());
page.setTotal(resp.getTotalCount());
page.setRecords(mediaList);
return R.ok(page);
} catch (Exception e) {
log.error("获取视频列表失败", e);
return R.fail("获取视频列表失败: " + e.getMessage());
}
}
/**
* 视频审核接口
*/

View File

@ -12,6 +12,7 @@ import org.apache.commons.lang3.StringUtils;
import org.dromara.common.core.domain.BaseBO;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Data
@AllArgsConstructor
@ -64,6 +65,12 @@ public class VlogBO extends BaseBO {
@ApiModelProperty("创建时间范围-结束时间")
private LocalDateTime endTime;
private List<String> blockVd;
private List<String> blockUser;
private List<String> ids;
@Override
public LambdaQueryWrapper<Vlog> toWrapper() {
LambdaQueryWrapper<Vlog> wrapper = new LambdaQueryWrapper<>();

View File

@ -14,7 +14,7 @@ public class VlogPushEventListener {
private final IVlogPushService vlogPushService;
@EventListener
public void handleNeedPushVlogEvent(VlogPushEvent event) {
log.info("收到需要推送的视频事件用户ID{}", event.getUserId());
vlogPushService.pushVlogToMember(100, event.getUserId());
log.info("收到需要推送的视频事件用户ID{}", event.getUserIdStr());
vlogPushService.pushVlogToMq(100, event.getTag());
}
}

View File

@ -2,6 +2,7 @@ package com.wzj.soopin.content.mapper;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wzj.soopin.content.domain.bo.VlogBO;
import com.wzj.soopin.content.domain.po.Vlog;
import com.wzj.soopin.content.domain.vo.IndexVlogVO;
import com.wzj.soopin.content.domain.vo.VlogerVO;
@ -16,9 +17,9 @@ import java.util.Map;
@InterceptorIgnore(tenantLine = "true")
public interface VlogMapperCustom extends BaseMapperPlus<Vlog, VlogerVO> {
List<IndexVlogVO> getIndexVlogList(@Param("paramMap") Map<String, Object> map);
List<IndexVlogVO> getIndexVlogList(@Param("bo") VlogBO bo);
Page<IndexVlogVO> getIndexVlogList(@Param("paramMap") Map<String, Object> map, Page<IndexVlogVO> page);
Page<IndexVlogVO> getIndexVlogList(@Param("bo") VlogBO bo, Page<IndexVlogVO> page);
List<IndexVlogVO> getVlogDetailById(@Param("paramMap") Map<String, Object> map);

View File

@ -1,6 +1,9 @@
package com.wzj.soopin.content.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tencentcloudapi.vod.v20180717.models.SearchMediaRequest;
import com.tencentcloudapi.vod.v20180717.models.SearchMediaResponse;
import com.wzj.soopin.content.domain.bo.VlogBO;
import com.wzj.soopin.content.domain.vo.IndexVlogVO;
public interface IVlogPullService {
@ -9,4 +12,10 @@ public interface IVlogPullService {
* 从视频库中拉取视频
*/
Page<IndexVlogVO> page(Page<IndexVlogVO> page);
void pullVlog(VlogBO vlogBO);
}

View File

@ -8,5 +8,5 @@ package com.wzj.soopin.content.service;
public interface IVlogPushService {
boolean cacheTopLikedVlogs(int limit) ;
boolean pushVlogToMember(int count,Long memberId);
boolean pushVlogToMq(int count,String tag);
}

View File

@ -139,7 +139,7 @@ public interface VlogService extends IService<Vlog> {
IPage<String> getVlogForUser(Page<IndexVlogVO> page, Long memberId);
int readVlog(Long memberId, String vlogId);
List<IndexVlogVO> getIndexVlogList(Map<String, Object> paramMap);
Page<IndexVlogVO> getIndexVlogList(Map<String, Object> paramMap,Page page);
List<IndexVlogVO> getIndexVlogList(VlogBO bo);
Page<IndexVlogVO> getIndexVlogList(VlogBO bo,Page page);
}

View File

@ -26,13 +26,6 @@ public interface VlogUploadService {
// */
// Map<String, Object> getTaskStatus(String taskId) throws Exception;
/**
* 搜索媒体文件
*
* @param req 搜索请求
* @return 搜索响应
*/
SearchMediaResponse searchMedia(SearchMediaRequest req);
// 新增视频审核
void auditVlog(String vlogId, Integer status, String reason);

View File

@ -1,28 +1,38 @@
package com.wzj.soopin.content.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.vod.v20180717.VodClient;
import com.tencentcloudapi.vod.v20180717.models.*;
import com.wzj.soopin.content.domain.bo.VlogBO;
import com.wzj.soopin.content.service.IVlogPullService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wzj.soopin.content.domain.vo.IndexVlogVO;
import com.wzj.soopin.content.service.VlogService;
import com.wzj.soopin.content.utils.TencentCloudUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.message.MessageExt;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mq.domain.MQMessage;
import org.dromara.common.mq.domain.VlogPushEvent;
import org.dromara.common.mq.utils.UserRocketMQConsumerManager;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
import static com.wzj.soopin.content.domain.base.BaseInfoProperties.REDIS_USER_LIKE_VLOG;
import static com.wzj.soopin.content.domain.base.BaseInfoProperties.REDIS_VLOG_COMMENT_COUNTS;
import static org.dromara.common.core.constant.GlobalConstants.HOT_VLOG_TAG;
@Service
@AllArgsConstructor
@Slf4j
@ -31,6 +41,7 @@ public class VlogPullServiceImpl implements IVlogPullService {
private final UserRocketMQConsumerManager mqConsumerManager;
private final VlogService vlogService;
private final ApplicationEventPublisher eventPublisher;
private final TencentCloudUtil tencentCloudUtil;
@Override
public Page<IndexVlogVO> page(Page<IndexVlogVO> page) {
@ -40,7 +51,7 @@ public class VlogPullServiceImpl implements IVlogPullService {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
if (loginUser == null) {
messageExts = mqConsumerManager.pullUserMessages("hot", (int) page.getSize());
messageExts = mqConsumerManager.pullUserMessages(HOT_VLOG_TAG, (int) page.getSize());
} else {
messageExts = mqConsumerManager.pullUserMessages(loginUser.getUserId() + "", (int) page.getSize());
@ -49,18 +60,22 @@ public class VlogPullServiceImpl implements IVlogPullService {
MQMessage mqMessage = JsonUtils.parseObject(messageExt.getBody(), MQMessage.class);
return mqMessage.getData().toString();
}).collect(Collectors.toList());
Map<String, Object> paramMap = new HashMap<>();
VlogBO vlogBO = new VlogBO();
if (ids.size() > 0) {
paramMap.put("ids", ids);
List<IndexVlogVO> vlogVOList = vlogService.getIndexVlogList(paramMap);
vlogBO.setIds(ids);
List<IndexVlogVO> vlogVOList = vlogService.getIndexVlogList(vlogBO);
return page.setRecords(vlogVOList);
} else {
if (loginUser != null) {
eventPublisher.publishEvent(new VlogPushEvent(loginUser.getUserId() + ""));
} else {
eventPublisher.publishEvent(new VlogPushEvent(HOT_VLOG_TAG));
}
//发出事件
eventPublisher.publishEvent(new VlogPushEvent(loginUser.getUserId()));
//先临时取10条数据
Page<IndexVlogVO> indexVlogVOPage = vlogService.getIndexVlogList(paramMap, page);
Page<IndexVlogVO> indexVlogVOPage = vlogService.getIndexVlogList(vlogBO, page);
//直接获取数据库数据并要求push类推送数据
return indexVlogVOPage;
}
@ -70,4 +85,309 @@ public class VlogPullServiceImpl implements IVlogPullService {
}
return page;
}
@Override
public void pullVlog(VlogBO vlogBO) {
try {
// 验证分页参数
if (vlogBO.getCurrent() < 1) {
vlogBO.setCurrent(1L);
}
if (vlogBO.getSize() < 1 || vlogBO.getSize() > 50) {
vlogBO.setSize(10L);
}
// 如果有模糊查询条件时间区间条件或者有排序条件则只查询数据库
if (StringUtils.isNotBlank(vlogBO.getMobile())
|| StringUtils.isNotBlank(vlogBO.getNickname())
|| StringUtils.isNotBlank(vlogBO.getTitleQuery())
|| vlogBO.getStartTime() != null
|| vlogBO.getEndTime() != null
|| vlogBO.getStatus() != null
|| StringUtils.isNotBlank(vlogBO.getColumn())) {
// 创建分页对象
Page<Map<String, Object>> page = new Page<>(vlogBO.getCurrent(), vlogBO.getSize());
// 调用服务层方法进行查询
IPage<Map<String, Object>> result = vlogService.getVlogListByMobile(page, vlogBO);
return;
}
// 否则查询腾讯云点播
// 验证密钥配置
String secretId = tencentCloudUtil.getSecretId();
String secretKey = tencentCloudUtil.getSecretKey();
if (secretId == null || secretId.isEmpty() || secretKey == null || secretKey.isEmpty()) {
log.error("腾讯云密钥未配置");
return;
}
// 计算实际的offset
long offset = (long) ((vlogBO.getCurrent() - 1) * vlogBO.getSize());
if (offset < 0) {
offset = 0;
}
// 打印分页参数
log.info("分页参数 - 当前页码: {}, 每页记录数: {}, 偏移量: {}", vlogBO.getCurrent(), vlogBO.getSize(), offset);
// 创建请求对象
SearchMediaRequest req = new SearchMediaRequest();
// 设置分页参数
req.setOffset(offset);
req.setLimit(vlogBO.getSize());
// 设置排序
SortBy sort = new SortBy();
sort.setField("CreateTime");
sort.setOrder("Desc");
req.setSort(sort);
// 调用腾讯云API获取视频列表
log.info("开始调用腾讯云 SearchMedia API");
SearchMediaResponse resp = searchMedia(req);
log.info("腾讯云 SearchMedia API 调用成功,总记录数: {}", resp.getTotalCount());
// 处理响应结果
Map<String, Object> result = new HashMap<>();
result.put("total", resp.getTotalCount());
result.put("current", vlogBO.getCurrent());
result.put("size", vlogBO.getSize());
result.put("pages", (resp.getTotalCount() + vlogBO.getSize() - 1) / vlogBO.getSize()); // 总页数
// 批量获取所有视频的统计信息避免重复查询
Map<String, String> fileIdToVlogIdMap = new HashMap<>();
List<String> allVlogIds = new ArrayList<>();
if (resp.getMediaInfoSet() != null) {
for (MediaInfo mediaInfo : resp.getMediaInfoSet()) {
String fileId = mediaInfo.getFileId();
Map<String, Object> statistics = vlogService.getVlogStatistics(fileId);
String vlogId = (String) statistics.get("vlogId");
if (StringUtils.isNotBlank(vlogId)) {
fileIdToVlogIdMap.put(fileId, vlogId);
allVlogIds.add(vlogId);
}
}
}
// 批量获取所有点赞数据
Map<String, Integer> vlogLikeCounts = new HashMap<>();
Map<String, Integer> vlogCommentCounts = new HashMap<>();
long totalLikeCounts = 0;
long totalCommentCounts = 0;
if (!allVlogIds.isEmpty()) {
// 批量获取所有点赞键
String likePattern = REDIS_USER_LIKE_VLOG + ":*";
Collection<String> allLikeKeys = RedisUtils.keys(likePattern);
if (allLikeKeys != null && !allLikeKeys.isEmpty()) {
// 逐个获取点赞值由于RedisUtils没有批量方法
Map<String, String> likeKeyValueMap = new HashMap<>();
for (String likeKey : allLikeKeys) {
String likeValue = RedisUtils.getCacheObject(likeKey);
if ("1".equals(likeValue)) {
likeKeyValueMap.put(likeKey, likeValue);
}
}
// 按vlogId分组统计点赞数
for (String vlogId : allVlogIds) {
int likeCount = 0;
for (String likeKey : likeKeyValueMap.keySet()) {
if (likeKey.endsWith(":" + vlogId)) {
likeCount++;
}
}
vlogLikeCounts.put(vlogId, likeCount);
totalLikeCounts += likeCount;
}
}
// 批量获取所有评论数
for (String vlogId : allVlogIds) {
String commentKey = REDIS_VLOG_COMMENT_COUNTS + ":" + vlogId;
String commentCountsStr = RedisUtils.getCacheObject(commentKey);
int commentCount = 0;
if (StringUtils.isNotBlank(commentCountsStr)) {
try {
commentCount = Integer.valueOf(commentCountsStr);
} catch (NumberFormatException e) {
log.warn("Redis中视频{}的评论数格式错误: {}", vlogId, commentCountsStr);
}
}
vlogCommentCounts.put(vlogId, commentCount);
totalCommentCounts += commentCount;
}
}
// 将总的点赞数和评论数添加到结果中
result.put("totalLikeCounts", totalLikeCounts);
result.put("totalCommentCounts", totalCommentCounts);
List<Map<String, Object>> mediaList = new ArrayList<>();
if (resp.getMediaInfoSet() != null) {
for (MediaInfo mediaInfo : resp.getMediaInfoSet()) {
Map<String, Object> mediaMap = new HashMap<>();
// 基础信息
MediaBasicInfo basicInfo = mediaInfo.getBasicInfo();
mediaMap.put("fileId", mediaInfo.getFileId());
mediaMap.put("name", basicInfo.getName());
mediaMap.put("description", basicInfo.getDescription());
mediaMap.put("createTime", basicInfo.getCreateTime());
mediaMap.put("updateTime", basicInfo.getUpdateTime());
mediaMap.put("expireTime", basicInfo.getExpireTime());
mediaMap.put("classId", basicInfo.getClassId());
mediaMap.put("className", basicInfo.getClassName());
mediaMap.put("classPath", basicInfo.getClassPath());
mediaMap.put("coverUrl", basicInfo.getCoverUrl());
mediaMap.put("type", basicInfo.getType());
mediaMap.put("mediaUrl", basicInfo.getMediaUrl());
mediaMap.put("status", basicInfo.getStatus());
mediaMap.put("storageRegion", basicInfo.getStorageRegion());
mediaMap.put("category", basicInfo.getCategory());
mediaMap.put("storageClass", basicInfo.getStorageClass());
mediaMap.put("tagSet", basicInfo.getTagSet());
// 获取视频统计信息
Map<String, Object> statistics = vlogService.getVlogStatistics(mediaInfo.getFileId());
mediaMap.putAll(statistics);
// 从statistics中获取vlogId用于Redis查询
String vlogId = (String) statistics.get("vlogId");
log.info("处理视频 fileId: {}, vlogId: {}", mediaInfo.getFileId(), vlogId);
// 使用批量获取的数据避免重复查询Redis
int videoLikeCounts = 0;
int videoCommentCounts = 0;
if (StringUtils.isNotBlank(vlogId)) {
videoLikeCounts = vlogLikeCounts.getOrDefault(vlogId, 0);
videoCommentCounts = vlogCommentCounts.getOrDefault(vlogId, 0);
}
mediaMap.put("likeCounts", videoLikeCounts);
mediaMap.put("commentCounts", videoCommentCounts);
// 获取视频上传者信息
Map<String, String> uploaderInfo = vlogService.getVlogUploaderInfo(mediaInfo.getFileId());
if (uploaderInfo != null) {
mediaMap.put("nickname", uploaderInfo.get("name"));
mediaMap.put("mobile", uploaderInfo.get("phone"));
}
mediaList.add(mediaMap);
}
}
result.put("records", mediaList);
}catch (Exception e){
log.error("拉取视频失败: {}", e.getMessage(), e);
}
}
public SearchMediaResponse searchMedia(SearchMediaRequest req) {
try {
// 获取VOD客户端实例
VodClient client = tencentCloudUtil.getVodClient();
// 设置默认值
if (req.getOffset() == null) {
req.setOffset(0L);
}
if (req.getLimit() == null) {
req.setLimit(10L);
}
// 添加日志打印请求参数
// log.info("SearchMediaRequest 请求参数: {}", req.toJsonString());
// 添加调试信息
log.info("请求时间戳: {}", System.currentTimeMillis());
log.info("请求参数详情 - Offset: {}, Limit: {}, Sort: {}, Filters: {}",
req.getOffset(),
req.getLimit(),
// req.getSort() != null ? req.getSort().toJsonString() : "null",
req.getFilters() != null ? String.join(",", req.getFilters()) : "null");
// 验证请求参数
validateSearchRequest(req);
SearchMediaResponse response = client.SearchMedia(req);
log.info("SearchMedia API 调用成功,返回结果数: {}", response.getTotalCount());
return response;
} catch (TencentCloudSDKException e) {
log.error("搜索媒体文件失败: {}", e.getMessage(), e);
// 添加更详细的错误信息
if (e.getMessage().contains("signature")) {
log.error("签名验证失败,请检查:\n1. 密钥是否正确\n2. 服务器时间是否准确\n3. 时区设置是否正确");
} else if (e.getMessage().contains("InvalidParameterValue")) {
log.error("参数值错误,请检查:\n1. 分页参数 Offset 和 Limit 是否有效\n2. 搜索条件是否合法\n3. 排序参数是否正确");
} else if (e.getMessage().contains("UnauthorizedOperation")) {
log.error("未授权操作,请检查:\n1. 密钥是否有权限\n2. 应用 ID 是否正确\n3. 是否开通了点播服务");
}
throw new RuntimeException("搜索媒体文件失败: " + e.getMessage());
}
}
/**
* 验证搜索请求参数
*/
private void validateSearchRequest(SearchMediaRequest req) {
// 验证分页参数
if (req.getOffset() < 0) {
throw new IllegalArgumentException("Offset 不能小于 0");
}
if (req.getLimit() < 1 || req.getLimit() > 50) {
throw new IllegalArgumentException("Limit 必须在 1-50 之间");
}
if (req.getOffset() + req.getLimit() > 5000) {
throw new IllegalArgumentException("Offset + Limit 不能超过 5000");
}
// 验证搜索条件
if (req.getFileIds() != null && req.getFileIds().length > 10) {
throw new IllegalArgumentException("FileIds 数组长度不能超过 10");
}
if (req.getNames() != null && req.getNames().length > 10) {
throw new IllegalArgumentException("Names 数组长度不能超过 10");
}
if (req.getNamePrefixes() != null && req.getNamePrefixes().length > 10) {
throw new IllegalArgumentException("NamePrefixes 数组长度不能超过 10");
}
if (req.getDescriptions() != null && req.getDescriptions().length > 10) {
throw new IllegalArgumentException("Descriptions 数组长度不能超过 10");
}
if (req.getClassIds() != null && req.getClassIds().length > 10) {
throw new IllegalArgumentException("ClassIds 数组长度不能超过 10");
}
if (req.getTags() != null && req.getTags().length > 16) {
throw new IllegalArgumentException("Tags 数组长度不能超过 16");
}
if (req.getSourceTypes() != null && req.getSourceTypes().length > 10) {
throw new IllegalArgumentException("SourceTypes 数组长度不能超过 10");
}
if (req.getStreamIds() != null && req.getStreamIds().length > 10) {
throw new IllegalArgumentException("StreamIds 数组长度不能超过 10");
}
if (req.getStorageRegions() != null && req.getStorageRegions().length > 20) {
throw new IllegalArgumentException("StorageRegions 数组长度不能超过 20");
}
// if (req.getMediaTypes() != null && req.getMediaTypes().length > 10) {
// throw new IllegalArgumentException("MediaTypes 数组长度不能超过 10");
// }
// if (req.getStatus() != null && req.getStatus().length > 10) {
// throw new IllegalArgumentException("Status 数组长度不能超过 10");
// }
// if (req.getReviewResults() != null && req.getReviewResults().length > 10) {
// throw new IllegalArgumentException("ReviewResults 数组长度不能超过 10");
// }
// if (req.getTrtcSdkAppIds() != null && req.getTrtcSdkAppIds().length > 10) {
// throw new IllegalArgumentException("TrtcSdkAppIds 数组长度不能超过 10");
// }
// if (req.getTrtcRoomIds() != null && req.getTrtcRoomIds().length > 10) {
// throw new IllegalArgumentException("TrtcRoomIds 数组长度不能超过 10");
// }
}
}

View File

@ -24,6 +24,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import static com.wzj.soopin.content.domain.base.BaseInfoProperties.REDIS_VLOG_BE_LIKED_COUNTS;
import static org.dromara.common.core.constant.GlobalConstants.HOT_VLOG_TAG;
@Service
@AllArgsConstructor
@ -38,18 +39,29 @@ public class VlogPushServiceImpl implements IVlogPushService {
@Override
public boolean cacheTopLikedVlogs(int limit ) {
try {
} catch (Exception e) {
log.error("缓存点赞最多视频到Redis失败", e);
}
return true;
}
@Override
public boolean pushVlogToMq(int count,String tag ) {
if(tag.equals(HOT_VLOG_TAG)){
// 1. 获取 ZSet 中所有成员按分数升序排列
// 这里我们只关心成员(member)不关心分数(score)所以使用 range(key, start, end)
log.info("开始查询点赞最多的{}条视频", limit);
Set<String> rankSet = redisCache.zSetRange(REDIS_VLOG_BE_LIKED_COUNTS, 0, limit);
log.info("开始查询点赞最多的{}条视频", count);
Set<String> rankSet = redisCache.zSetRange(REDIS_VLOG_BE_LIKED_COUNTS, 0, count);
if (rankSet == null ||rankSet.isEmpty()) {
return false;
}
if(rankSet!=null&&rankSet.size()<limit){
if(rankSet!=null&&rankSet.size()<count){
//从数据库中获取差量数据
Page<Vlog> vlogPage = new Page<>(1, limit-rankSet.size());
Page<Vlog> vlogPage = new Page<>(1, count-rankSet.size());
vlogPage = vlogService.page(vlogPage, new LambdaQueryWrapper<Vlog>()
.orderByDesc(Vlog::getLikeCounts));
.orderByDesc(Vlog::getLikeCounts));
Set<String> vlogIds = vlogPage.getRecords().stream().map(vlog -> vlog.getId()).collect(Collectors.toSet());
rankSet.addAll(vlogIds);
}
@ -63,42 +75,36 @@ public class VlogPushServiceImpl implements IVlogPushService {
//将数据发送的mq的热点数据队列
MQMessage message = MQMessage.builder()
.topic("MEMBER_VLOG_MSG")
.tag("hot")
.tag(HOT_VLOG_TAG)
.messageType("json")
.data(vlogId)
.source("vlog_service")
.sendTime(LocalDateTime.now())
.build();
MqUtil.sendMessage("MEMBER_VLOG_MSG", message);
MqUtil.sendMessage("MEMBER_VLOG_MSG",HOT_VLOG_TAG, message);
});
} catch (Exception e) {
log.error("缓存点赞最多视频到Redis失败", e);
}else{
Member member = memberService.getById(tag);
String cityCode = member.getCity();
IPage<String> vlogPage=vlogService.getVlogForUser(new Page<>(1, 100), Long.parseLong(tag));
if(CollectionUtils.isEmpty(vlogPage.getRecords())){
return false;
}
vlogPage.getRecords().stream().forEach(vlogId -> {
//将数据发送的mq的热点数据队列
MQMessage message = MQMessage.builder()
.topic("MEMBER_VLOG_MSG")
.tag(tag)
.messageType("json")
.data(vlogId)
.source("vlog_service")
.sendTime(LocalDateTime.now())
.build();
MqUtil.sendMessage("MEMBER_VLOG_MSG"+":"+tag, message);
});
}
return true;
}
@Override
public boolean pushVlogToMember(int count,Long memberId ) {
Member member = memberService.getById(memberId);
String cityCode = member.getCity();
IPage<String> vlogPage=vlogService.getVlogForUser(new Page<>(1, 100), memberId);
if(CollectionUtils.isEmpty(vlogPage.getRecords())){
return false;
}
vlogPage.getRecords().stream().forEach(vlogId -> {
//将数据发送的mq的热点数据队列
MQMessage message = MQMessage.builder()
.topic("MEMBER_VLOG_MSG")
.tag(memberId+"")
.messageType("json")
.data(vlogId)
.source("vlog_service")
.sendTime(LocalDateTime.now())
.build();
MqUtil.sendMessage("MEMBER_VLOG_MSG"+":"+memberId, message);
});
return true;
}

View File

@ -612,15 +612,15 @@ public class VlogServiceImpl extends ServiceImpl<VlogMapper, Vlog> implements Vl
}
@Override
public List<IndexVlogVO> getIndexVlogList(Map<String, Object> paramMap) {
List<IndexVlogVO> indexVlogVOPage = vlogMapperCustom.getIndexVlogList(paramMap);
public List<IndexVlogVO> getIndexVlogList(VlogBO bo) {
List<IndexVlogVO> indexVlogVOPage = vlogMapperCustom.getIndexVlogList(bo);
fillRedisColumn(indexVlogVOPage );
return indexVlogVOPage;
}
@Override
public Page<IndexVlogVO> getIndexVlogList(Map<String, Object> paramMap, Page page) {
Page<IndexVlogVO> indexVlogVOPage = vlogMapperCustom.getIndexVlogList(paramMap,page);
public Page<IndexVlogVO> getIndexVlogList(VlogBO bo, Page page) {
Page<IndexVlogVO> indexVlogVOPage = vlogMapperCustom.getIndexVlogList(bo,page);
fillRedisColumn(indexVlogVOPage.getRecords());
return indexVlogVOPage;
}

View File

@ -49,110 +49,8 @@ public class VlogUploadServiceImpl implements VlogUploadService {
// return client.PullUpload(req);
// }
@Override
public SearchMediaResponse searchMedia(SearchMediaRequest req) {
try {
// 获取VOD客户端实例
VodClient client = tencentCloudUtil.getVodClient();
// 设置默认值
if (req.getOffset() == null) {
req.setOffset(0L);
}
if (req.getLimit() == null) {
req.setLimit(10L);
}
// 添加日志打印请求参数
// log.info("SearchMediaRequest 请求参数: {}", req.toJsonString());
// 添加调试信息
log.info("请求时间戳: {}", System.currentTimeMillis());
log.info("请求参数详情 - Offset: {}, Limit: {}, Sort: {}, Filters: {}",
req.getOffset(),
req.getLimit(),
// req.getSort() != null ? req.getSort().toJsonString() : "null",
req.getFilters() != null ? String.join(",", req.getFilters()) : "null");
// 验证请求参数
validateSearchRequest(req);
SearchMediaResponse response = client.SearchMedia(req);
log.info("SearchMedia API 调用成功,返回结果数: {}", response.getTotalCount());
return response;
} catch (TencentCloudSDKException e) {
log.error("搜索媒体文件失败: {}", e.getMessage(), e);
// 添加更详细的错误信息
if (e.getMessage().contains("signature")) {
log.error("签名验证失败,请检查:\n1. 密钥是否正确\n2. 服务器时间是否准确\n3. 时区设置是否正确");
} else if (e.getMessage().contains("InvalidParameterValue")) {
log.error("参数值错误,请检查:\n1. 分页参数 Offset 和 Limit 是否有效\n2. 搜索条件是否合法\n3. 排序参数是否正确");
} else if (e.getMessage().contains("UnauthorizedOperation")) {
log.error("未授权操作,请检查:\n1. 密钥是否有权限\n2. 应用 ID 是否正确\n3. 是否开通了点播服务");
}
throw new RuntimeException("搜索媒体文件失败: " + e.getMessage());
}
}
/**
* 验证搜索请求参数
*/
private void validateSearchRequest(SearchMediaRequest req) {
// 验证分页参数
if (req.getOffset() < 0) {
throw new IllegalArgumentException("Offset 不能小于 0");
}
if (req.getLimit() < 1 || req.getLimit() > 50) {
throw new IllegalArgumentException("Limit 必须在 1-50 之间");
}
if (req.getOffset() + req.getLimit() > 5000) {
throw new IllegalArgumentException("Offset + Limit 不能超过 5000");
}
// 验证搜索条件
if (req.getFileIds() != null && req.getFileIds().length > 10) {
throw new IllegalArgumentException("FileIds 数组长度不能超过 10");
}
if (req.getNames() != null && req.getNames().length > 10) {
throw new IllegalArgumentException("Names 数组长度不能超过 10");
}
if (req.getNamePrefixes() != null && req.getNamePrefixes().length > 10) {
throw new IllegalArgumentException("NamePrefixes 数组长度不能超过 10");
}
if (req.getDescriptions() != null && req.getDescriptions().length > 10) {
throw new IllegalArgumentException("Descriptions 数组长度不能超过 10");
}
if (req.getClassIds() != null && req.getClassIds().length > 10) {
throw new IllegalArgumentException("ClassIds 数组长度不能超过 10");
}
if (req.getTags() != null && req.getTags().length > 16) {
throw new IllegalArgumentException("Tags 数组长度不能超过 16");
}
if (req.getSourceTypes() != null && req.getSourceTypes().length > 10) {
throw new IllegalArgumentException("SourceTypes 数组长度不能超过 10");
}
if (req.getStreamIds() != null && req.getStreamIds().length > 10) {
throw new IllegalArgumentException("StreamIds 数组长度不能超过 10");
}
if (req.getStorageRegions() != null && req.getStorageRegions().length > 20) {
throw new IllegalArgumentException("StorageRegions 数组长度不能超过 20");
}
// if (req.getMediaTypes() != null && req.getMediaTypes().length > 10) {
// throw new IllegalArgumentException("MediaTypes 数组长度不能超过 10");
// }
// if (req.getStatus() != null && req.getStatus().length > 10) {
// throw new IllegalArgumentException("Status 数组长度不能超过 10");
// }
// if (req.getReviewResults() != null && req.getReviewResults().length > 10) {
// throw new IllegalArgumentException("ReviewResults 数组长度不能超过 10");
// }
// if (req.getTrtcSdkAppIds() != null && req.getTrtcSdkAppIds().length > 10) {
// throw new IllegalArgumentException("TrtcSdkAppIds 数组长度不能超过 10");
// }
// if (req.getTrtcRoomIds() != null && req.getTrtcRoomIds().length > 10) {
// throw new IllegalArgumentException("TrtcRoomIds 数组长度不能超过 10");
// }
}
// 视频审核
@Override

View File

@ -32,35 +32,35 @@
WHERE
v.is_private = 0
<choose>
<when test="paramMap.status != null and paramMap.status!=''">
AND v.status = #{paramMap.status}
<when test="bo.status != null and bo.status!=''">
AND v.status = #{bo.status}
</when>
<otherwise>
AND v.status = 1
</otherwise>
</choose>
AND v.first_frame_img IS NOT NULL
<if test="paramMap.cityCode != null and paramMap.cityCode != ''">
AND v.city_code = #{paramMap.cityCode}
<if test="bo.cityCode != null and bo.cityCode != ''">
AND v.city_code = #{bo.cityCode}
</if>
<if test="paramMap.search != null and paramMap.search != ''">
AND v.title like '%${paramMap.search}%'
<if test="bo.title != null and bo.title != ''">
AND v.title like '%${bo.title}%'
</if>
<if test="paramMap.blockVd != null and paramMap.blockVd.size() > 0">
<if test="bo.blockVd != null and bo.blockVd.size() > 0">
AND v.id NOT IN
<foreach collection="paramMap.blockVd" item="vlogId" open="(" separator="," close=")">
<foreach collection="bo.blockVd" item="vlogId" open="(" separator="," close=")">
#{vlogId}
</foreach>
</if>
<if test="paramMap.blockUser != null and paramMap.blockUser.size() > 0">
<if test="bo.blockUser != null and bo.blockUser.size() > 0">
AND v.member_id NOT IN
<foreach collection="paramMap.blockUser" item="memberId" open="(" separator="," close=")">
<foreach collection="bo.blockUser" item="memberId" open="(" separator="," close=")">
#{memberId}
</foreach>
</if>
<if test="paramMap.ids != null and paramMap.ids.size() > 0">
<if test="bo.ids != null and bo.ids.size() > 0">
AND v.id IN
<foreach collection="paramMap.ids" item="vlogId" open="(" separator="," close=")">
<foreach collection="bo.ids" item="vlogId" open="(" separator="," close=")">
#{vlogId}
</foreach>
</if>