feat: 实现跨平台用户身份统一和配置管理优化
- 修改 BaseAdapter.findOrCreateUnifiedUser 方法支持手机号去重 - 实现相同手机号用户在不同平台自动绑定到统一账号 - 优化数据库连接配置支持环境变量 - 修改 N8N 模板系统支持 ConfigService 依赖注入 - 统一 N8N Webhook URL 配置管理
This commit is contained in:
@@ -13,8 +13,7 @@ NODE_ENV=development
|
||||
PORT=3000
|
||||
|
||||
# N8N配置
|
||||
N8N_BASE_URL=http://localhost:5678
|
||||
N8N_API_KEY=your_n8n_api_key
|
||||
N8N_WEBHOOK_URL=https://n8n.bowongai.com/webhook/76f92dbf-785f-4add-96b5-a108174b7c14
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=your_jwt_secret_key_here
|
||||
|
||||
@@ -2,15 +2,16 @@ import { DataSource } from 'typeorm';
|
||||
|
||||
export const AppDataSource = new DataSource({
|
||||
type: 'mysql',
|
||||
host: 'mysql-6bc9094abd49-public.rds.volces.com',
|
||||
port: 53305,
|
||||
username: 'root',
|
||||
password: 'WsJWXfwFx0bq6DE2kmB6',
|
||||
database: 'nano_camera_miniapp',
|
||||
host: process.env.DB_HOST || 'mysql-6bc9094abd49-public.rds.volces.com',
|
||||
port: parseInt(process.env.DB_PORT || '53305'),
|
||||
username: process.env.DB_USERNAME || 'root',
|
||||
password: process.env.DB_PASSWORD || 'WsJWXfwFx0bq6DE2kmB6',
|
||||
database: process.env.DB_DATABASE || 'nano_camera_miniapp',
|
||||
entities: ['src/**/*.entity.ts'],
|
||||
migrations: ['src/migrations/*.ts'],
|
||||
synchronize: false,
|
||||
logging: process.env.NODE_ENV === 'development',
|
||||
synchronize: process.env.DB_SYNCHRONIZE === 'true',
|
||||
logging: process.env.DB_LOGGING === 'true' || process.env.NODE_ENV === 'development',
|
||||
migrationsRun: process.env.DB_MIGRATIONS_RUN === 'true',
|
||||
charset: 'utf8mb4',
|
||||
timezone: '+08:00',
|
||||
});
|
||||
@@ -33,11 +33,12 @@ export abstract class BaseAdapter implements IPlatformAdapter {
|
||||
/**
|
||||
* 查找或创建统一用户
|
||||
* 根据平台用户信息查找现有用户,如不存在则创建新用户
|
||||
* 支持跨平台用户身份统一:优先按手机号识别相同用户
|
||||
*/
|
||||
async findOrCreateUnifiedUser(
|
||||
platformUserData: PlatformUserInfo,
|
||||
): Promise<UserEntity> {
|
||||
// 查找是否已存在平台用户绑定
|
||||
// 1. 查找是否已存在平台用户绑定
|
||||
const existingPlatformUser = await this.platformUserRepository.findOne({
|
||||
where: {
|
||||
platform: this.platform,
|
||||
@@ -52,7 +53,28 @@ export abstract class BaseAdapter implements IPlatformAdapter {
|
||||
return existingPlatformUser.user;
|
||||
}
|
||||
|
||||
// 创建新的统一用户
|
||||
// 2. 按手机号查找现有统一用户,实现跨平台身份统一
|
||||
if (platformUserData.phone) {
|
||||
const existingUser = await this.userRepository.findOne({
|
||||
where: { phone: platformUserData.phone },
|
||||
relations: ['platformUsers'],
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
// 检查是否已绑定当前平台
|
||||
const existingBinding = existingUser.platformUsers?.find(
|
||||
(pu) => pu.platform === this.platform,
|
||||
);
|
||||
|
||||
if (!existingBinding) {
|
||||
// 绑定新平台到现有用户
|
||||
await this.createPlatformUser(existingUser.id, platformUserData);
|
||||
}
|
||||
return existingUser;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 创建新的统一用户和平台绑定
|
||||
const unifiedUser = await this.createUnifiedUser(platformUserData);
|
||||
await this.createPlatformUser(unifiedUser.id, platformUserData);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Repository } from 'typeorm';
|
||||
import {
|
||||
N8nTemplateEntity,
|
||||
@@ -25,6 +26,7 @@ export class N8nTemplateFactoryService {
|
||||
private templateRepository: Repository<N8nTemplateEntity>,
|
||||
@InjectRepository(TemplateExecutionEntity)
|
||||
private executionRepository: Repository<TemplateExecutionEntity>,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -38,6 +40,7 @@ export class N8nTemplateFactoryService {
|
||||
const instance = new DynamicN8nImageTemplate(
|
||||
templateId,
|
||||
this.templateRepository,
|
||||
this.configService,
|
||||
);
|
||||
return await instance.onInit();
|
||||
}
|
||||
@@ -53,6 +56,7 @@ export class N8nTemplateFactoryService {
|
||||
const instance = new DynamicN8nVideoTemplate(
|
||||
templateId,
|
||||
this.templateRepository,
|
||||
this.configService,
|
||||
);
|
||||
return await instance.onInit();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Repository } from 'typeorm';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
N8nTemplateEntity,
|
||||
TemplateType,
|
||||
@@ -18,8 +19,9 @@ export class DynamicN8nImageTemplate extends N8nImageGenerateTemplate {
|
||||
constructor(
|
||||
private templateId: number,
|
||||
private templateRepository: Repository<N8nTemplateEntity>,
|
||||
configService: ConfigService,
|
||||
) {
|
||||
super();
|
||||
super(configService);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,8 +100,9 @@ export class DynamicN8nVideoTemplate extends N8nVideoGenerateTemplate {
|
||||
constructor(
|
||||
private templateId: number,
|
||||
private templateRepository: Repository<N8nTemplateEntity>,
|
||||
configService: ConfigService,
|
||||
) {
|
||||
super();
|
||||
super(configService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
import { ImageGenerateTemplate, VideoGenerateTemplate } from './types';
|
||||
import axios from 'axios';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
export abstract class N8nImageGenerateTemplate extends ImageGenerateTemplate {
|
||||
abstract readonly imageModel: string;
|
||||
abstract readonly imagePrompt: string;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(imageUrl: string): Promise<string> {
|
||||
const webhookUrl = this.configService.get<string>(
|
||||
'N8N_WEBHOOK_URL',
|
||||
'https://n8n.bowongai.com/webhook/76f92dbf-785f-4add-96b5-a108174b7c14',
|
||||
);
|
||||
|
||||
return axios
|
||||
.request({
|
||||
url: `https://n8n.bowongai.com/webhook/76f92dbf-785f-4add-96b5-a108174b7c14`,
|
||||
url: webhookUrl,
|
||||
method: 'post',
|
||||
data: {
|
||||
workflow: '图生图',
|
||||
@@ -42,10 +53,20 @@ export abstract class N8nVideoGenerateTemplate extends VideoGenerateTemplate {
|
||||
abstract readonly videoPrompt: string;
|
||||
abstract readonly duration: number;
|
||||
abstract readonly aspectRatio: string;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
super();
|
||||
}
|
||||
|
||||
execute(imageUrl: string): Promise<string> {
|
||||
const webhookUrl = this.configService.get<string>(
|
||||
'N8N_WEBHOOK_URL',
|
||||
'https://n8n.bowongai.com/webhook/76f92dbf-785f-4add-96b5-a108174b7c14',
|
||||
);
|
||||
|
||||
return axios
|
||||
.request({
|
||||
url: `https://n8n.bowongai.com/webhook/76f92dbf-785f-4add-96b5-a108174b7c14`,
|
||||
url: webhookUrl,
|
||||
method: 'post',
|
||||
data: {
|
||||
workflow: '图生图+生视频',
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { N8nVideoGenerateTemplate } from '../n8nTemplate';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
// 人物手办模板
|
||||
export class CharacterFigurineTemplate extends N8nVideoGenerateTemplate {
|
||||
constructor(configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
readonly code = 'character_figurine_v1';
|
||||
readonly name = '人物手办';
|
||||
readonly description =
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { N8nVideoGenerateTemplate } from '../n8nTemplate';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
// cos真人模板
|
||||
export class CosplayRealPersonTemplate extends N8nVideoGenerateTemplate {
|
||||
constructor(configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
readonly code = 'cosplay_real_person_v1';
|
||||
readonly name = 'cos真人';
|
||||
readonly description =
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { N8nVideoGenerateTemplate } from '../n8nTemplate';
|
||||
|
||||
// 车库开门模板
|
||||
export class GarageOpeningTemplate extends N8nVideoGenerateTemplate {
|
||||
constructor(configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
readonly code = 'garage_opening_v1';
|
||||
readonly name = '车库开门';
|
||||
readonly description =
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { N8nVideoGenerateTemplate } from '../n8nTemplate';
|
||||
|
||||
// 日本杂志模板
|
||||
export class JapaneseMagazineTemplate extends N8nVideoGenerateTemplate {
|
||||
constructor(configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
readonly code = 'japanese_magazine_v1';
|
||||
readonly name = '日本杂志';
|
||||
readonly description =
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { N8nVideoGenerateTemplate } from '../n8nTemplate';
|
||||
|
||||
// 原子弹爆炸模板
|
||||
export class NuclearExplosionTemplate extends N8nVideoGenerateTemplate {
|
||||
constructor(configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
readonly code = 'nuclear_explosion_v1';
|
||||
readonly name = '原子弹爆炸';
|
||||
readonly description =
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { N8nVideoGenerateTemplate } from '../n8nTemplate';
|
||||
|
||||
// 宠物手办模板
|
||||
export class PetFigurineTemplate extends N8nVideoGenerateTemplate {
|
||||
constructor(configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
readonly code = 'pet_figurine_v1';
|
||||
readonly name = '宠物手办';
|
||||
readonly description =
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { N8nImageGenerateTemplate } from '../n8nTemplate';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
// 老照片修复上色模板
|
||||
export class PhotoRestoreTemplate extends N8nImageGenerateTemplate {
|
||||
constructor(configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
readonly code = 'photo_restore_v1';
|
||||
readonly name = '老照片修复上色';
|
||||
readonly description = '将黑白老照片修复并上色,让历史照片重现生机';
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { N8nImageGenerateTemplate } from '../n8nTemplate';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
// 闭眼照片修复
|
||||
export class OpenEyesTemplate extends N8nImageGenerateTemplate {
|
||||
constructor(configService: ConfigService) {
|
||||
super(configService);
|
||||
}
|
||||
readonly code = 'open_eyes_v1';
|
||||
readonly name = '闭眼照片修复';
|
||||
readonly description = '修复照片闭眼问题,让人物睁开眼睛';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { TemplateManager, Template } from './types';
|
||||
import {
|
||||
OpenEyesTemplate,
|
||||
@@ -15,7 +16,10 @@ import {
|
||||
export class TemplateService implements OnModuleInit {
|
||||
private readonly logger = new Logger(TemplateService.name);
|
||||
|
||||
constructor(private readonly templateManager: TemplateManager) {}
|
||||
constructor(
|
||||
private readonly templateManager: TemplateManager,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async onModuleInit() {
|
||||
// 启动时自动注册所有模板
|
||||
@@ -77,14 +81,14 @@ export class TemplateService implements OnModuleInit {
|
||||
|
||||
try {
|
||||
this.templateManager.registerTemplates([
|
||||
new PhotoRestoreTemplate(),
|
||||
new OpenEyesTemplate(),
|
||||
new CosplayRealPersonTemplate(),
|
||||
new PetFigurineTemplate(),
|
||||
new GarageOpeningTemplate(),
|
||||
new CharacterFigurineTemplate(),
|
||||
new NuclearExplosionTemplate(),
|
||||
new JapaneseMagazineTemplate(),
|
||||
new PhotoRestoreTemplate(this.configService),
|
||||
new OpenEyesTemplate(this.configService),
|
||||
new CosplayRealPersonTemplate(this.configService),
|
||||
new PetFigurineTemplate(this.configService),
|
||||
new GarageOpeningTemplate(this.configService),
|
||||
new CharacterFigurineTemplate(this.configService),
|
||||
new NuclearExplosionTemplate(this.configService),
|
||||
new JapaneseMagazineTemplate(this.configService),
|
||||
]);
|
||||
} catch (error) {
|
||||
this.logger.error('模板初始化失败:', error);
|
||||
|
||||
Reference in New Issue
Block a user