refactor: 统一实体类命名规范并完善模板执行进度查询功能

- 统一所有实体类添加Entity后缀保持命名一致性
- 重命名User->UserEntity, PlatformUser->PlatformUserEntity等
- 更新所有实体间关联引用和Repository注入
- 新增模板执行进度查询接口GET /templates/execution/:taskId/progress
- 新增用户执行任务列表查询接口GET /templates/executions/user/:userId
- 完善数据库配置支持环境变量动态配置
- 添加class-validator和class-transformer依赖支持数据验证
This commit is contained in:
imeepos
2025-09-04 17:05:34 +08:00
parent 1a7ef913ef
commit 3b07e641db
11 changed files with 390 additions and 66 deletions

17
.env.example Normal file
View File

@@ -0,0 +1,17 @@
# 数据库配置
DB_HOST=mysql-6bc9094abd49-public.rds.volces.com
DB_PORT=53305
DB_USERNAME=root
DB_PASSWORD=WsJWXfwFx0bq6DE2kmB6
DB_DATABASE=nano_camera_miniapp
DB_SYNCHRONIZE=false
DB_LOGGING=true
DB_MIGRATIONS_RUN=true
# 应用配置
NODE_ENV=development
PORT=3000
# N8N配置
N8N_BASE_URL=http://localhost:5678
N8N_API_KEY=your_n8n_api_key

View File

@@ -1,17 +1,41 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { TemplateService } from './templates/index';
import { TemplateManager } from './templates/types';
import { typeOrmConfig } from './config/database.config';
import { databaseConfig } from './config/database.config';
import { N8nTemplateEntity } from './entities/n8n-template.entity';
import { TemplateExecutionEntity } from './entities/template-execution.entity';
import { UserEntity } from './entities/user.entity';
import { PlatformUserEntity } from './entities/platform-user.entity';
import { ExtensionDataEntity } from './entities/extension-data.entity';
import { UserCreditEntity } from './entities/user-credit.entity';
import { UserSubscriptionEntity } from './entities/user-subscription.entity';
import { AdWatchEntity } from './entities/ad-watch.entity';
import { N8nTemplateFactoryService } from './services/n8n-template-factory.service';
import { TemplateController } from './controllers/template.controller';
@Module({
imports: [
TypeOrmModule.forRoot(typeOrmConfig),
TypeOrmModule.forFeature([N8nTemplateEntity])
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['.env.local', '.env'],
}),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => databaseConfig(configService),
}),
TypeOrmModule.forFeature([
N8nTemplateEntity,
TemplateExecutionEntity,
UserEntity,
PlatformUserEntity,
ExtensionDataEntity,
UserCreditEntity,
UserSubscriptionEntity,
AdWatchEntity
])
],
controllers: [AppController, TemplateController],
providers: [

View File

@@ -3,33 +3,16 @@ import { ConfigService } from '@nestjs/config';
export const databaseConfig = (configService: ConfigService): TypeOrmModuleOptions => ({
type: 'mysql',
host: 'mysql-6bc9094abd49-public.rds.volces.com',
port: 53305,
username: 'root',
password: 'WsJWXfwFx0bq6DE2kmB6',
database: 'nano_camera_miniapp',
host: configService.get<string>('DB_HOST', 'mysql-6bc9094abd49-public.rds.volces.com'),
port: configService.get<number>('DB_PORT', 53305),
username: configService.get<string>('DB_USERNAME', 'root'),
password: configService.get<string>('DB_PASSWORD', 'WsJWXfwFx0bq6DE2kmB6'),
database: configService.get<string>('DB_DATABASE', 'nano_camera_miniapp'),
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
migrations: [__dirname + '/../migrations/*{.ts,.js}'],
synchronize: false, // 生产环境建议设为false
logging: process.env.NODE_ENV === 'development',
migrationsRun: true, // 自动运行迁移
synchronize: configService.get<boolean>('DB_SYNCHRONIZE', false), // 生产环境建议设为false
logging: configService.get<boolean>('DB_LOGGING', process.env.NODE_ENV === 'development'),
migrationsRun: configService.get<boolean>('DB_MIGRATIONS_RUN', true), // 自动运行迁移
charset: 'utf8mb4',
timezone: '+08:00',
});
// 也可以直接导出配置对象
export const typeOrmConfig: TypeOrmModuleOptions = {
type: 'mysql',
host: 'mysql-6bc9094abd49-public.rds.volces.com',
port: 53305,
username: 'root',
password: 'WsJWXfwFx0bq6DE2kmB6',
database: 'nano_camera_miniapp',
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
migrations: [__dirname + '/../migrations/*{.ts,.js}'],
synchronize: false,
logging: process.env.NODE_ENV === 'development',
migrationsRun: true,
charset: 'utf8mb4',
timezone: '+08:00',
};
});

