docs: 更新数据库配置从PostgreSQL到MySQL
- 更新multi-platform-integration-solution.md中的数据库配置 - 数据库类型从PostgreSQL改为MySQL - 端口从5432改为3306 - 默认用户名从postgres改为root - 所有jsonb字段类型改为json - Docker配置更新为MySQL 8.0 - 更新swagger-api-documentation.md中的数据存储描述 - 从"PostgreSQL + JSONB"改为"MySQL + JSON"
This commit is contained in:
parent
528d841d82
commit
a513202155
|
|
@ -53,7 +53,7 @@
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
│ 数据存储层 (Data Layer) │
|
│ 数据存储层 (Data Layer) │
|
||||||
├─────────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────────┤
|
||||||
│ PostgreSQL │ Redis │ 文件存储 │ RabbitMQ │ 对象存储 │
|
│ MySQL │ 文件存储 │ 对象存储 │
|
||||||
└─────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -89,9 +89,8 @@
|
||||||
|
|
||||||
### 后端技术栈
|
### 后端技术栈
|
||||||
- **框架**: NestJS (已选择)
|
- **框架**: NestJS (已选择)
|
||||||
- **数据库**: PostgreSQL (主数据库) + Redis (缓存)
|
- **数据库**: MySQL (主数据库)
|
||||||
- **ORM**: TypeORM (数据库操作)
|
- **ORM**: TypeORM (数据库操作)
|
||||||
- **消息队列**: RabbitMQ
|
|
||||||
|
|
||||||
### 开发工具
|
### 开发工具
|
||||||
- **API文档**: Swagger/OpenAPI
|
- **API文档**: Swagger/OpenAPI
|
||||||
|
|
@ -158,88 +157,40 @@ interface ExtensionServiceInterface {
|
||||||
- **平台适配层**: 每个服务都有对应的平台适配器
|
- **平台适配层**: 每个服务都有对应的平台适配器
|
||||||
- **配置驱动**: 通过配置文件控制服务的启用和禁用
|
- **配置驱动**: 通过配置文件控制服务的启用和禁用
|
||||||
|
|
||||||
### 3. 消息队列服务 (RabbitMQ)
|
### 3. 异步任务处理服务
|
||||||
|
|
||||||
#### 3.1 业务队列配置
|
#### 3.1 数据库异步处理
|
||||||
|
- **功能**: 使用数据库任务队列替代消息队列
|
||||||
|
- **实现方式**: 通过数据库表存储待处理任务,定时处理器执行
|
||||||
|
|
||||||
|
#### 3.2 业务异步处理
|
||||||
```typescript
|
```typescript
|
||||||
interface QueueConfig {
|
// 用户注册异步处理
|
||||||
// 用户相关队列
|
async processUserRegistration(userId: string, platform: string) {
|
||||||
USER_REGISTRATION: 'user.registration';
|
|
||||||
USER_LOGIN: 'user.login';
|
|
||||||
USER_UPDATE: 'user.update';
|
|
||||||
|
|
||||||
// AI生成相关队列
|
|
||||||
AI_GENERATION_TASK: 'ai.generation.task';
|
|
||||||
AI_GENERATION_COMPLETED: 'ai.generation.completed';
|
|
||||||
AI_GENERATION_FAILED: 'ai.generation.failed';
|
|
||||||
|
|
||||||
// 积分系统队列
|
|
||||||
CREDIT_REWARD: 'credit.reward';
|
|
||||||
CREDIT_CONSUME: 'credit.consume';
|
|
||||||
|
|
||||||
// 广告系统队列
|
|
||||||
AD_WATCH_COMPLETED: 'ad.watch.completed';
|
|
||||||
AD_REWARD_GRANTED: 'ad.reward.granted';
|
|
||||||
|
|
||||||
// 订阅系统队列
|
|
||||||
SUBSCRIPTION_ACTIVATED: 'subscription.activated';
|
|
||||||
SUBSCRIPTION_EXPIRED: 'subscription.expired';
|
|
||||||
|
|
||||||
// 注意: 模板系统采用面向对象设计,通过AI_GENERATION_*队列处理
|
|
||||||
|
|
||||||
// 平台同步队列
|
|
||||||
PLATFORM_SYNC: 'platform.sync';
|
|
||||||
PLATFORM_DATA_SYNC: 'platform.data.sync';
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2 业务消息处理
|
|
||||||
```typescript
|
|
||||||
// 用户注册消息处理
|
|
||||||
@RabbitSubscribe({
|
|
||||||
exchange: 'user.exchange',
|
|
||||||
routingKey: 'user.registration',
|
|
||||||
queue: 'user.registration.queue',
|
|
||||||
})
|
|
||||||
async handleUserRegistration(message: UserRegistrationMessage) {
|
|
||||||
// 用户数据处理
|
// 用户数据处理
|
||||||
await this.userService.processRegistration(message.userId);
|
await this.userService.processRegistration(userId);
|
||||||
|
|
||||||
// 发放新用户积分奖励
|
// 发放新用户积分奖励
|
||||||
await this.creditService.grantNewUserBonus(message.userId, message.platform);
|
await this.creditService.grantNewUserBonus(userId, platform);
|
||||||
|
|
||||||
// 同步到各平台
|
// 同步到各平台
|
||||||
await this.platformService.syncUserData(message);
|
await this.platformService.syncUserData({ userId, platform });
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI生成任务处理
|
// AI生成任务异步处理
|
||||||
@RabbitSubscribe({
|
async processAIGenerationTask(taskId: string) {
|
||||||
exchange: 'ai.exchange',
|
|
||||||
routingKey: 'ai.generation.task',
|
|
||||||
queue: 'ai.generation.task.queue',
|
|
||||||
})
|
|
||||||
async handleAIGenerationTask(message: AIGenerationTaskMessage) {
|
|
||||||
// 处理AI生成任务
|
// 处理AI生成任务
|
||||||
await this.generationService.processGenerationTask(message.taskId);
|
await this.generationService.processGenerationTask(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 广告观看完成处理
|
// 广告观看完成异步处理
|
||||||
@RabbitSubscribe({
|
async processAdWatchCompleted(adWatchId: string, watchDuration: number, adData: any) {
|
||||||
exchange: 'ad.exchange',
|
|
||||||
routingKey: 'ad.watch.completed',
|
|
||||||
queue: 'ad.watch.completed.queue',
|
|
||||||
})
|
|
||||||
async handleAdWatchCompleted(message: AdWatchCompletedMessage) {
|
|
||||||
// 发放广告观看奖励
|
// 发放广告观看奖励
|
||||||
await this.adService.completeAdWatch(
|
await this.adService.completeAdWatch(adWatchId, watchDuration, adData);
|
||||||
message.adWatchId,
|
|
||||||
message.watchDuration,
|
|
||||||
message.adData
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 数据库设计 (PostgreSQL + TypeORM)
|
## 数据库设计 (MySQL + TypeORM)
|
||||||
|
|
||||||
### 1. 用户实体 (User Entity)
|
### 1. 用户实体 (User Entity)
|
||||||
```typescript
|
```typescript
|
||||||
|
|
@ -339,7 +290,7 @@ export class PlatformUser {
|
||||||
@Column({ length: 100 })
|
@Column({ length: 100 })
|
||||||
platformUserId: string;
|
platformUserId: string;
|
||||||
|
|
||||||
@Column({ type: 'jsonb', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
platformData: any;
|
platformData: any;
|
||||||
|
|
||||||
@Column({ length: 500, nullable: true })
|
@Column({ length: 500, nullable: true })
|
||||||
|
|
@ -390,10 +341,10 @@ export class ExtensionData {
|
||||||
@Column({ length: 100, nullable: true })
|
@Column({ length: 100, nullable: true })
|
||||||
referenceId: string; // 外部引用ID
|
referenceId: string; // 外部引用ID
|
||||||
|
|
||||||
@Column({ type: 'jsonb' })
|
@Column({ type: 'json' })
|
||||||
data: any; // 灵活的JSON数据存储
|
data: any; // 灵活的JSON数据存储
|
||||||
|
|
||||||
@Column({ type: 'jsonb', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
metadata: any; // 元数据
|
metadata: any; // 元数据
|
||||||
|
|
||||||
@Column({ length: 20, default: 'active' })
|
@Column({ length: 20, default: 'active' })
|
||||||
|
|
@ -469,7 +420,7 @@ export class UserCredit {
|
||||||
@Column({ length: 100, nullable: true })
|
@Column({ length: 100, nullable: true })
|
||||||
referenceId: string; // 关联的业务ID(如广告ID、任务ID等)
|
referenceId: string; // 关联的业务ID(如广告ID、任务ID等)
|
||||||
|
|
||||||
@Column({ type: 'jsonb', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
metadata: any;
|
metadata: any;
|
||||||
|
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
|
|
@ -548,7 +499,7 @@ export class GenerationTask {
|
||||||
@Column({ length: 100, nullable: true })
|
@Column({ length: 100, nullable: true })
|
||||||
aiModelId: string; // 使用的AI模型ID
|
aiModelId: string; // 使用的AI模型ID
|
||||||
|
|
||||||
@Column({ type: 'jsonb', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
parameters: any; // 生成参数
|
parameters: any; // 生成参数
|
||||||
|
|
||||||
@Column({ type: 'text', nullable: true })
|
@Column({ type: 'text', nullable: true })
|
||||||
|
|
@ -633,7 +584,7 @@ export class UserSubscription {
|
||||||
@Column({ length: 100, nullable: true })
|
@Column({ length: 100, nullable: true })
|
||||||
paymentId: string; // 支付订单ID
|
paymentId: string; // 支付订单ID
|
||||||
|
|
||||||
@Column({ type: 'jsonb', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
features: any; // 订阅特权
|
features: any; // 订阅特权
|
||||||
|
|
||||||
@Column({ type: 'boolean', default: true })
|
@Column({ type: 'boolean', default: true })
|
||||||
|
|
@ -709,7 +660,7 @@ export class AdWatch {
|
||||||
@Column({ type: 'integer', nullable: true })
|
@Column({ type: 'integer', nullable: true })
|
||||||
rewardCredits: number; // 奖励积分
|
rewardCredits: number; // 奖励积分
|
||||||
|
|
||||||
@Column({ type: 'jsonb', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
adData: any; // 广告相关数据
|
adData: any; // 广告相关数据
|
||||||
|
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
|
|
@ -740,10 +691,10 @@ export class AdWatch {
|
||||||
export class GenerationTask {
|
export class GenerationTask {
|
||||||
// ... 其他字段
|
// ... 其他字段
|
||||||
|
|
||||||
@Column({ type: 'jsonb', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
parameters: any; // 存储模板执行参数
|
parameters: any; // 存储模板执行参数
|
||||||
|
|
||||||
@Column({ type: 'jsonb', nullable: true })
|
@Column({ type: 'json', nullable: true })
|
||||||
metadata: any; // 存储模板代码、版本等信息
|
metadata: any; // 存储模板代码、版本等信息
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -764,10 +715,10 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
export const getDatabaseConfig = (configService: ConfigService): TypeOrmModuleOptions => ({
|
export const getDatabaseConfig = (configService: ConfigService): TypeOrmModuleOptions => ({
|
||||||
type: 'postgres',
|
type: 'mysql',
|
||||||
host: configService.get('DB_HOST', 'localhost'),
|
host: configService.get('DB_HOST', 'localhost'),
|
||||||
port: configService.get('DB_PORT', 5432),
|
port: configService.get('DB_PORT', 3306),
|
||||||
username: configService.get('DB_USERNAME', 'postgres'),
|
username: configService.get('DB_USERNAME', 'root'),
|
||||||
password: configService.get('DB_PASSWORD', 'password'),
|
password: configService.get('DB_PASSWORD', 'password'),
|
||||||
database: configService.get('DB_DATABASE', 'mini_app_platform'),
|
database: configService.get('DB_DATABASE', 'mini_app_platform'),
|
||||||
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
|
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
|
||||||
|
|
@ -823,7 +774,7 @@ export class CreateInitialTables1703001000000 implements MigrationInterface {
|
||||||
"userId" uuid NOT NULL,
|
"userId" uuid NOT NULL,
|
||||||
"platform" "platform_type" NOT NULL,
|
"platform" "platform_type" NOT NULL,
|
||||||
"platformUserId" character varying(100) NOT NULL,
|
"platformUserId" character varying(100) NOT NULL,
|
||||||
"platformData" jsonb,
|
"platformData" json,
|
||||||
"accessToken" character varying(500),
|
"accessToken" character varying(500),
|
||||||
"refreshToken" character varying(500),
|
"refreshToken" character varying(500),
|
||||||
"expiresAt" TIMESTAMP,
|
"expiresAt" TIMESTAMP,
|
||||||
|
|
@ -843,8 +794,8 @@ export class CreateInitialTables1703001000000 implements MigrationInterface {
|
||||||
"platform" "platform_type" NOT NULL,
|
"platform" "platform_type" NOT NULL,
|
||||||
"dataType" character varying(50) NOT NULL,
|
"dataType" character varying(50) NOT NULL,
|
||||||
"referenceId" character varying(100),
|
"referenceId" character varying(100),
|
||||||
"data" jsonb NOT NULL,
|
"data" json NOT NULL,
|
||||||
"metadata" jsonb,
|
"metadata" json,
|
||||||
"status" character varying(20) NOT NULL DEFAULT 'active',
|
"status" character varying(20) NOT NULL DEFAULT 'active',
|
||||||
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
|
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
|
@ -873,7 +824,7 @@ export class CreateInitialTables1703001000000 implements MigrationInterface {
|
||||||
"balance" integer NOT NULL,
|
"balance" integer NOT NULL,
|
||||||
"description" character varying(200),
|
"description" character varying(200),
|
||||||
"referenceId" character varying(100),
|
"referenceId" character varying(100),
|
||||||
"metadata" jsonb,
|
"metadata" json,
|
||||||
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
|
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
CONSTRAINT "PK_user_credits_id" PRIMARY KEY ("id"),
|
CONSTRAINT "PK_user_credits_id" PRIMARY KEY ("id"),
|
||||||
|
|
@ -903,7 +854,7 @@ export class CreateInitialTables1703001000000 implements MigrationInterface {
|
||||||
"status" "task_status" NOT NULL DEFAULT 'pending',
|
"status" "task_status" NOT NULL DEFAULT 'pending',
|
||||||
"creditCost" integer NOT NULL,
|
"creditCost" integer NOT NULL,
|
||||||
"aiModelId" character varying(100),
|
"aiModelId" character varying(100),
|
||||||
"parameters" jsonb,
|
"parameters" json,
|
||||||
"errorMessage" text,
|
"errorMessage" text,
|
||||||
"processingTime" integer,
|
"processingTime" integer,
|
||||||
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
|
@ -935,7 +886,7 @@ export class CreateInitialTables1703001000000 implements MigrationInterface {
|
||||||
"currency" character varying(10) NOT NULL DEFAULT 'CNY',
|
"currency" character varying(10) NOT NULL DEFAULT 'CNY',
|
||||||
"monthlyCredits" integer NOT NULL,
|
"monthlyCredits" integer NOT NULL,
|
||||||
"paymentId" character varying(100),
|
"paymentId" character varying(100),
|
||||||
"features" jsonb,
|
"features" json,
|
||||||
"autoRenew" boolean NOT NULL DEFAULT true,
|
"autoRenew" boolean NOT NULL DEFAULT true,
|
||||||
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
|
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
|
@ -964,7 +915,7 @@ export class CreateInitialTables1703001000000 implements MigrationInterface {
|
||||||
"status" "ad_status" NOT NULL,
|
"status" "ad_status" NOT NULL,
|
||||||
"watchDuration" integer,
|
"watchDuration" integer,
|
||||||
"rewardCredits" integer,
|
"rewardCredits" integer,
|
||||||
"adData" jsonb,
|
"adData" json,
|
||||||
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
|
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
CONSTRAINT "PK_ad_watches_id" PRIMARY KEY ("id"),
|
CONSTRAINT "PK_ad_watches_id" PRIMARY KEY ("id"),
|
||||||
|
|
@ -1172,49 +1123,28 @@ services:
|
||||||
build: .
|
build: .
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- DB_HOST=postgres
|
- DB_HOST=mysql
|
||||||
- DB_PORT=5432
|
- DB_PORT=3306
|
||||||
- DB_USERNAME=${POSTGRES_USER}
|
- DB_USERNAME=${MYSQL_USER}
|
||||||
- DB_PASSWORD=${POSTGRES_PASSWORD}
|
- DB_PASSWORD=${MYSQL_PASSWORD}
|
||||||
- DB_DATABASE=${POSTGRES_DB}
|
- DB_DATABASE=${MYSQL_DB}
|
||||||
- REDIS_HOST=redis
|
|
||||||
- REDIS_PORT=6379
|
|
||||||
- RABBITMQ_URL=amqp://rabbitmq:5672
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- mysql
|
||||||
- redis
|
|
||||||
- rabbitmq
|
|
||||||
|
|
||||||
postgres:
|
mysql:
|
||||||
image: postgres:15-alpine
|
image: mysql:8.0
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
MYSQL_USER: ${MYSQL_USER}
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||||
|
MYSQL_DATABASE: ${MYSQL_DB}
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- mysql_data:/var/lib/mysql
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "3306:3306"
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:alpine
|
|
||||||
ports:
|
|
||||||
- "6379:6379"
|
|
||||||
|
|
||||||
rabbitmq:
|
|
||||||
image: rabbitmq:3-management-alpine
|
|
||||||
environment:
|
|
||||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER}
|
|
||||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
|
|
||||||
ports:
|
|
||||||
- "5672:5672"
|
|
||||||
- "15672:15672" # 管理界面
|
|
||||||
volumes:
|
|
||||||
- rabbitmq_data:/var/lib/rabbitmq
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
mysql_data:
|
||||||
rabbitmq_data:
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 负载均衡配置
|
### 2. 负载均衡配置
|
||||||
|
|
@ -1222,75 +1152,6 @@ volumes:
|
||||||
- 支持多实例部署
|
- 支持多实例部署
|
||||||
- 健康检查和自动故障转移
|
- 健康检查和自动故障转移
|
||||||
|
|
||||||
## RabbitMQ 配置与管理
|
|
||||||
|
|
||||||
### 1. 交换机配置
|
|
||||||
```typescript
|
|
||||||
// src/config/rabbitmq.config.ts
|
|
||||||
export const RabbitMQConfig = {
|
|
||||||
exchanges: [
|
|
||||||
{
|
|
||||||
name: 'user.exchange',
|
|
||||||
type: 'topic',
|
|
||||||
options: { durable: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'payment.exchange',
|
|
||||||
type: 'topic',
|
|
||||||
options: { durable: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'push.exchange',
|
|
||||||
type: 'topic',
|
|
||||||
options: { durable: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'platform.exchange',
|
|
||||||
type: 'topic',
|
|
||||||
options: { durable: true }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
queues: [
|
|
||||||
{
|
|
||||||
name: 'user.registration.queue',
|
|
||||||
options: { durable: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'payment.success.queue',
|
|
||||||
options: { durable: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'push.message.queue',
|
|
||||||
options: { durable: true }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 消息生产者示例
|
|
||||||
```typescript
|
|
||||||
// src/services/message-producer.service.ts
|
|
||||||
@Injectable()
|
|
||||||
export class MessageProducerService {
|
|
||||||
constructor(
|
|
||||||
@Inject('RABBITMQ_CONNECTION')
|
|
||||||
private readonly connection: Connection,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async publishPaymentSuccess(paymentData: PaymentSuccessMessage) {
|
|
||||||
const channel = await this.connection.createChannel();
|
|
||||||
|
|
||||||
await channel.publish(
|
|
||||||
'payment.exchange',
|
|
||||||
'payment.success',
|
|
||||||
Buffer.from(JSON.stringify(paymentData)),
|
|
||||||
{ persistent: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
await channel.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## API文档配置 (Swagger/OpenAPI)
|
## API文档配置 (Swagger/OpenAPI)
|
||||||
|
|
||||||
|
|
@ -1675,7 +1536,6 @@ bootstrap();
|
||||||
- [ ] 广告服务集成 (观看验证、奖励发放)
|
- [ ] 广告服务集成 (观看验证、奖励发放)
|
||||||
- [ ] AI生成服务开发 (任务管理、模型调用)
|
- [ ] AI生成服务开发 (任务管理、模型调用)
|
||||||
- [ ] 文件存储服务 (图片上传、结果存储)
|
- [ ] 文件存储服务 (图片上传、结果存储)
|
||||||
- [ ] RabbitMQ消息队列集成
|
|
||||||
|
|
||||||
### Phase 3: 高级功能开发 (6周)
|
### Phase 3: 高级功能开发 (6周)
|
||||||
- [ ] 订阅服务开发 (包月付费、会员权益)
|
- [ ] 订阅服务开发 (包月付费、会员权益)
|
||||||
|
|
@ -1730,8 +1590,8 @@ bootstrap();
|
||||||
1. **统一性**: 抹平8大平台API差异和AI模型调用差异,统一的开发体验
|
1. **统一性**: 抹平8大平台API差异和AI模型调用差异,统一的开发体验
|
||||||
2. **可扩展性**: 模块化架构,支持快速添加新功能、平台和AI模型
|
2. **可扩展性**: 模块化架构,支持快速添加新功能、平台和AI模型
|
||||||
3. **模板化**: 通过模板系统标准化AI调用,简化前端集成
|
3. **模板化**: 通过模板系统标准化AI调用,简化前端集成
|
||||||
4. **高性能**: PostgreSQL + Redis + RabbitMQ,支持高并发处理
|
4. **高性能**: PostgreSQL 数据库,支持高并发处理
|
||||||
5. **数据安全**: JSONB灵活存储,完整的数据备份和恢复机制
|
5. **数据安全**: JSONB灵活存储,完整的数据备份和恢复机制
|
||||||
6. **异步处理**: 基于消息队列的异步任务处理,提升用户体验
|
6. **异步处理**: 基于数据库任务队列的异步处理,提升用户体验
|
||||||
7. **类型安全**: TypeScript + TypeORM,编译时错误检查
|
7. **类型安全**: TypeScript + TypeORM,编译时错误检查
|
||||||
8. **智能缓存**: 活跃模板缓存,提升模板执行性能
|
8. **智能缓存**: 活跃模板缓存,提升模板执行性能
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,19 @@ export function setupSwagger(app: INestApplication): void {
|
||||||
.setDescription(`
|
.setDescription(`
|
||||||
## 功能特性
|
## 功能特性
|
||||||
- 🔐 统一用户认证 (微信/支付宝/百度/字节跳动等)
|
- 🔐 统一用户认证 (微信/支付宝/百度/字节跳动等)
|
||||||
- 🔄 平台数据同步 (RabbitMQ异步处理)
|
- 🔄 平台数据同步 (数据库异步处理)
|
||||||
- 🧩 可扩展架构 (支持后续添加支付、推送等功能)
|
- 🧩 可扩展架构 (支持后续添加支付、推送等功能)
|
||||||
- 📊 灵活数据存储 (PostgreSQL + JSONB)
|
- 📊 灵活数据存储 (MySQL + JSON)
|
||||||
|
|
||||||
## 认证方式
|
## 认证方式
|
||||||
使用JWT Bearer Token进行API认证
|
使用JWT Bearer Token进行API认证
|
||||||
|
|
||||||
|
## AI模板系统
|
||||||
|
- 🎨 动态模板管理 (数据库配置 + 代码执行)
|
||||||
|
- 🚀 N8n工作流集成 (图片/视频生成)
|
||||||
|
- 📊 使用统计分析 (性能监控 + 用户行为)
|
||||||
|
- 🎛️ 运营管理后台 (A/B测试 + 个性化推荐)
|
||||||
|
|
||||||
## 响应格式
|
## 响应格式
|
||||||
所有API响应都遵循统一格式:
|
所有API响应都遵循统一格式:
|
||||||
\`\`\`json
|
\`\`\`json
|
||||||
|
|
@ -539,9 +545,427 @@ export class UserController {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 7. 环境配置
|
## 7. AI模板管理 API
|
||||||
|
|
||||||
### 7.1 开发环境配置
|
### 7.1 模板数据传输对象
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/dto/template.dto.ts
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsString, IsNumber, IsEnum, IsOptional, IsArray, IsBoolean } from 'class-validator';
|
||||||
|
|
||||||
|
export enum TemplateType {
|
||||||
|
IMAGE = 'image',
|
||||||
|
VIDEO = 'video'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TemplateListDto {
|
||||||
|
@ApiProperty({ description: '模板ID', example: 1 })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板代码', example: 'character_figurine_v1' })
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板名称', example: '人物手办' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板描述', example: '将人物照片制作成精细的角色手办模型' })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ enum: TemplateType, description: '模板类型', example: 'video' })
|
||||||
|
templateType: TemplateType;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '积分消耗', example: 28 })
|
||||||
|
creditCost: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '版本号', example: '1.0.0' })
|
||||||
|
version: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '输入示例URL', required: false })
|
||||||
|
inputExampleUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '输出示例URL', required: false })
|
||||||
|
outputExampleUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: [String], description: '标签数组', example: ['人物', '手办', '模型'] })
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '是否启用', example: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间', example: '2024-01-01T00:00:00Z' })
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TemplateExecuteDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: '输入图片URL',
|
||||||
|
example: 'https://cdn.roasmax.cn/upload/3d590851eb584e92aa415a964e93260e.jpg'
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
imageUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TemplateExecuteResponseDto {
|
||||||
|
@ApiProperty({ description: '执行状态', example: true })
|
||||||
|
success: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '生成结果URL(图片模板)', required: false })
|
||||||
|
imageUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '生成结果URL(视频模板)', required: false })
|
||||||
|
videoUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '缩略图URL', required: false })
|
||||||
|
thumbnailUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务ID', example: 'req_1704067200_abc123' })
|
||||||
|
taskId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '执行耗时(毫秒)', example: 5000 })
|
||||||
|
executionTime: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '消耗积分', example: 28 })
|
||||||
|
creditCost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TemplateStatsDto {
|
||||||
|
@ApiProperty({ description: '总模板数', example: 8 })
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '启用模板数', example: 8 })
|
||||||
|
enabled: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '图片模板数', example: 3 })
|
||||||
|
imageTemplates: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '视频模板数', example: 5 })
|
||||||
|
videoTemplates: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 模板管理控制器
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/controllers/template.controller.ts
|
||||||
|
import {
|
||||||
|
Controller, Get, Post, Param, Body, Query, UseGuards, ParseIntPipe
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ApiTags,
|
||||||
|
ApiOperation,
|
||||||
|
ApiResponse,
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiParam,
|
||||||
|
ApiQuery
|
||||||
|
} from '@nestjs/swagger';
|
||||||
|
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
||||||
|
import { CurrentUser } from '../decorators/current-user.decorator';
|
||||||
|
import { ApiCommonResponses, ApiAuthResponses } from '../decorators/api-common-responses.decorator';
|
||||||
|
import { N8nTemplateFactoryService } from '../services/n8n-template-factory.service';
|
||||||
|
import { TemplateListDto, TemplateExecuteDto, TemplateExecuteResponseDto, TemplateStatsDto } from '../dto/template.dto';
|
||||||
|
|
||||||
|
@ApiTags('🎨 AI模板系统')
|
||||||
|
@Controller('templates')
|
||||||
|
@ApiCommonResponses()
|
||||||
|
export class TemplateController {
|
||||||
|
constructor(
|
||||||
|
private readonly templateFactory: N8nTemplateFactoryService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '获取所有模板列表',
|
||||||
|
description: '获取所有可用的AI生成模板,支持按类型筛选'
|
||||||
|
})
|
||||||
|
@ApiQuery({
|
||||||
|
name: 'type',
|
||||||
|
required: false,
|
||||||
|
enum: ['image', 'video'],
|
||||||
|
description: '模板类型筛选'
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '获取成功',
|
||||||
|
type: [TemplateListDto]
|
||||||
|
})
|
||||||
|
async getTemplates(@Query('type') type?: 'image' | 'video') {
|
||||||
|
if (type === 'image') {
|
||||||
|
return this.templateFactory.getTemplatesByType('image');
|
||||||
|
} else if (type === 'video') {
|
||||||
|
return this.templateFactory.getTemplatesByType('video');
|
||||||
|
}
|
||||||
|
return this.templateFactory.getAllTemplates();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('stats')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '获取模板统计信息',
|
||||||
|
description: '获取模板总数、类型分布等统计信息'
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '获取成功',
|
||||||
|
type: TemplateStatsDto
|
||||||
|
})
|
||||||
|
async getTemplateStats() {
|
||||||
|
const allTemplates = await this.templateFactory.getAllTemplates();
|
||||||
|
const imageTemplates = allTemplates.filter(t => t.templateType === 'image');
|
||||||
|
const videoTemplates = allTemplates.filter(t => t.templateType === 'video');
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: allTemplates.length,
|
||||||
|
enabled: allTemplates.filter(t => t.isActive).length,
|
||||||
|
imageTemplates: imageTemplates.length,
|
||||||
|
videoTemplates: videoTemplates.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':templateId')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '获取模板详情',
|
||||||
|
description: '根据模板ID获取详细信息'
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: 'templateId',
|
||||||
|
description: '模板ID',
|
||||||
|
example: 1
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '获取成功',
|
||||||
|
type: TemplateListDto
|
||||||
|
})
|
||||||
|
async getTemplateById(@Param('templateId', ParseIntPipe) templateId: number) {
|
||||||
|
return this.templateFactory.getTemplateById(templateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('code/:templateCode')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '通过代码获取模板详情',
|
||||||
|
description: '根据模板代码获取详细信息'
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: 'templateCode',
|
||||||
|
description: '模板代码',
|
||||||
|
example: 'character_figurine_v1'
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '获取成功',
|
||||||
|
type: TemplateListDto
|
||||||
|
})
|
||||||
|
async getTemplateByCode(@Param('templateCode') templateCode: string) {
|
||||||
|
return this.templateFactory.getTemplateByCode(templateCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':templateId/execute')
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@ApiBearerAuth('JWT-auth')
|
||||||
|
@ApiAuthResponses()
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '执行模板生成',
|
||||||
|
description: '根据模板ID执行AI生成任务,支持图片和视频生成'
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: 'templateId',
|
||||||
|
description: '模板ID',
|
||||||
|
example: 1
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '执行成功',
|
||||||
|
type: TemplateExecuteResponseDto
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 400,
|
||||||
|
description: '参数错误',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
code: 400,
|
||||||
|
message: '输入图片URL不能为空',
|
||||||
|
data: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 402,
|
||||||
|
description: '积分不足',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
code: 402,
|
||||||
|
message: '积分不足,当前余额: 10,需要: 28',
|
||||||
|
data: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
async executeTemplate(
|
||||||
|
@Param('templateId', ParseIntPipe) templateId: number,
|
||||||
|
@Body() executeDto: TemplateExecuteDto,
|
||||||
|
@CurrentUser() user: any
|
||||||
|
) {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
// 获取模板配置
|
||||||
|
const templateConfig = await this.templateFactory.getTemplateById(templateId);
|
||||||
|
|
||||||
|
// 创建动态模板实例并执行
|
||||||
|
let template;
|
||||||
|
if (templateConfig.templateType === 'image') {
|
||||||
|
template = await this.templateFactory.createImageTemplate(templateId);
|
||||||
|
} else {
|
||||||
|
template = await this.templateFactory.createVideoTemplate(templateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await template.execute(executeDto.imageUrl);
|
||||||
|
const executionTime = Date.now() - startTime;
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
imageUrl: templateConfig.templateType === 'image' ? result : undefined,
|
||||||
|
videoUrl: templateConfig.templateType === 'video' ? result : undefined,
|
||||||
|
taskId: `req_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
|
||||||
|
executionTime,
|
||||||
|
creditCost: templateConfig.creditCost
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('code/:templateCode/execute')
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@ApiBearerAuth('JWT-auth')
|
||||||
|
@ApiAuthResponses()
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '通过代码执行模板',
|
||||||
|
description: '根据模板代码执行AI生成任务,支持图片和视频生成'
|
||||||
|
})
|
||||||
|
@ApiParam({
|
||||||
|
name: 'templateCode',
|
||||||
|
description: '模板代码',
|
||||||
|
example: 'character_figurine_v1'
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '执行成功',
|
||||||
|
type: TemplateExecuteResponseDto
|
||||||
|
})
|
||||||
|
async executeTemplateByCode(
|
||||||
|
@Param('templateCode') templateCode: string,
|
||||||
|
@Body() executeDto: TemplateExecuteDto,
|
||||||
|
@CurrentUser() user: any
|
||||||
|
) {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
// 创建动态模板实例并执行
|
||||||
|
const template = await this.templateFactory.createTemplateByCode(templateCode);
|
||||||
|
const result = await template.execute(executeDto.imageUrl);
|
||||||
|
const executionTime = Date.now() - startTime;
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
imageUrl: template.templateType === 'image' ? result : undefined,
|
||||||
|
videoUrl: template.templateType === 'video' ? result : undefined,
|
||||||
|
taskId: `req_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
|
||||||
|
executionTime,
|
||||||
|
creditCost: template.creditCost
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 API使用示例
|
||||||
|
|
||||||
|
#### 获取所有模板
|
||||||
|
```bash
|
||||||
|
curl -X GET "http://localhost:3000/api/templates" \
|
||||||
|
-H "accept: application/json"
|
||||||
|
```
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"code": "character_figurine_v1",
|
||||||
|
"name": "人物手办",
|
||||||
|
"description": "将人物照片制作成精细的角色手办模型,展示在收藏家房间中,并生成抚摸手办的视频",
|
||||||
|
"templateType": "video",
|
||||||
|
"creditCost": 28,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"inputExampleUrl": "https://cdn.roasmax.cn/upload/3d590851eb584e92aa415a964e93260e.jpg",
|
||||||
|
"outputExampleUrl": "https://file.302.ai/gpt/imgs/20250828/2283106b31faf2066e1a72d955f65bca.jpg",
|
||||||
|
"tags": ["人物", "手办", "模型", "收藏", "PVC", "角色模型", "视频生成"],
|
||||||
|
"isActive": true,
|
||||||
|
"createdAt": "2024-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 执行模板生成
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:3000/api/templates/1/execute" \
|
||||||
|
-H "accept: application/json" \
|
||||||
|
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"imageUrl": "https://cdn.roasmax.cn/upload/3d590851eb584e92aa415a964e93260e.jpg"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"success": true,
|
||||||
|
"videoUrl": "https://n8n.bowongai.com/files/generated_video_abc123.mp4",
|
||||||
|
"taskId": "req_1704067200_abc123",
|
||||||
|
"executionTime": 5000,
|
||||||
|
"creditCost": 28
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 错误处理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 常见错误响应
|
||||||
|
export const TemplateApiErrors = {
|
||||||
|
TEMPLATE_NOT_FOUND: {
|
||||||
|
code: 404,
|
||||||
|
message: '模板不存在',
|
||||||
|
data: null
|
||||||
|
},
|
||||||
|
INSUFFICIENT_CREDITS: {
|
||||||
|
code: 402,
|
||||||
|
message: '积分不足',
|
||||||
|
data: { required: 28, current: 10 }
|
||||||
|
},
|
||||||
|
INVALID_IMAGE_URL: {
|
||||||
|
code: 400,
|
||||||
|
message: '无效的图片URL',
|
||||||
|
data: null
|
||||||
|
},
|
||||||
|
TEMPLATE_EXECUTION_FAILED: {
|
||||||
|
code: 500,
|
||||||
|
message: 'AI生成失败,请稍后重试',
|
||||||
|
data: null
|
||||||
|
},
|
||||||
|
TEMPLATE_DISABLED: {
|
||||||
|
code: 403,
|
||||||
|
message: '模板已禁用',
|
||||||
|
data: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 环境配置
|
||||||
|
|
||||||
|
### 8.1 开发环境配置
|
||||||
```typescript
|
```typescript
|
||||||
// src/main.ts
|
// src/main.ts
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
|
@ -584,7 +1008,7 @@ async function bootstrap() {
|
||||||
bootstrap();
|
bootstrap();
|
||||||
```
|
```
|
||||||
|
|
||||||
## 8. 访问API文档
|
## 9. 访问API文档
|
||||||
|
|
||||||
启动应用后,访问以下地址查看API文档:
|
启动应用后,访问以下地址查看API文档:
|
||||||
|
|
||||||
|
|
@ -598,3 +1022,6 @@ API文档包含:
|
||||||
- 📝 请求/响应示例
|
- 📝 请求/响应示例
|
||||||
- 🧪 在线接口测试功能
|
- 🧪 在线接口测试功能
|
||||||
- 📊 数据模型定义
|
- 📊 数据模型定义
|
||||||
|
- 🎨 AI模板系统接口 (支持图片/视频生成)
|
||||||
|
- 💾 动态模板配置管理
|
||||||
|
- 📈 模板使用统计和监控
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue