feat: 重构多语言模块,符合NestJS规范

- 重构LanguageUtils为LanguageService,实现ILanguageService接口
- 移除自定义验证管道和装饰器,使用标准NestJS验证
- 集成框架ValidatorService进行业务验证
- 简化目录结构,移除不必要的子目录
- 支持模块化语言包加载(common、user、order等)
- 统一API响应格式(code、msg、data、timestamp)
- 添加ValidationExceptionFilter处理多语言验证错误
- 完善多语言示例和文档
This commit is contained in:
wanwu
2025-10-06 10:56:59 +08:00
parent 8da4047110
commit b1e16be25d
284 changed files with 13098 additions and 32731 deletions

View File

@@ -0,0 +1,547 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const BusinessLogicConverter = require('./business-logic-converter');
/**
* ⚙️ 服务生成器
* 专门负责生成和更新NestJS服务
*/
class ServiceGenerator {
constructor() {
this.config = {
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud',
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest/src/core',
discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools/php-discovery-result.json'
};
this.discoveryData = null;
this.converter = new BusinessLogicConverter();
this.stats = {
servicesCreated: 0,
servicesUpdated: 0,
methodsProcessed: 0,
errors: 0
};
}
/**
* 运行服务生成
*/
async run() {
console.log('⚙️ 启动服务生成器...');
try {
// 加载发现数据
await this.loadDiscoveryData();
// 生成服务
await this.generateServices();
// 更新服务为真实业务逻辑
await this.updateAllServicesWithRealLogic();
// 生成统计报告
this.generateStatsReport();
} catch (error) {
console.error('❌ 服务生成过程中发生错误:', error.message);
this.stats.errors++;
throw error;
}
}
/**
* 加载PHP文件发现结果
*/
async loadDiscoveryData() {
try {
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf-8');
this.discoveryData = JSON.parse(data);
console.log(' ✅ 成功加载PHP文件发现结果');
} catch (error) {
console.error(' ❌ 加载发现数据失败:', error.message);
throw error;
}
}
/**
* 生成服务
*/
async generateServices() {
console.log(' 🔨 生成服务文件...');
// 检查是否有服务数据
if (!this.discoveryData.services || Object.keys(this.discoveryData.services).length === 0) {
console.log(' ⚠️ 未发现PHP服务跳过生成');
return;
}
let processedCount = 0;
// 服务数据结构是按层级分组的,需要遍历所有层级
for (const [layerName, services] of Object.entries(this.discoveryData.services)) {
console.log(` 📁 处理服务层级: ${layerName}, 服务数量: ${Object.keys(services).length}`);
for (const [serviceName, serviceInfo] of Object.entries(services)) {
console.log(` ⚙️ 处理服务: ${serviceName}`);
try {
const correctModuleName = this.extractModuleNameFromServicePath(serviceInfo.filePath);
const layer = this.extractLayerFromServicePath(serviceInfo.filePath);
// 检查PHP项目是否有对应的服务目录
if (!this.hasPHPServices(correctModuleName, layer)) {
console.log(` ⚠️ 模块 ${correctModuleName} 在PHP项目中无${layer}服务,跳过`);
continue;
}
await this.createService(correctModuleName, serviceName, serviceInfo, layer);
processedCount++;
console.log(` ✅ 成功创建服务: ${correctModuleName}/${serviceName}`);
} catch (error) {
console.error(` ❌ 创建服务失败 ${serviceName}:`, error.message);
this.stats.errors++;
}
}
}
this.stats.servicesCreated = processedCount;
console.log(` ✅ 创建了 ${this.stats.servicesCreated} 个服务`);
}
/**
* 更新所有服务为真实业务逻辑
*/
async updateAllServicesWithRealLogic() {
console.log(' 🔨 更新服务为真实业务逻辑...');
let processedCount = 0;
// 服务数据结构是按层级分组的,需要遍历所有层级
for (const [layerName, services] of Object.entries(this.discoveryData.services)) {
console.log(` 📁 处理服务层级: ${layerName}, 服务数量: ${Object.keys(services).length}`);
for (const [serviceName, serviceInfo] of Object.entries(services)) {
console.log(` ⚙️ 处理服务: ${serviceName}`);
try {
const correctModuleName = this.extractModuleNameFromServicePath(serviceInfo.filePath);
const layer = this.extractLayerFromServicePath(serviceInfo.filePath);
await this.updateServiceWithRealLogic(correctModuleName, serviceName, serviceInfo, layer);
processedCount++;
console.log(` ✅ 成功更新服务: ${correctModuleName}/${serviceName}`);
} catch (error) {
console.error(` ❌ 更新服务失败 ${serviceName}:`, error.message);
this.stats.errors++;
}
}
}
this.stats.servicesUpdated = processedCount;
console.log(` ✅ 更新了 ${this.stats.servicesUpdated} 个服务`);
}
/**
* 创建服务
*/
async createService(moduleName, serviceName, serviceInfo, layer) {
// 先去掉层级后缀再去掉Service后缀
const cleanServiceName = serviceName.replace(/_(admin|api|core)$/, '');
const baseName = cleanServiceName.endsWith('Service') ? cleanServiceName.slice(0, -7) : cleanServiceName;
const servicePath = path.join(
this.config.nestjsBasePath,
moduleName,
'services',
layer,
`${this.toKebabCase(baseName)}.service.ts`
);
// 确保目录存在
const serviceDir = path.dirname(servicePath);
if (!fs.existsSync(serviceDir)) {
fs.mkdirSync(serviceDir, { recursive: true });
}
// 检查是否有对应的PHP服务文件
// 从服务名中提取基础类名去掉_layer后缀
const baseServiceName = serviceName.replace(/_(admin|api|core)$/, '');
const phpServicePath = path.join(this.config.phpBasePath, 'app/service', layer, moduleName, `${baseServiceName}.php`);
if (!fs.existsSync(phpServicePath)) {
console.log(` ❌ 未找到PHP服务文件跳过生成: ${phpServicePath}`);
return;
}
// 生成基础服务内容
const serviceContent = this.generateBasicServiceContent(moduleName, serviceName, layer);
// 写入文件
fs.writeFileSync(servicePath, serviceContent);
console.log(` ✅ 创建服务: ${moduleName}/${layer}/${this.toKebabCase(baseName)}.service.ts`);
this.stats.servicesCreated++;
}
/**
* 更新服务为真实逻辑
*/
async updateServiceWithRealLogic(moduleName, serviceName, serviceInfo, layer) {
// 先去掉层级后缀再去掉Service后缀
const cleanServiceName = serviceName.replace(/_(admin|api|core)$/, '');
const baseName = cleanServiceName.endsWith('Service') ? cleanServiceName.slice(0, -7) : cleanServiceName;
const servicePath = path.join(
this.config.nestjsBasePath,
moduleName,
'services',
layer,
`${this.toKebabCase(baseName)}.service.ts`
);
if (!fs.existsSync(servicePath)) {
console.log(` ⚠️ 服务文件不存在: ${servicePath}`);
return;
}
try {
// 读取PHP服务文件
const phpServicePath = serviceInfo.filePath;
const phpContent = fs.readFileSync(phpServicePath, 'utf-8');
// 提取PHP方法
const phpMethods = this.converter.extractPHPMethods(phpContent);
if (phpMethods.length === 0) {
console.log(` ⚠️ 未找到PHP方法: ${serviceName}`);
return;
}
console.log(` 📝 找到 ${phpMethods.length} 个PHP方法`);
// 生成NestJS服务内容
const nestjsContent = this.generateRealServiceContent(moduleName, serviceName, layer, phpMethods);
// 写入文件
fs.writeFileSync(servicePath, nestjsContent);
console.log(` ✅ 更新服务: ${moduleName}/${layer}/${this.toKebabCase(baseName)}.service.ts`);
this.stats.methodsProcessed += phpMethods.length;
} catch (error) {
console.log(` ❌ 无法更新服务 ${serviceName}: ${error.message}`);
this.stats.errors++;
}
}
/**
* 生成基础服务内容
*/
generateBasicServiceContent(moduleName, serviceName, layer) {
// 先去掉层级后缀再去掉Service后缀
const cleanServiceName = serviceName.replace(/_(admin|api|core)$/, '');
const baseName = cleanServiceName.endsWith('Service') ? cleanServiceName.slice(0, -7) : cleanServiceName;
// 正确的命名规范服务类名与PHP/Java保持一致
let className = `${baseName}Service`;
if (layer === 'core') {
// Core层服务需要Core前缀
className = baseName.startsWith('Core') ? `${baseName}Service` : `Core${baseName}Service`;
} else {
// admin和api层直接使用业务名称
className = `${baseName}Service`;
}
// 获取基础设施导入
const infrastructureImports = this.getInfrastructureImports();
return `import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { BaseService } from '@wwjCommon/base/base.service';
import { CacheService } from '@wwjCommon/cache/cache.service';
import { LoggingService } from '@wwjCommon/logging/logging.service';
import { UploadService } from '@wwjVendor/upload/upload.service';
import { PayService } from '@wwjVendor/pay/pay.service';
import { SmsService } from '@wwjVendor/sms/sms.service';
import { NoticeService } from '@wwjVendor/notice/notice.service';
/**
* ${className} - ${layer}层服务
* 继承BaseService使用TypeORM Repository模式
* 对应 Java: @Service + @Autowired
* 对应 PHP: extends BaseCoreService
*
* 使用Common层基础设施
* - CacheService (缓存对应PHP Cache::)
* - ConfigService (配置读取对应PHP Config::get)
* - LoggingService (日志记录对应PHP Log::write)
*
* 使用Vendor层业务服务
* - UploadService (文件上传对应PHP Storage/UploadLoader)
* - PayService (支付服务对应PHP PayLoader)
* - SmsService (短信服务对应PHP SmsLoader)
* - NoticeService (通知服务对应PHP NoticeService)
*/
@Injectable()
export class ${className} extends BaseService<any> {
private readonly logger = new Logger(${className}.name);
constructor(
@InjectRepository(Object)
protected readonly repository: Repository<any>,
private readonly cacheService: CacheService,
private readonly configService: ConfigService,
private readonly loggingService: LoggingService,
private readonly uploadService: UploadService,
private readonly payService: PayService,
private readonly smsService: SmsService,
private readonly noticeService: NoticeService,
) {
super(repository);
}
// 服务方法需要基于真实PHP服务类解析
// 禁止假设方法所有方法必须来自PHP源码
// 可使用注入的服务configService, loggingService, uploadService, payService, smsService, noticeService
}
`;
}
/**
* 获取基础设施导入
*/
getInfrastructureImports() {
return `import { ConfigService } from '@nestjs/config';
import { CacheService } from '@wwjCommon/cache/cache.service';
import { LoggingService } from '@wwjCommon/logging/logging.service';
import { UploadService } from '@wwjVendor/upload/upload.service';
import { PayService } from '@wwjVendor/pay/pay.service';
import { SmsService } from '@wwjVendor/sms/sms.service';
import { NoticeService } from '@wwjVendor/notice/notice.service';`;
}
/**
* 生成真实服务内容
*/
generateRealServiceContent(moduleName, serviceName, layer, phpMethods) {
// 先去掉层级后缀再去掉Service后缀
const cleanServiceName = serviceName.replace(/_(admin|api|core)$/, '');
const baseName = cleanServiceName.endsWith('Service') ? cleanServiceName.slice(0, -7) : cleanServiceName;
// 正确的命名规范服务类名与PHP/Java保持一致
let className = `${baseName}Service`;
if (layer === 'core') {
// Core层服务需要Core前缀
className = baseName.startsWith('Core') ? `${baseName}Service` : `Core${baseName}Service`;
} else {
// admin和api层直接使用业务名称
className = `${baseName}Service`;
}
// BaseService 中已存在的方法,需要避免覆盖
const baseServiceMethods = ['create', 'update', 'delete', 'find', 'findOne', 'findAll', 'save', 'remove'];
const methodImplementations = phpMethods.filter(method => method && method.name).map(method => {
// 调试:检查参数格式
console.log(`🔍 调试参数: ${method.name}`, method.parameters);
const parameters = this.converter.generateServiceParameters(method.parameters);
// 检查是否与BaseService方法冲突
if (baseServiceMethods.includes(method.name)) {
// 如果方法名与BaseService冲突重命名方法
const newMethodName = `${method.name}Record`;
console.log(`⚠️ 方法名冲突,重命名: ${method.name} -> ${newMethodName}`);
const realLogic = this.generateRealServiceLogic(method);
const logic = method.logic || { type: 'real', description: '基于真实PHP业务逻辑' };
return ` /**
* ${newMethodName} (原方法名: ${method.name})
* 对应 PHP: ${serviceName}::${method.name}()
* 逻辑类型: ${logic.type} - ${logic.description}
* 注意: 为避免与BaseService方法冲突已重命名
*/
async ${newMethodName}(${parameters}) {
${realLogic}
}`;
} else {
// 正常生成方法
const realLogic = this.generateRealServiceLogic(method);
const logic = method.logic || { type: 'real', description: '基于真实PHP业务逻辑' };
return ` /**
* ${method.name}
* 对应 PHP: ${serviceName}::${method.name}()
* 逻辑类型: ${logic.type} - ${logic.description}
*/
async ${method.name}(${parameters}) {
${realLogic}
}`;
}
}).join('\n\n');
return `import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { BaseService } from '@wwjCommon/base/base.service';
import { CacheService } from '@wwjCommon/cache/cache.service';
import { LoggingService } from '@wwjCommon/logging/logging.service';
import { UploadService } from '@wwjVendor/upload/upload.service';
import { PayService } from '@wwjVendor/pay/pay.service';
import { SmsService } from '@wwjVendor/sms/sms.service';
import { NoticeService } from '@wwjVendor/notice/notice.service';
@Injectable()
export class ${className} extends BaseService<any> {
private readonly logger = new Logger(${className}.name);
constructor(
@InjectRepository(Object)
protected readonly repository: Repository<any>,
private readonly cacheService: CacheService,
private readonly configService: ConfigService,
private readonly loggingService: LoggingService,
private readonly uploadService: UploadService,
private readonly payService: PayService,
private readonly smsService: SmsService,
private readonly noticeService: NoticeService,
) {
super(repository);
}
${methodImplementations}
}
`;
}
/**
* 生成真实服务逻辑
*/
generateRealServiceLogic(method) {
if (!method || !method.name) {
return ` // 方法信息缺失
return { success: false, message: "Method information missing" };`;
}
// 使用method.logic而不是method.body
const phpLogic = method.logic || method.body || '';
if (!phpLogic.trim()) {
return ` // TODO: 实现${method.name}业务逻辑
throw new Error('${method.name} not implemented');`;
}
// 转换PHP代码到TypeScript
const tsBody = this.converter.convertBusinessLogic('', method.name, phpLogic);
return ` // 基于PHP真实逻辑: ${method.name}
// PHP原文: ${phpLogic.substring(0, 150).replace(/\n/g, ' ')}...
${tsBody}`;
}
/**
* 从服务路径提取模块名
*/
extractModuleNameFromServicePath(filePath) {
// 从路径中提取模块名
const pathParts = filePath.split('/');
const serviceIndex = pathParts.findIndex(part => part === 'service');
if (serviceIndex > 0 && serviceIndex < pathParts.length - 2) {
// service目录后面应该是层级(admin/api/core),再后面是模块名
// 路径格式: .../app/service/admin/home/AuthSiteService.php
// 索引: .../8 9 10 11 12
return pathParts[serviceIndex + 2];
}
// 如果找不到service目录尝试从文件名推断
const fileName = path.basename(filePath, '.php');
if (fileName.includes('Service')) {
return fileName.replace('Service', '').toLowerCase();
}
return 'unknown';
}
/**
* 从服务路径提取层级
*/
extractLayerFromServicePath(filePath) {
// 从路径中提取层级信息
if (filePath.includes('/admin/')) {
return 'admin';
} else if (filePath.includes('/api/')) {
return 'api';
} else if (filePath.includes('/core/')) {
return 'core';
}
return 'core'; // 默认为core层
}
/**
* 转换为驼峰命名
*/
toCamelCase(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
}).replace(/\s+/g, '');
}
/**
* 转换为PascalCase
*/
toPascalCase(str) {
return str.replace(/(^|-)([a-z])/g, (match, p1, p2) => p2.toUpperCase());
}
/**
* 转换为kebab-case我们框架的标准命名格式
*/
toKebabCase(str) {
return str
.replace(/([A-Z])/g, '-$1')
.replace(/^-/, '')
.toLowerCase();
}
/**
* 检查模块是否有PHP服务
*/
hasPHPServices(moduleName, layer) {
const phpProjectPath = path.join(__dirname, '../../niucloud-php/niucloud');
const servicePath = path.join(phpProjectPath, 'app/service', layer, moduleName);
if (!fs.existsSync(servicePath)) return false;
// 检查目录内是否有PHP文件
try {
const files = fs.readdirSync(servicePath);
return files.some(file => file.endsWith('.php'));
} catch (error) {
return false;
}
}
/**
* 生成统计报告
*/
generateStatsReport() {
console.log('\n📊 服务生成统计报告');
console.log('='.repeat(50));
console.log(`✅ 创建服务数量: ${this.stats.servicesCreated}`);
console.log(`🔄 更新服务数量: ${this.stats.servicesUpdated}`);
console.log(`📝 处理方法数量: ${this.stats.methodsProcessed}`);
console.log(`❌ 错误数量: ${this.stats.errors}`);
console.log(`📈 成功率: ${this.stats.servicesCreated > 0 ? ((this.stats.servicesCreated - this.stats.errors) / this.stats.servicesCreated * 100).toFixed(2) : 0}%`);
}
}
// 如果直接运行此文件
if (require.main === module) {
const generator = new ServiceGenerator();
generator.run().catch(console.error);
}
module.exports = ServiceGenerator;