feat: 实现跨平台用户身份统一和配置管理优化

- 修改 BaseAdapter.findOrCreateUnifiedUser 方法支持手机号去重
- 实现相同手机号用户在不同平台自动绑定到统一账号
- 优化数据库连接配置支持环境变量
- 修改 N8N 模板系统支持 ConfigService 依赖注入
- 统一 N8N Webhook URL 配置管理
This commit is contained in:
imeepos
2025-09-04 17:24:32 +08:00
parent 14f4c450b5
commit b750aede6a
15 changed files with 114 additions and 24 deletions

View File

@@ -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

View File

@@ -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',
});

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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);
}
/**

View File

@@ -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: '图生图+生视频',

View File

@@ -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 =

View File

@@ -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 =

View File

@@ -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 =

View File

@@ -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 =

View File

@@ -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 =

View File

@@ -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 =

View File

@@ -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 = '将黑白老照片修复并上色,让历史照片重现生机';

View File

@@ -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 = '修复照片闭眼问题,让人物睁开眼睛';

View File

@@ -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);