View File

@@ -14,6 +14,7 @@ import {
import { N8nTemplateFactoryService } from '../services/n8n-template-factory.service';
import { N8nTemplateEntity, TemplateType } from '../entities/n8n-template.entity';
import { CreateTemplateDto, UpdateTemplateDto } from '../dto/template.dto';
import { TemplateExecutionEntity } from '../entities/template-execution.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@@ -26,7 +27,9 @@ export class TemplateController {
constructor(
private readonly templateFactory: N8nTemplateFactoryService,
@InjectRepository(N8nTemplateEntity)
private readonly templateRepository: Repository<N8nTemplateEntity>
private readonly templateRepository: Repository<N8nTemplateEntity>,
@InjectRepository(TemplateExecutionEntity)
private readonly executionRepository: Repository<TemplateExecutionEntity>
) {}
/**
@@ -287,6 +290,112 @@ export class TemplateController {
}
}
/**
* 查询模板执行进度
* @param taskId 任务ID
* @returns 执行进度信息
*/
@Get('execution/:taskId/progress')
async getExecutionProgress(@Param('taskId', ParseIntPipe) taskId: number) {
try {
const execution = await this.executionRepository.findOne({
where: { id: taskId },
relations: ['template']
});
if (!execution) {
throw new HttpException('Execution task not found', HttpStatus.NOT_FOUND);
}
return {
success: true,
data: {
taskId: execution.id,
templateId: execution.templateId,
templateName: execution.template?.name,
userId: execution.userId,
platform: execution.platform,
type: execution.type,
status: execution.status,
progress: execution.progress,
inputImageUrl: execution.inputImageUrl,
outputUrl: execution.outputUrl,
thumbnailUrl: execution.thumbnailUrl,
errorMessage: execution.errorMessage,
creditCost: execution.creditCost,
startedAt: execution.startedAt,
completedAt: execution.completedAt,
executionDuration: execution.executionDuration,
createdAt: execution.createdAt,
updatedAt: execution.updatedAt
}
};
} catch (error) {
if (error instanceof HttpException) {
throw error;
}
throw new HttpException(
error.message || 'Failed to fetch execution progress',
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
/**
* 查询用户的执行任务列表
* @param userId 用户ID
* @param status 状态筛选
* @param limit 数量限制
* @returns 用户执行任务列表
*/
@Get('executions/user/:userId')
async getUserExecutions(
@Param('userId') userId: string,
@Query('status') status?: string,
@Query('limit') limit: number = 20
) {
try {
const queryBuilder = this.executionRepository
.createQueryBuilder('execution')
.leftJoinAndSelect('execution.template', 'template')
.where('execution.userId = :userId', { userId })
.orderBy('execution.createdAt', 'DESC')
.limit(limit);
if (status) {
queryBuilder.andWhere('execution.status = :status', { status });
}
const executions = await queryBuilder.getMany();
return {
success: true,
data: executions.map(execution => ({
taskId: execution.id,
templateId: execution.templateId,
templateName: execution.template?.name,
templateCode: execution.template?.code,
type: execution.type,
status: execution.status,
progress: execution.progress,
inputImageUrl: execution.inputImageUrl,
outputUrl: execution.outputUrl,
thumbnailUrl: execution.thumbnailUrl,
creditCost: execution.creditCost,
startedAt: execution.startedAt,
completedAt: execution.completedAt,
executionDuration: execution.executionDuration,
createdAt: execution.createdAt
}))
};
} catch (error) {
throw new HttpException(
error.message || 'Failed to fetch user executions',
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
/**
* 创建新模板
* @param createTemplateDto 创建模板的数据传输对象

View File

@@ -1,5 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { User } from './user.entity';
import { UserEntity } from './user.entity';
import { PlatformType } from './platform-user.entity';
/**
@@ -30,7 +30,7 @@ export enum AdStatus {
* 用于积分奖励、数据统计和用户行为分析
*/
@Entity('ad_watches')
export class AdWatch {
export class AdWatchEntity {
/** 主键 - 广告观看记录的唯一标识符 */
@PrimaryGeneratedColumn('uuid')
id: string;
@@ -89,7 +89,7 @@ export class AdWatch {
updatedAt: Date;
/** 用户关系 - 与用户实体的多对一关系,级联删除 */
@ManyToOne(() => User, user => user.adWatches, { onDelete: 'CASCADE' })
@ManyToOne(() => UserEntity, user => user.adWatches, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'userId' })
user: User;
user: UserEntity;
}

View File

@@ -1,5 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { User } from './user.entity';
import { UserEntity } from './user.entity';
import { PlatformType } from './platform-user.entity';
/**
@@ -8,7 +8,7 @@ import { PlatformType } from './platform-user.entity';
* 提供灵活的JSON存储机制支持动态数据结构
*/
@Entity('extension_data')
export class ExtensionData {
export class ExtensionDataEntity {
/** 主键 - 扩展数据记录的唯一标识符 */
@PrimaryGeneratedColumn('uuid')
id: string;
@@ -53,7 +53,7 @@ export class ExtensionData {
updatedAt: Date;
/** 用户关系 - 与用户实体的多对一关系,级联删除 */
@ManyToOne(() => User, user => user.extensionData, { onDelete: 'CASCADE' })
@ManyToOne(() => UserEntity, user => user.extensionData, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'userId' })
user: User;
user: UserEntity;
}

View File

@@ -1,5 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn, Index } from 'typeorm';
import { User } from './user.entity';
import { UserEntity } from './user.entity';
/**
* 平台类型枚举
@@ -27,7 +27,7 @@ export enum PlatformType {
*/
@Entity('platform_users')
@Index(['platform', 'platformUserId'], { unique: true }) // 确保同一平台下的用户ID唯一
export class PlatformUser {
export class PlatformUserEntity {
/** 主键 - 平台用户记录的唯一标识符 */
@PrimaryGeneratedColumn('uuid')
id: string;
@@ -72,7 +72,7 @@ export class PlatformUser {
updatedAt: Date;
/** 用户关系 - 与统一用户实体的多对一关系,级联删除 */
@ManyToOne(() => User, user => user.platformUsers, { onDelete: 'CASCADE' })
@ManyToOne(() => UserEntity, user => user.platformUsers, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'userId' })
user: User;
user: UserEntity;
}

View File

@@ -1,5 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { User } from './user.entity';
import { UserEntity } from './user.entity';
import { PlatformType } from './platform-user.entity';
/**
@@ -32,7 +32,7 @@ export enum CreditSource {
* 支持多平台积分系统和业务关联
*/
@Entity('user_credits')
export class UserCredit {
export class UserCreditEntity {
/** 主键 - 积分记录的唯一标识符 */
@PrimaryGeneratedColumn('uuid')
id: string;
@@ -91,7 +91,7 @@ export class UserCredit {
updatedAt: Date;
/** 用户关系 - 与用户实体的多对一关系,级联删除 */
@ManyToOne(() => User, user => user.credits, { onDelete: 'CASCADE' })
@ManyToOne(() => UserEntity, user => user.credits, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'userId' })
user: User;
user: UserEntity;
}

View File

@@ -1,5 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { User } from './user.entity';
import { UserEntity } from './user.entity';
import { PlatformType } from './platform-user.entity';
/**
@@ -31,7 +31,7 @@ export enum SubscriptionStatus {
* 提供积分配额和特权功能管理
*/
@Entity('user_subscriptions')
export class UserSubscription {
export class UserSubscriptionEntity {
/** 主键 - 订阅记录的唯一标识符 */
@PrimaryGeneratedColumn('uuid')
id: string;
@@ -103,7 +103,7 @@ export class UserSubscription {
updatedAt: Date;
/** 用户关系 - 与用户实体的多对一关系,级联删除 */
@ManyToOne(() => User, user => user.subscriptions, { onDelete: 'CASCADE' })
@ManyToOne(() => UserEntity, user => user.subscriptions, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'userId' })
user: User;
user: UserEntity;
}

View File

@@ -1,10 +1,10 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
import { PlatformUser } from './platform-user.entity';
import { ExtensionData } from './extension-data.entity';
import { UserCredit } from './user-credit.entity';
import { PlatformUserEntity } from './platform-user.entity';
import { ExtensionDataEntity } from './extension-data.entity';
import { UserCreditEntity } from './user-credit.entity';
import { TemplateExecutionEntity } from './template-execution.entity';
import { UserSubscription } from './user-subscription.entity';
import { AdWatch } from './ad-watch.entity';
import { UserSubscriptionEntity } from './user-subscription.entity';
import { AdWatchEntity } from './ad-watch.entity';
/**
* 用户实体类
@@ -13,7 +13,7 @@ import { AdWatch } from './ad-watch.entity';
* 支持跨平台用户身份统一管理
*/
@Entity('users')
export class User {
export class UserEntity {
/** 主键 - 用户的内部唯一标识符 */
@PrimaryGeneratedColumn('uuid')
id: string;
@@ -51,26 +51,26 @@ export class User {
updatedAt: Date;
/** 平台用户列表 - 用户在各个小程序平台的身份信息和授权令牌 */
@OneToMany(() => PlatformUser, platformUser => platformUser.user)
platformUsers: PlatformUser[];
@OneToMany(() => PlatformUserEntity, platformUser => platformUser.user)
platformUsers: PlatformUserEntity[];
/** 扩展数据列表 - 用户在各平台的特有业务数据和第三方集成数据 */
@OneToMany(() => ExtensionData, extensionData => extensionData.user)
extensionData: ExtensionData[];
@OneToMany(() => ExtensionDataEntity, extensionData => extensionData.user)
extensionData: ExtensionDataEntity[];
/** 积分记录列表 - 用户的所有积分变动明细和交易流水 */
@OneToMany(() => UserCredit, userCredit => userCredit.user)
credits: UserCredit[];
@OneToMany(() => UserCreditEntity, userCredit => userCredit.user)
credits: UserCreditEntity[];
/** 模板执行列表 - 用户使用AI模板生成内容的历史记录 */
@OneToMany(() => TemplateExecutionEntity, execution => execution.userId)
templateExecutions: TemplateExecutionEntity[];
/** 订阅列表 - 用户的会员订阅服务信息和历史记录 */
@OneToMany(() => UserSubscription, userSubscription => userSubscription.user)
subscriptions: UserSubscription[];
@OneToMany(() => UserSubscriptionEntity, userSubscription => userSubscription.user)
subscriptions: UserSubscriptionEntity[];
/** 广告观看列表 - 用户观看广告的完整记录和奖励明细 */
@OneToMany(() => AdWatch, adWatch => adWatch.user)
adWatches: AdWatch[];
@OneToMany(() => AdWatchEntity, adWatch => adWatch.user)
adWatches: AdWatchEntity[];
}

View File

@@ -0,0 +1,191 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { Repository } from 'typeorm';
import { JwtService } from '@nestjs/jwt';
import { UserEntity } from '../../entities/user.entity';
import { PlatformUserEntity, PlatformType } from '../../entities/platform-user.entity';
import {
IPlatformAdapter,
PlatformLoginData,
PlatformRegisterData,
UserAuthResult,
PlatformUserInfo,
TokenRefreshResult,
UnifiedUserInfo
} from '../interfaces/platform.interface';
@Injectable()
export abstract class BaseAdapter implements IPlatformAdapter {
abstract platform: PlatformType;
constructor(
protected readonly httpService: HttpService,
protected readonly configService: ConfigService,
protected readonly userRepository: Repository<UserEntity>,
protected readonly platformUserRepository: Repository<PlatformUserEntity>,
protected readonly jwtService: JwtService,
) {}
/**
* 查找或创建统一用户
* 根据平台用户信息查找现有用户,如不存在则创建新用户
*/
async findOrCreateUnifiedUser(platformUserData: PlatformUserInfo): Promise<UserEntity> {
// 查找是否已存在平台用户绑定
const existingPlatformUser = await this.platformUserRepository.findOne({
where: {
platform: this.platform,
platformUserId: platformUserData.platformUserId,
},
relations: ['user'],
});
if (existingPlatformUser) {
// 更新平台用户信息
await this.updatePlatformUserInfo(existingPlatformUser, platformUserData);
return existingPlatformUser.user;
}
// 创建新的统一用户
const unifiedUser = await this.createUnifiedUser(platformUserData);
await this.createPlatformUser(unifiedUser.id, platformUserData);
return unifiedUser;
}
/**
* 创建统一用户记录
*/
protected async createUnifiedUser(platformData: PlatformUserInfo): Promise<UserEntity> {
const user = new UserEntity();
user.unifiedUserId = this.generateUnifiedUserId();
user.nickname = platformData.nickname;
user.avatarUrl = platformData.avatarUrl;
user.phone = platformData.phone;
user.email = platformData.email;
user.status = 1; // 正常状态
return this.userRepository.save(user);
}
/**
* 创建平台用户绑定记录
*/
protected async createPlatformUser(userId: string, platformData: PlatformUserInfo, authData?: any): Promise<PlatformUserEntity> {
const platformUser = new PlatformUserEntity();
platformUser.userId = userId;
platformUser.platform = this.platform;
platformUser.platformUserId = platformData.platformUserId;
platformUser.platformData = platformData;
if (authData) {
platformUser.accessToken = authData.access_token;
platformUser.refreshToken = authData.refresh_token;
platformUser.expiresAt = authData.expires_at ? new Date(authData.expires_at * 1000) : null;
}
return this.platformUserRepository.save(platformUser);
}
/**
* 更新平台用户信息
*/
protected async updatePlatformUserInfo(platformUser: PlatformUserEntity, newData: PlatformUserInfo): Promise<void> {
platformUser.platformData = { ...platformUser.platformData, ...newData };
await this.platformUserRepository.save(platformUser);
}
/**
* 更新平台用户认证数据
*/
protected async updatePlatformUserData(userId: string, authData: any, userInfo?: any): Promise<void> {
const platformUser = await this.platformUserRepository.findOne({
where: { userId, platform: this.platform }
});
if (platformUser) {
// 统一存储session_key到accessToken字段
platformUser.accessToken = authData.session_key;
platformUser.refreshToken = null; // 两平台都不支持refresh
platformUser.expiresAt = null; // session_key无固定过期时间
if (userInfo) {
platformUser.platformData = { ...platformUser.platformData, ...userInfo };
}
await this.platformUserRepository.save(platformUser);
}
}
/**
* 生成统一用户ID
*/
protected generateUnifiedUserId(): string {
return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 生成JWT令牌
*/
protected async generateTokens(user: UserEntity, platform: PlatformType): Promise<{
accessToken: string;
refreshToken: string;
expiresAt: Date;
}> {
const payload = {
userId: user.id,
unifiedUserId: user.unifiedUserId,
platform,
};
const accessToken = this.jwtService.sign(payload, { expiresIn: '24h' });
const refreshToken = this.jwtService.sign(payload, { expiresIn: '30d' });
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24小时后
return { accessToken, refreshToken, expiresAt };
}
/**
* 格式化统一用户信息
*/
protected formatUnifiedUserInfo(user: UserEntity): UnifiedUserInfo {
return {
id: user.id,
unifiedUserId: user.unifiedUserId,
nickname: user.nickname,
avatarUrl: user.avatarUrl,
phone: user.phone,
email: user.email,
status: user.status,
platforms: user.platformUsers?.map(pu => pu.platform) || [this.platform],
};
}
/**
* 统一错误处理
*/
protected handlePlatformError(error: any, platform: string): Error {
// 统一不同平台的错误格式
if (error.response?.data) {
const errorData = error.response.data;
// 微信格式: {errcode, errmsg}
if (errorData.errcode) {
return new Error(`${platform}错误[${errorData.errcode}]: ${errorData.errmsg}`);
}
// 抖音格式: {err_no, err_tips}
if (errorData.err_no !== undefined) {
return new Error(`${platform}错误[${errorData.err_no}]: ${errorData.err_tips}`);
}
}
return new Error(`${platform}API调用失败: ${error.message}`);
}
// 抽象方法 - 子类必须实现
abstract login(loginData: PlatformLoginData): Promise<UserAuthResult>;
abstract getUserInfo(token: string, platformUserId: string): Promise<PlatformUserInfo>;
abstract refreshToken(refreshToken: string): Promise<TokenRefreshResult>;
abstract register(registerData: PlatformRegisterData): Promise<UserAuthResult>;
abstract updateUserInfo(userId: string, updateData: Partial<PlatformUserInfo>): Promise<void>;
abstract validateToken(token: string): Promise<boolean>;
abstract revokeToken(token: string): Promise<void>;
}