chore: 初始化TikTok短剧后端项目基础架构

创建了完整的NestJS项目结构,包含配置文件、核心模块、工具类、拦截器、过滤器等基础代码,实现了健康检查、用户认证、管理员RBAC、剧集管理、订单支付、媒体管理等核心功能的基础框架,配置了ESLint、TypeScript、Jest等开发工具链。
This commit is contained in:
2026-07-01 13:55:37 +08:00
commit ef8c31d3be
92 changed files with 11462 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsUrl } from 'class-validator';
export class CreateCoverDto {
@ApiProperty()
@IsString()
fileName: string;
@ApiProperty()
@IsUrl({ require_tld: false })
url: string;
}

View File

@@ -0,0 +1,22 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsObject, IsOptional, IsString } from 'class-validator';
export class CreateUploadJobDto {
@ApiProperty()
@IsString()
jobId: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
byteplusVid?: string;
@ApiProperty()
@IsString()
status: string;
@ApiPropertyOptional()
@IsOptional()
@IsObject()
rawResponse?: Record<string, unknown>;
}

View File

@@ -0,0 +1,34 @@
import {
Column,
CreateDateColumn,
Entity,
Index,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('media_upload_jobs')
export class MediaUploadJob {
@PrimaryGeneratedColumn()
id: number;
@Index({ unique: true })
@Column({ type: 'varchar', length: 128 })
jobId: string;
@Index()
@Column({ type: 'varchar', length: 128, nullable: true })
byteplusVid?: string;
@Column({ type: 'varchar', length: 64 })
status: string;
@Column({ type: 'json', nullable: true })
rawResponse?: Record<string, unknown>;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@@ -0,0 +1,16 @@
export interface CoverRecord {
id: number;
fileName: string;
url: string;
createdAt: string;
}
export interface MediaUploadJobRecord {
id: number;
jobId: string;
byteplusVid?: string;
status: string;
rawResponse?: Record<string, unknown>;
createdAt: string;
updatedAt: string;
}

View File

@@ -0,0 +1,40 @@
import { Body, Controller, Get, Post, Query, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { RequirePermissions } from '../admin/decorators/require-permissions.decorator';
import { AdminJwtAuthGuard } from '../admin/guards/admin-jwt-auth.guard';
import { PermissionsGuard } from '../admin/guards/permissions.guard';
import { CreateCoverDto } from './dto/create-cover.dto';
import { CreateUploadJobDto } from './dto/create-upload-job.dto';
import { MediaService } from './media.service';
@ApiTags('Media')
@Controller('/api/admin/v1/media')
@ApiBearerAuth()
@UseGuards(AdminJwtAuthGuard, PermissionsGuard)
export class MediaController {
constructor(private readonly mediaService: MediaService) {}
@Post('/covers')
@RequirePermissions('media:create')
createCover(@Body() dto: CreateCoverDto) {
return this.mediaService.createCover(dto);
}
@Post('/upload-jobs')
@RequirePermissions('media:create')
createUploadJob(@Body() dto: CreateUploadJobDto) {
return this.mediaService.createUploadJob(dto);
}
@Get('/upload-jobs')
@RequirePermissions('media:read')
listUploadJobs(
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.mediaService.listUploadJobs({
page: Number(page) || 1,
pageSize: Number(pageSize) || 20,
});
}
}

View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { AdminModule } from '../admin/admin.module';
import { MediaController } from './media.controller';
import { MediaService } from './media.service';
@Module({
imports: [AdminModule],
controllers: [MediaController],
providers: [MediaService],
exports: [MediaService],
})
export class MediaModule {}

View File

@@ -0,0 +1,65 @@
import { Injectable } from '@nestjs/common';
import { PaginatedResponse } from '../../common/dto/paginated-response.dto';
import { CreateCoverDto } from './dto/create-cover.dto';
import { CreateUploadJobDto } from './dto/create-upload-job.dto';
import {
CoverRecord,
MediaUploadJobRecord,
} from './interfaces/media-records.interface';
interface PaginationInput {
page: number;
pageSize: number;
}
@Injectable()
export class MediaService {
private coverSequence = 1;
private uploadJobSequence = 1;
private readonly covers: CoverRecord[] = [];
private readonly uploadJobs: MediaUploadJobRecord[] = [];
createCover(dto: CreateCoverDto): CoverRecord {
const cover: CoverRecord = {
id: this.coverSequence++,
fileName: dto.fileName,
url: dto.url,
createdAt: new Date().toISOString(),
};
this.covers.push(cover);
return cover;
}
createUploadJob(dto: CreateUploadJobDto): MediaUploadJobRecord {
const now = new Date().toISOString();
const job: MediaUploadJobRecord = {
id: this.uploadJobSequence++,
jobId: dto.jobId,
byteplusVid: dto.byteplusVid,
status: dto.status,
rawResponse: dto.rawResponse,
createdAt: now,
updatedAt: now,
};
this.uploadJobs.push(job);
return job;
}
listUploadJobs(
input: PaginationInput,
): PaginatedResponse<MediaUploadJobRecord> {
const page = Math.max(input.page, 1);
const pageSize = Math.min(Math.max(input.pageSize, 1), 100);
const start = (page - 1) * pageSize;
const records = [...this.uploadJobs].reverse();
return {
list: records.slice(start, start + pageSize),
pagination: {
page,
pageSize,
total: records.length,
},
};
}
}