feat: 添加数据分析模块、媒体上传功能与剧集管理完善
本次提交新增了完整的数据分析模块,包括数据同步、指标统计接口;实现了基于BytePlus的直传媒体上传流程,包含上传任务创建、完成、审核同步等功能;同时完善了剧集管理功能,新增了剧集配置、更新接口,扩展了审核状态枚举,补充了剧集与剧集指标的关联逻辑,还添加了播放器对外接口与相关测试用例。
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { PaginatedResponse } from '../../common/dto/paginated-response.dto';
|
||||
import { DramasService } from '../dramas/dramas.service';
|
||||
import { CreateCoverDto } from './dto/create-cover.dto';
|
||||
import { CreateDirectUploadTaskDto } from './dto/create-direct-upload-task.dto';
|
||||
import { CreateUploadJobDto } from './dto/create-upload-job.dto';
|
||||
import {
|
||||
CoverRecord,
|
||||
MediaUploadJobRecord,
|
||||
} from './interfaces/media-records.interface';
|
||||
import { BytePlusMediaProvider } from './providers/byteplus-media.provider';
|
||||
|
||||
interface PaginationInput {
|
||||
page: number;
|
||||
@@ -19,6 +23,11 @@ export class MediaService {
|
||||
private readonly covers: CoverRecord[] = [];
|
||||
private readonly uploadJobs: MediaUploadJobRecord[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly dramasService: DramasService,
|
||||
private readonly bytePlusMediaProvider: BytePlusMediaProvider,
|
||||
) {}
|
||||
|
||||
createCover(dto: CreateCoverDto): CoverRecord {
|
||||
const cover: CoverRecord = {
|
||||
id: this.coverSequence++,
|
||||
@@ -45,6 +54,136 @@ export class MediaService {
|
||||
return job;
|
||||
}
|
||||
|
||||
createDirectUploadTask(dto: CreateDirectUploadTaskDto): MediaUploadJobRecord {
|
||||
this.dramasService.getEpisodeOrThrow(dto.episodeId);
|
||||
const now = new Date().toISOString();
|
||||
const jobId = randomUUID();
|
||||
const upload = this.bytePlusMediaProvider.createDirectUpload({
|
||||
jobId,
|
||||
fileName: dto.fileName,
|
||||
contentType: dto.contentType,
|
||||
});
|
||||
const job: MediaUploadJobRecord = {
|
||||
id: this.uploadJobSequence++,
|
||||
jobId,
|
||||
episodeId: dto.episodeId,
|
||||
status: 'pending',
|
||||
fileName: dto.fileName,
|
||||
contentType: dto.contentType,
|
||||
fileSize: dto.fileSize,
|
||||
bucket: upload.bucket,
|
||||
objectKey: upload.objectKey,
|
||||
uploadUrl: upload.uploadUrl,
|
||||
reviewStatus: 'not_submitted',
|
||||
rawResponse: {
|
||||
headers: upload.headers,
|
||||
expiresAt: upload.expiresAt,
|
||||
},
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
this.uploadJobs.push(job);
|
||||
return job;
|
||||
}
|
||||
|
||||
completeDirectUploadTask(jobId: string): MediaUploadJobRecord {
|
||||
const job = this.getUploadJobOrThrow(jobId);
|
||||
if (!job.objectKey || !job.episodeId) {
|
||||
throw new NotFoundException('Upload task not found');
|
||||
}
|
||||
|
||||
const completed = this.bytePlusMediaProvider.completeUpload({
|
||||
jobId: job.jobId,
|
||||
objectKey: job.objectKey,
|
||||
});
|
||||
const review = this.bytePlusMediaProvider.submitReview(completed.byteplusVid);
|
||||
|
||||
Object.assign(job, {
|
||||
byteplusVid: completed.byteplusVid,
|
||||
videoUrl: completed.videoUrl,
|
||||
width: completed.width,
|
||||
height: completed.height,
|
||||
durationSeconds: completed.durationSeconds,
|
||||
status: review.reviewStatus,
|
||||
reviewStatus: review.reviewStatus,
|
||||
reviewMessage: review.reviewMessage,
|
||||
rawResponse: {
|
||||
...job.rawResponse,
|
||||
upload: completed.rawResponse,
|
||||
review,
|
||||
},
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
this.dramasService.attachEpisodeMedia({
|
||||
episodeId: job.episodeId,
|
||||
byteplusVid: completed.byteplusVid,
|
||||
videoUrl: completed.videoUrl,
|
||||
storageBucket: job.bucket ?? '',
|
||||
objectKey: job.objectKey,
|
||||
durationSeconds: completed.durationSeconds,
|
||||
width: completed.width,
|
||||
height: completed.height,
|
||||
reviewStatus: review.reviewStatus,
|
||||
reviewMessage: review.reviewMessage,
|
||||
});
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
retryReview(jobId: string): MediaUploadJobRecord {
|
||||
const job = this.getUploadJobOrThrow(jobId);
|
||||
if (!job.byteplusVid || !job.episodeId) {
|
||||
throw new NotFoundException('Upload task not found');
|
||||
}
|
||||
|
||||
const review = this.bytePlusMediaProvider.submitReview(job.byteplusVid);
|
||||
job.status = review.reviewStatus;
|
||||
job.reviewStatus = review.reviewStatus;
|
||||
job.reviewMessage = review.reviewMessage;
|
||||
job.updatedAt = new Date().toISOString();
|
||||
this.dramasService.updateEpisodeReviewStatus(
|
||||
job.episodeId,
|
||||
review.reviewStatus,
|
||||
review.reviewMessage,
|
||||
);
|
||||
return job;
|
||||
}
|
||||
|
||||
syncReviewStatuses() {
|
||||
const reviewingJobs = this.uploadJobs.filter(
|
||||
(job) =>
|
||||
job.byteplusVid &&
|
||||
job.episodeId &&
|
||||
(job.reviewStatus === 'reviewing' || job.reviewStatus === 'pending'),
|
||||
);
|
||||
|
||||
for (const job of reviewingJobs) {
|
||||
const review = this.bytePlusMediaProvider.queryReviewStatus(
|
||||
job.byteplusVid as string,
|
||||
);
|
||||
job.status = review.reviewStatus;
|
||||
job.reviewStatus = review.reviewStatus;
|
||||
job.reviewMessage = review.reviewMessage;
|
||||
job.updatedAt = new Date().toISOString();
|
||||
this.dramasService.updateEpisodeReviewStatus(
|
||||
job.episodeId as number,
|
||||
review.reviewStatus,
|
||||
review.reviewMessage,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'completed',
|
||||
synced: reviewingJobs.length,
|
||||
};
|
||||
}
|
||||
|
||||
getUploadTask(jobId: string): MediaUploadJobRecord {
|
||||
return this.getUploadJobOrThrow(jobId);
|
||||
}
|
||||
|
||||
listUploadJobs(
|
||||
input: PaginationInput,
|
||||
): PaginatedResponse<MediaUploadJobRecord> {
|
||||
@@ -62,4 +201,13 @@ export class MediaService {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private getUploadJobOrThrow(jobId: string) {
|
||||
const job = this.uploadJobs.find((item) => item.jobId === jobId);
|
||||
if (!job) {
|
||||
throw new NotFoundException('Upload task not found');
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user