|
|
@@ -3,23 +3,9 @@ import { Injectable, Logger } from '@nestjs/common';
|
|
|
import { RedisService } from '@box/db/redis/redis.service';
|
|
|
import { PrismaMongoService } from '../../prisma/prisma-mongo.service';
|
|
|
import { tsCacheKeys } from '@box/common/cache/ts-cache-key.provider';
|
|
|
-import type { VideoHomeSectionKey } from '@box/common/cache/ts-cache-key.provider';
|
|
|
+import { VideoCacheHelper } from '@box/common/cache/video-cache.helper';
|
|
|
import {
|
|
|
- RawVideoPayloadRow,
|
|
|
- toVideoPayload,
|
|
|
- VideoPayload,
|
|
|
- parseVideoPayload,
|
|
|
- VideoCacheHelper,
|
|
|
-} from '@box/common/cache/video-cache.helper';
|
|
|
-import {
|
|
|
- VideoCategoryDto,
|
|
|
- VideoTagDto,
|
|
|
- VideoDetailDto,
|
|
|
- VideoPageDto,
|
|
|
- VideoCategoryWithTagsResponseDto,
|
|
|
VideoListRequestDto,
|
|
|
- VideoListResponseDto,
|
|
|
- VideoSearchByTagRequestDto,
|
|
|
VideoClickDto,
|
|
|
RecommendedVideosDto,
|
|
|
VideoItemDto,
|
|
|
@@ -38,14 +24,6 @@ import {
|
|
|
import { CategoryDto } from '../homepage/dto/homepage.dto';
|
|
|
import { VideoListItemDto } from './dto/video-list-response.dto';
|
|
|
|
|
|
-/**
|
|
|
- * VideoService provides read-only access to video data from Redis cache.
|
|
|
- * All data is prebuilt and maintained by box-mgnt-api cache builders.
|
|
|
- * Follows the new Redis cache semantics where:
|
|
|
- * - Video list keys store video IDs only (not JSON objects)
|
|
|
- * - Tag metadata keys store tag JSON objects
|
|
|
- * - Video details are fetched separately using video IDs
|
|
|
- */
|
|
|
@Injectable()
|
|
|
export class VideoService {
|
|
|
private readonly logger = new Logger(VideoService.name);
|
|
|
@@ -59,14 +37,7 @@ export class VideoService {
|
|
|
this.cacheHelper = new VideoCacheHelper(redis);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Get home section videos for a channel.
|
|
|
- * Reads from appVideoHomeSectionKey (LIST of videoIds).
|
|
|
- * Returns video details for each ID.
|
|
|
- */
|
|
|
- async getHomeSectionVideos(
|
|
|
- channelId: string
|
|
|
- ): Promise<any[]> {
|
|
|
+ async getHomeSectionVideos(channelId: string): Promise<any[]> {
|
|
|
try {
|
|
|
const channel = await this.mongoPrisma.channel.findUnique({
|
|
|
where: { channelId },
|
|
|
@@ -75,11 +46,14 @@ export class VideoService {
|
|
|
const result: { tag: string; records: VideoListItemDto[] }[] = [];
|
|
|
|
|
|
for (const tag of channel.tagNames) {
|
|
|
- const records = await this.getVideoList({
|
|
|
- random: true,
|
|
|
- tag,
|
|
|
- size: 7,
|
|
|
- }, 3600 * 24);
|
|
|
+ const records = await this.getVideoList(
|
|
|
+ {
|
|
|
+ random: true,
|
|
|
+ tag,
|
|
|
+ size: 7,
|
|
|
+ },
|
|
|
+ 3600 * 24,
|
|
|
+ );
|
|
|
|
|
|
result.push({
|
|
|
tag,
|
|
|
@@ -97,20 +71,18 @@ export class VideoService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Get paginated list of videos for a category with optional tag filtering.
|
|
|
- * Reads video IDs from Redis cache, fetches full details from MongoDB,
|
|
|
- * and returns paginated results.
|
|
|
- */
|
|
|
- async getVideoList(dto: VideoListRequestDto, ttl?: number): Promise<VideoListItemDto[]> {
|
|
|
+ async getVideoList(
|
|
|
+ dto: VideoListRequestDto,
|
|
|
+ ttl?: number,
|
|
|
+ ): Promise<VideoListItemDto[]> {
|
|
|
const { page, size, tag, keyword, random } = dto;
|
|
|
const start = (page - 1) * size;
|
|
|
|
|
|
- const cacheKey = `video:list:${Buffer.from(
|
|
|
- JSON.stringify(dto),
|
|
|
- ).toString('base64')}`;
|
|
|
+ const cacheKey = `video:list:${Buffer.from(JSON.stringify(dto)).toString(
|
|
|
+ 'base64',
|
|
|
+ )}`;
|
|
|
|
|
|
- if(!ttl){
|
|
|
+ if (!ttl) {
|
|
|
ttl = random ? 15 : 300;
|
|
|
}
|
|
|
|
|
|
@@ -121,11 +93,11 @@ export class VideoService {
|
|
|
return cache;
|
|
|
}
|
|
|
|
|
|
- let where: any = {
|
|
|
- status: 'Completed'
|
|
|
+ const where: any = {
|
|
|
+ status: 'Completed',
|
|
|
};
|
|
|
|
|
|
- if(random){
|
|
|
+ if (random) {
|
|
|
if (tag) {
|
|
|
where.secondTags = tag;
|
|
|
}
|
|
|
@@ -134,7 +106,7 @@ export class VideoService {
|
|
|
where.title = {
|
|
|
$regex: keyword,
|
|
|
$options: 'i',
|
|
|
- }
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
fallbackRecords = (await this.mongoPrisma.videoMedia.aggregateRaw({
|
|
|
@@ -154,7 +126,7 @@ export class VideoService {
|
|
|
},
|
|
|
],
|
|
|
})) as unknown as VideoListItemDto[];
|
|
|
- }else{
|
|
|
+ } else {
|
|
|
if (tag) {
|
|
|
where.secondTags = {
|
|
|
has: tag,
|
|
|
@@ -163,8 +135,9 @@ export class VideoService {
|
|
|
|
|
|
if (keyword) {
|
|
|
where.title = {
|
|
|
- contains: keyword, mode: 'insensitive'
|
|
|
- }
|
|
|
+ contains: keyword,
|
|
|
+ mode: 'insensitive',
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
fallbackRecords = (await this.mongoPrisma.videoMedia.findMany({
|
|
|
@@ -198,16 +171,6 @@ export class VideoService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Record video click event.
|
|
|
- * Publishes a stats.video.click event to RabbitMQ for analytics processing.
|
|
|
- * Uses fire-and-forget pattern for non-blocking operation.
|
|
|
- *
|
|
|
- * @param uid - User ID from JWT
|
|
|
- * @param body - Video click data from client
|
|
|
- * @param ip - Client IP address
|
|
|
- * @param userAgent - User agent string (unused but kept for compatibility)
|
|
|
- */
|
|
|
async recordVideoClick(
|
|
|
uid: string,
|
|
|
body: VideoClickDto,
|
|
|
@@ -471,7 +434,10 @@ export class VideoService {
|
|
|
async getGuessLikeVideos(tag: string): Promise<VideoItemDto[]> {
|
|
|
try {
|
|
|
// Try to fetch from Redis cache first
|
|
|
- const cached = await this.readCachedVideoList(tsCacheKeys.video.guess() + encodeURIComponent(tag), 'guess like videos');
|
|
|
+ const cached = await this.readCachedVideoList(
|
|
|
+ tsCacheKeys.video.guess() + encodeURIComponent(tag),
|
|
|
+ 'guess like videos',
|
|
|
+ );
|
|
|
|
|
|
if (cached && Array.isArray(cached) && cached.length > 0) {
|
|
|
return cached;
|
|
|
@@ -493,9 +459,15 @@ export class VideoService {
|
|
|
this.mapVideoToDto(v),
|
|
|
);
|
|
|
|
|
|
- this.redis.setJson(tsCacheKeys.video.guess() + encodeURIComponent(tag), items, 3600).catch(err => {
|
|
|
- this.logger.warn("Redis setJson video.guess failed", err);
|
|
|
- });
|
|
|
+ this.redis
|
|
|
+ .setJson(
|
|
|
+ tsCacheKeys.video.guess() + encodeURIComponent(tag),
|
|
|
+ items,
|
|
|
+ 3600,
|
|
|
+ )
|
|
|
+ .catch((err) => {
|
|
|
+ this.logger.warn('Redis setJson video.guess failed', err);
|
|
|
+ });
|
|
|
|
|
|
return items;
|
|
|
} catch (error) {
|