🎯 重构目录结构:完成项目组织优化
- 将wwjcloud目录重命名为wwjcloud-nest-v1作为项目根目录 - 将原nestjs目录重命名为wwjcloud作为NestJS后端目录 - 实现真正的前后端分离架构 - 恢复工作区中丢失的目录结构 - 更新相关配置文件路径引用 - 清理重复和嵌套目录问题 目录结构: wwjcloud-nest-v1/ ├── wwjcloud/ # NestJS 后端 ├── admin/ # 管理端前端 ├── web/ # PC端前端 ├── uni-app-x/ # 移动端前端 ├── wwjcloud-web/ # 部署根目录 ├── docker/ # Docker 配置 ├── docs/ # 文档 └── tools/ # 工具集
This commit is contained in:
@@ -1,410 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const BaseGenerator = require('./base-generator');
|
||||
|
||||
/**
|
||||
* 🏗️ 实体生成器
|
||||
* 专门负责生成NestJS实体文件 (参考Java架构)
|
||||
*/
|
||||
class EntityGenerator extends BaseGenerator {
|
||||
constructor() {
|
||||
super('EntityGenerator');
|
||||
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud', // 仅用于业务逻辑提取
|
||||
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
|
||||
discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/java-tools/java-discovery-result.json'
|
||||
};
|
||||
|
||||
this.discoveryData = null;
|
||||
this.entityStats = {
|
||||
entitiesCreated: 0,
|
||||
entitiesSkipped: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行实体生成
|
||||
*/
|
||||
async run() {
|
||||
try {
|
||||
console.log('🏗️ 启动实体生成器...');
|
||||
console.log('目标:生成NestJS实体文件\n');
|
||||
|
||||
// 加载Java架构发现结果(含PHP业务逻辑)
|
||||
await this.loadDiscoveryData();
|
||||
|
||||
// 生成实体
|
||||
await this.generateEntities();
|
||||
|
||||
// 输出统计报告
|
||||
this.printStats();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 实体生成失败:', error);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
} catch (error) {
|
||||
console.error('❌ 加载发现结果失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成实体
|
||||
*/
|
||||
async generateEntities() {
|
||||
console.log(' 🔨 生成实体...');
|
||||
|
||||
// 检查是否有模型数据
|
||||
if (!this.discoveryData.models || Object.keys(this.discoveryData.models).length === 0) {
|
||||
console.log(' ⚠️ 未发现PHP模型,跳过生成');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [moduleName, models] of Object.entries(this.discoveryData.models)) {
|
||||
// 检查Java架构是否有对应的模型目录
|
||||
if (!this.hasPHPModels(moduleName)) {
|
||||
console.log(` ⚠️ 模块 ${moduleName} 在Java架构中无对应,且PHP项目中也无模型,跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [modelName, modelInfo] of Object.entries(models)) {
|
||||
await this.createEntity(moduleName, modelName, modelInfo);
|
||||
this.stats.entitiesCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 生成了 ${this.stats.entitiesCreated} 个实体`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建实体
|
||||
*/
|
||||
async createEntity(moduleName, modelName, modelInfo) {
|
||||
const entityPath = path.join(
|
||||
this.config.nestjsBasePath,
|
||||
moduleName,
|
||||
'entity',
|
||||
`${this.toKebabCase(modelName)}.entity.ts`
|
||||
);
|
||||
|
||||
// 基于真实PHP model文件生成实体
|
||||
const content = await this.generateEntityFromPHP(moduleName, modelName, modelInfo);
|
||||
if (content) {
|
||||
this.writeFile(entityPath, content, `Entity for ${moduleName}/${modelName}`);
|
||||
this.entityStats.entitiesCreated++;
|
||||
} else {
|
||||
this.log(`跳过实体生成: ${moduleName}/${this.toKebabCase(modelName)}.entity.ts (无PHP源码)`, 'warning');
|
||||
this.entityStats.entitiesSkipped++;
|
||||
this.stats.filesSkipped++;
|
||||
}
|
||||
}
|
||||
|
||||
toKebabCase(str) {
|
||||
return String(str)
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
||||
.replace(/_/g, '-')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于PHP model文件生成实体
|
||||
*/
|
||||
async generateEntityFromPHP(moduleName, modelName, modelInfo) {
|
||||
const className = this.toPascalCase(modelName) + 'Entity';
|
||||
// 表名必须从PHP模型解析,禁止假设
|
||||
let tableName = '';
|
||||
|
||||
// 尝试读取真实的PHP model文件
|
||||
let fields = '';
|
||||
let primaryKey = 'id';
|
||||
let hasCustomPrimaryKey = false;
|
||||
|
||||
try {
|
||||
const phpModelPath = path.join(this.config.phpBasePath, 'app/model', moduleName, `${modelName}.php`);
|
||||
if (fs.existsSync(phpModelPath)) {
|
||||
const phpContent = fs.readFileSync(phpModelPath, 'utf-8');
|
||||
|
||||
// 提取主键信息
|
||||
const pkMatch = phpContent.match(/protected\s+\$pk\s*=\s*['"]([^'"]+)['"]/);
|
||||
if (pkMatch) {
|
||||
primaryKey = pkMatch[1];
|
||||
hasCustomPrimaryKey = true;
|
||||
}
|
||||
|
||||
fields = this.extractEntityFieldsFromPHP(phpContent, modelName);
|
||||
// 从PHP模型解析表名
|
||||
const nameMatch = phpContent.match(/protected\s+\$name\s*=\s*['"]([^'"]*)['"]/);
|
||||
tableName = nameMatch ? nameMatch[1] : '';
|
||||
console.log(` 📖 基于真实PHP model: ${phpModelPath}, 表名: ${tableName}`);
|
||||
} else {
|
||||
// 禁止假设,如果找不到PHP文件,不生成实体
|
||||
console.log(` ❌ 未找到PHP model文件,跳过生成: ${phpModelPath}`);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
// 禁止假设,如果读取失败,不生成实体
|
||||
console.log(` ❌ 读取PHP model文件失败,跳过生成: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 生成主键字段
|
||||
let primaryKeyField = '';
|
||||
if (hasCustomPrimaryKey) {
|
||||
// 基于真实PHP主键定义生成,禁止假设类型
|
||||
primaryKeyField = ` @PrimaryColumn({ name: '${primaryKey}', type: 'int' })
|
||||
${this.toCamelCase(primaryKey)}: number;`;
|
||||
} else {
|
||||
// 禁止假设主键,如果没有找到PHP主键定义,不生成主键字段
|
||||
primaryKeyField = '';
|
||||
console.log(` ⚠️ 未找到PHP主键定义,不生成主键字段: ${modelName}`);
|
||||
}
|
||||
|
||||
return `import { Entity, PrimaryGeneratedColumn, PrimaryColumn, Column, Index } from 'typeorm';
|
||||
|
||||
/**
|
||||
* ${className} - 数据库实体
|
||||
* 基于真实 PHP 模型生成,不依赖旧的 Common 基类
|
||||
*/
|
||||
@Entity('${tableName}')
|
||||
export class ${className} {
|
||||
${primaryKeyField}
|
||||
${fields}
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从PHP内容中提取实体字段 - 基于真实PHP模型
|
||||
*/
|
||||
extractEntityFieldsFromPHP(phpContent, modelName) {
|
||||
// 提取表名
|
||||
const nameMatch = phpContent.match(/protected\s+\$name\s*=\s*['"]([^'"]*)['"]/);
|
||||
const tableName = nameMatch ? nameMatch[1] : this.getTableName(modelName);
|
||||
|
||||
// 提取字段类型定义
|
||||
const typeMatch = phpContent.match(/protected\s+\$type\s*=\s*\[([\s\S]*?)\];/);
|
||||
const typeMap = {};
|
||||
if (typeMatch) {
|
||||
const typeContent = typeMatch[1];
|
||||
const typeMatches = typeContent.match(/(['"][^'"]*['"])\s*=>\s*(['"][^'"]*['"])/g);
|
||||
if (typeMatches) {
|
||||
typeMatches.forEach(match => {
|
||||
const fieldTypeMatch = match.match(/(['"][^'"]*['"])\s*=>\s*(['"][^'"]*['"])/);
|
||||
if (fieldTypeMatch) {
|
||||
const fieldName = fieldTypeMatch[1].replace(/['"]/g, '');
|
||||
const fieldType = fieldTypeMatch[2].replace(/['"]/g, '');
|
||||
typeMap[fieldName] = fieldType;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 提取软删除字段
|
||||
const deleteTimeMatch = phpContent.match(/protected\s+\$deleteTime\s*=\s*['"]([^'"]*)['"]/);
|
||||
const deleteTimeField = deleteTimeMatch ? deleteTimeMatch[1] : 'delete_time';
|
||||
|
||||
// 基于真实PHP模型结构生成字段
|
||||
console.log(` 📖 解析PHP模型字段: ${modelName}, 表名: ${tableName}`);
|
||||
|
||||
// 解析PHP模型字段定义
|
||||
const fields = this.parsePHPModelFields(phpContent, typeMap);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析PHP模型字段定义
|
||||
*/
|
||||
parsePHPModelFields(phpContent, typeMap) {
|
||||
const fields = [];
|
||||
|
||||
// 提取所有getter方法,这些通常对应数据库字段
|
||||
const getterMatches = phpContent.match(/public function get(\w+)Attr\([^)]*\)[\s\S]*?\{[\s\S]*?\n\s*\}/g);
|
||||
|
||||
if (getterMatches) {
|
||||
getterMatches.forEach(match => {
|
||||
const nameMatch = match.match(/public function get(\w+)Attr/);
|
||||
if (nameMatch) {
|
||||
const fieldName = this.toCamelCase(nameMatch[1]);
|
||||
const fieldType = this.determineFieldType(fieldName, typeMap);
|
||||
|
||||
fields.push(` @Column({ name: '${this.toSnakeCase(fieldName)}', type: '${fieldType}' })
|
||||
${fieldName}: ${this.getTypeScriptType(fieldType)};`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果没有找到getter方法,尝试从注释或其他地方提取字段信息
|
||||
if (fields.length === 0) {
|
||||
// 基于常见的数据库字段生成基础字段
|
||||
const commonFields = [
|
||||
{ name: 'title', type: 'varchar' },
|
||||
{ name: 'name', type: 'varchar' },
|
||||
{ name: 'type', type: 'varchar' },
|
||||
{ name: 'value', type: 'text' },
|
||||
{ name: 'is_default', type: 'tinyint' },
|
||||
{ name: 'sort', type: 'int' },
|
||||
{ name: 'status', type: 'tinyint' }
|
||||
];
|
||||
|
||||
commonFields.forEach(field => {
|
||||
if (phpContent.includes(field.name) || phpContent.includes(`'${field.name}'`)) {
|
||||
fields.push(` @Column({ name: '${field.name}', type: '${field.type}' })
|
||||
${this.toCamelCase(field.name)}: ${this.getTypeScriptType(field.type)};`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return fields.join('\n\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定字段类型
|
||||
*/
|
||||
determineFieldType(fieldName, typeMap) {
|
||||
if (typeMap[fieldName]) {
|
||||
return typeMap[fieldName];
|
||||
}
|
||||
|
||||
// 基于字段名推断类型
|
||||
if (fieldName.includes('time') || fieldName.includes('date')) {
|
||||
return 'timestamp';
|
||||
} else if (fieldName.includes('id')) {
|
||||
return 'int';
|
||||
} else if (fieldName.includes('status') || fieldName.includes('is_')) {
|
||||
return 'tinyint';
|
||||
} else if (fieldName.includes('sort') || fieldName.includes('order')) {
|
||||
return 'int';
|
||||
} else {
|
||||
return 'varchar';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取TypeScript类型
|
||||
*/
|
||||
getTypeScriptType(phpType) {
|
||||
const typeMap = {
|
||||
'varchar': 'string',
|
||||
'text': 'string',
|
||||
'int': 'number',
|
||||
'tinyint': 'number',
|
||||
'timestamp': 'Date',
|
||||
'datetime': 'Date',
|
||||
'json': 'object'
|
||||
};
|
||||
|
||||
return typeMap[phpType] || 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为camelCase
|
||||
*/
|
||||
toCamelCase(str) {
|
||||
return str.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为snake_case
|
||||
*/
|
||||
toSnakeCase(str) {
|
||||
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成默认实体字段 - 禁止假设,仅返回空
|
||||
*/
|
||||
generateEntityFields(modelName) {
|
||||
// 禁止假设字段,返回空字符串
|
||||
// 所有字段必须基于真实PHP模型解析
|
||||
console.log(` ⚠️ 禁止假设字段,请基于真实PHP模型: ${modelName}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表名
|
||||
*/
|
||||
getTableName(modelName) {
|
||||
// 禁止假设表名,表名必须从PHP模型的$name属性获取
|
||||
// 这里返回空字符串,强制从PHP源码解析
|
||||
console.log(` ⚠️ 禁止假设表名,必须从PHP模型解析: ${modelName}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为PascalCase - 处理连字符
|
||||
*/
|
||||
toPascalCase(str) {
|
||||
return str.replace(/(^|-)([a-z])/g, (match, p1, p2) => p2.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为camelCase
|
||||
*/
|
||||
toCamelCase(str) {
|
||||
return str.charAt(0).toLowerCase() + str.slice(1);
|
||||
}
|
||||
|
||||
toPascalCase(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP模型
|
||||
*/
|
||||
hasPHPModels(moduleName) {
|
||||
const phpProjectPath = path.join(__dirname, '../../niucloud-php/niucloud');
|
||||
const modelPath = path.join(phpProjectPath, 'app/model', moduleName);
|
||||
|
||||
if (!fs.existsSync(modelPath)) return false;
|
||||
|
||||
// 检查目录内是否有PHP文件
|
||||
try {
|
||||
const files = fs.readdirSync(modelPath);
|
||||
return files.some(file => file.endsWith('.php'));
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保目录存在
|
||||
*/
|
||||
ensureDir(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出统计报告
|
||||
*/
|
||||
printStats() {
|
||||
super.printStats({
|
||||
'Entities Created': this.entityStats.entitiesCreated,
|
||||
'Entities Skipped': this.entityStats.entitiesSkipped
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
const generator = new EntityGenerator();
|
||||
generator.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = EntityGenerator;
|
||||
@@ -1,893 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const BusinessLogicConverter = require('./business-logic-converter');
|
||||
|
||||
/**
|
||||
* ⚙️ 服务生成器
|
||||
* 专门负责生成和更新NestJS服务
|
||||
*/
|
||||
class ServiceGenerator {
|
||||
constructor() {
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud', // 用于业务逻辑提取
|
||||
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
|
||||
discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/java-tools/java-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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf-8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
} catch (error) {
|
||||
console.error(' ❌ 加载发现数据失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按模块动态扫描Java服务层结构,参考Java框架架构
|
||||
*/
|
||||
async scanJavaModulesAndGenerateServices() {
|
||||
console.log(' 🔨 扫描Java框架架构,生成对应的NestJS服务...');
|
||||
|
||||
const javaServicePath = path.join(this.config.javaBasePath, 'com/niu/core/service');
|
||||
const layers = ['core', 'admin', 'api'];
|
||||
let processedCount = 0;
|
||||
|
||||
// 收集所有模块 - 从Java项目中扫描
|
||||
const modules = new Set();
|
||||
for (const layer of layers) {
|
||||
const layerPath = path.join(javaServicePath, layer);
|
||||
if (fs.existsSync(layerPath)) {
|
||||
try {
|
||||
const moduleDirs = fs.readdirSync(layerPath, { withFileTypes: true })
|
||||
.filter(dirent => dirent.isDirectory())
|
||||
.map(dirent => dirent.name);
|
||||
moduleDirs.forEach(module => modules.add(module));
|
||||
console.log(` 📁 发现Java ${layer}层模块: ${moduleDirs.join(', ')}`);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 无法读取Java ${layer}层目录: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` 📊 发现 ${modules.size} 个模块: ${Array.from(modules).join(', ')}`);
|
||||
|
||||
// 为每个模块生成服务 - 按照Java的@Service + @Resource模式
|
||||
for (const moduleName of modules) {
|
||||
console.log(` 🔍 处理模块: ${moduleName} (参考Java架构)`);
|
||||
|
||||
// 检查模块在各层的存在性 - 扫描Java service/core, service/admin, service/api结构
|
||||
const moduleLayers = [];
|
||||
for (const layer of layers) {
|
||||
const moduleServicePath = path.join(javaServicePath, layer, moduleName);
|
||||
if (fs.existsSync(moduleServicePath)) {
|
||||
try {
|
||||
const files = fs.readdirSync(moduleServicePath, { withFileTypes: true });
|
||||
const javaFiles = files
|
||||
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.java') && dirent.name.includes('Impl'))
|
||||
.map(dirent => dirent.name);
|
||||
if (javaFiles.length > 0) {
|
||||
moduleLayers.push({
|
||||
layer,
|
||||
serviceFiles: javaFiles,
|
||||
servicePath: moduleServicePath
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 无法读取Java模块${moduleName}/${layer}目录: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleLayers.length === 0) {
|
||||
console.log(` ⚠️ 模块 ${moduleName} 没有任何Java服务文件,跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(` 📁 模块 ${moduleName} 有 ${moduleLayers.length} 个Java服务层: ${moduleLayers.map(l => l.layer).join(', ')}`);
|
||||
|
||||
// 为每个Java服务层生成对应的NestJS服务 - 按Java架构处理core依赖
|
||||
for (const { layer, serviceFiles, servicePath } of moduleLayers) {
|
||||
for (const serviceFile of serviceFiles) {
|
||||
const javaServicePath = path.join(servicePath, serviceFile);
|
||||
|
||||
console.log(` ⚙️ 处理Java服务: ${moduleName}/${layer}/${serviceFile} -> NestJS`);
|
||||
|
||||
try {
|
||||
await this.createNestJSServiceFromJava(moduleName, serviceFile, javaServicePath, layer);
|
||||
processedCount++;
|
||||
console.log(` ✅ 成功创建NestJS服务: ${moduleName}/${layer}/${serviceFile}`);
|
||||
} catch (error) {
|
||||
console.error(` ❌ 创建NestJS服务失败 ${moduleName}/${layer}/${serviceFile}:`, error.message);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.stats.servicesCreated = processedCount;
|
||||
console.log(` ✅ 创建了 ${this.stats.servicesCreated} 个服务`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成服务
|
||||
*/
|
||||
async generateServices() {
|
||||
console.log(' 🔨 生成服务文件...');
|
||||
|
||||
// 优先扫描Java项目架构,参考Java的@Service和Core依赖模式
|
||||
await this.scanJavaModulesAndGenerateServices();
|
||||
|
||||
// 如果发现数据存在,也尝试基于发现数据生成(作为备选)
|
||||
if (this.discoveryData.services && Object.keys(this.discoveryData.services).length > 0) {
|
||||
console.log(' 🔄 基于发现数据补充生成服务...');
|
||||
await this.generateServicesFromDiscovery();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于发现数据生成服务(备选方法)
|
||||
*/
|
||||
async generateServicesFromDiscovery() {
|
||||
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);
|
||||
|
||||
// 检查Java架构是否有对应的服务目录
|
||||
if (!this.hasPHPServices(correctModuleName, layer)) {
|
||||
console.log(` ⚠️ 模块 ${correctModuleName} 在Java架构中无对应,且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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 基于发现数据创建了 ${processedCount} 个服务`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有服务为真实业务逻辑
|
||||
*/
|
||||
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++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Java服务文件创建NestJS服务 - 参考Java架构,处理core依赖
|
||||
*/
|
||||
async createNestJSServiceFromJava(moduleName, javaServiceFile, javaFilePath, layer) {
|
||||
// 确保服务目录存在
|
||||
const serviceDir = path.join(
|
||||
this.config.nestjsBasePath,
|
||||
moduleName,
|
||||
'services',
|
||||
layer
|
||||
);
|
||||
|
||||
// 从Java文件名提取服务名,去掉Impl和Service后缀
|
||||
let serviceName = javaServiceFile.replace('.java', '');
|
||||
if (serviceName.endsWith('ServiceImpl')) {
|
||||
serviceName = serviceName.replace('ServiceImpl', '');
|
||||
} else if (serviceName.endsWith('Service')) {
|
||||
serviceName = serviceName.replace('Service', '');
|
||||
}
|
||||
|
||||
const servicePath = path.join(serviceDir, `${this.toKebabCase(serviceName)}.service.ts`);
|
||||
|
||||
// 检查文件是否已存在
|
||||
if (fs.existsSync(servicePath)) {
|
||||
console.log(` ⚠️ 服务文件已存在: ${servicePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 读取Java服务文件
|
||||
const javaContent = fs.readFileSync(javaFilePath, 'utf-8');
|
||||
|
||||
// 解析Java服务的依赖关系,特别是core服务依赖
|
||||
const coreDependencies = this.extractCoreDependencies(javaContent);
|
||||
const javaMethods = this.extractJavaMethods(javaContent);
|
||||
|
||||
console.log(` 📝 从${path.basename(javaFilePath)}中找到 ${javaMethods.length} 个方法`);
|
||||
console.log(` 🔗 发现Core依赖: ${coreDependencies.join(', ')}`);
|
||||
|
||||
// 生成NestJS服务内容,处理core依赖
|
||||
const nestjsContent = this.generateNestJSServiceFromJava(moduleName, serviceName, layer, javaMethods, coreDependencies);
|
||||
|
||||
// 确保目录存在
|
||||
if (!fs.existsSync(serviceDir)) {
|
||||
fs.mkdirSync(serviceDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
fs.writeFileSync(servicePath, nestjsContent, 'utf-8');
|
||||
console.log(` ✅ 创建NestJS服务: ${moduleName}/${layer}/${this.toKebabCase(serviceName)}.service.ts`);
|
||||
|
||||
this.stats.methodsProcessed += javaMethods.length;
|
||||
this.stats.servicesCreated++;
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ 无法创建NestJS服务 ${serviceName}: ${error.message}`);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从PHP文件创建NestJS服务 - 参考Java架构,使用V1框架基础设施
|
||||
*/
|
||||
async createNestJSServiceFromPHP(moduleName, serviceName, phpFilePath, layer) {
|
||||
// 确保服务目录存在
|
||||
const serviceDir = path.join(
|
||||
this.config.nestjsBasePath,
|
||||
moduleName,
|
||||
'services',
|
||||
layer
|
||||
);
|
||||
|
||||
// 先去掉Service后缀
|
||||
const baseName = serviceName.endsWith('Service') ? serviceName.slice(0, -7) : serviceName;
|
||||
const servicePath = path.join(serviceDir, `${this.toKebabCase(baseName)}.service.ts`);
|
||||
|
||||
// 检查文件是否已存在
|
||||
if (fs.existsSync(servicePath)) {
|
||||
console.log(` ⚠️ 服务文件已存在: ${servicePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 读取PHP服务文件
|
||||
const phpContent = fs.readFileSync(phpFilePath, 'utf-8');
|
||||
|
||||
// 提取PHP方法
|
||||
const phpMethods = this.converter.extractPHPMethods(phpContent);
|
||||
|
||||
console.log(` 📝 从${path.basename(phpFilePath)}中找到 ${phpMethods.length} 个PHP方法`);
|
||||
|
||||
// 生成NestJS服务内容
|
||||
const nestjsContent = phpMethods.length > 0
|
||||
? this.generateRealServiceContent(moduleName, serviceName, layer, phpMethods)
|
||||
: this.generateBasicServiceContent(moduleName, serviceName, layer);
|
||||
|
||||
// 确保目录存在
|
||||
if (!fs.existsSync(serviceDir)) {
|
||||
fs.mkdirSync(serviceDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
fs.writeFileSync(servicePath, nestjsContent, 'utf-8');
|
||||
console.log(` ✅ 创建服务: ${moduleName}/${layer}/${this.toKebabCase(baseName)}.service.ts`);
|
||||
|
||||
this.stats.methodsProcessed += phpMethods.length;
|
||||
this.stats.servicesCreated++;
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ 无法创建服务 ${serviceName}: ${error.message}`);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新服务为真实逻辑
|
||||
*/
|
||||
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`;
|
||||
}
|
||||
|
||||
// 获取V1框架基础设施和Vendor服务导入
|
||||
const infrastructureImports = this.getV1FrameworkInfrastructureImports();
|
||||
const vendorImports = this.getV1FrameworkVendorImports();
|
||||
|
||||
return `import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
${infrastructureImports}
|
||||
${vendorImports}
|
||||
|
||||
/**
|
||||
* ${className} - ${layer}层服务
|
||||
* 参考Java Spring Boot架构:@Service注解 + 依赖注入
|
||||
* 对应Java: @Service + @Resource注入,类似CoreAliappConfigServiceImpl
|
||||
* 使用V1框架基础设施:CacheService, MetricsService, TenantService等
|
||||
* 业务逻辑来源:PHP ${moduleName}/${layer}层服务
|
||||
*/
|
||||
@Injectable()
|
||||
export class ${className} {
|
||||
private readonly logger = new Logger(${className}.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Object)
|
||||
private readonly repository: Repository<any>,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly metricsService: MetricsService,
|
||||
private readonly tenantService: TenantService,
|
||||
private readonly uploadService: UploadService,
|
||||
private readonly payService: PayService,
|
||||
private readonly smsService: SmsService,
|
||||
private readonly noticeService: NoticeService,
|
||||
) {}
|
||||
|
||||
// 服务方法基于Java框架风格,直接从PHP业务逻辑迁移
|
||||
// 使用V1框架提供的服务:configService, cacheService, metricsService等
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取V1框架基础设施导入 - 基于实际V1框架导出
|
||||
*/
|
||||
getV1FrameworkInfrastructureImports() {
|
||||
return `import { ConfigService } from '@nestjs/config';
|
||||
import { CacheService } from '@wwjcloud-boot/infra/cache/cache.service';
|
||||
import { MetricsService } from '@wwjcloud-boot/infra/metrics/metrics.service';
|
||||
import { TenantService } from '@wwjcloud-boot/infra/tenant/tenant.service';`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取V1框架Vendor服务导入 - 基于实际V1框架vendor导出
|
||||
*/
|
||||
getV1FrameworkVendorImports() {
|
||||
return `import { UploadService } from '@wwjcloud-boot/vendor/upload';
|
||||
import { PayService } from '@wwjcloud-boot/vendor/pay';
|
||||
import { SmsService } from '@wwjcloud-boot/vendor/sms';
|
||||
import { NoticeService } from '@wwjcloud-boot/vendor/notice';`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成真实服务内容
|
||||
*/
|
||||
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`;
|
||||
}
|
||||
|
||||
const methodImplementations = phpMethods.filter(method => method && method.name).map(method => {
|
||||
console.log(`🔍 调试参数: ${method.name}`, method.parameters);
|
||||
const parameters = this.converter.generateServiceParameters(method.parameters);
|
||||
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');
|
||||
|
||||
const infrastructureImports = this.getV1FrameworkInfrastructureImports();
|
||||
const vendorImports = this.getV1FrameworkVendorImports();
|
||||
|
||||
return `import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
${infrastructureImports}
|
||||
${vendorImports}
|
||||
|
||||
/**
|
||||
* ${className} - ${layer}层服务
|
||||
* 参考Java Spring Boot架构:@Service + @Resource模式
|
||||
* 对应Java实现:类似CoreAliappConfigServiceImpl的结构
|
||||
* 使用V1框架基础设施:CacheService, MetricsService, TenantService
|
||||
* 业务逻辑迁移自:PHP ${moduleName}/${layer}层服务
|
||||
*/
|
||||
@Injectable()
|
||||
export class ${className} {
|
||||
private readonly logger = new Logger(${className}.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Object)
|
||||
private readonly repository: Repository<any>,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly metricsService: MetricsService,
|
||||
private readonly tenantService: TenantService,
|
||||
private readonly uploadService: UploadService,
|
||||
private readonly payService: PayService,
|
||||
private readonly smsService: SmsService,
|
||||
private readonly noticeService: NoticeService,
|
||||
) {}
|
||||
|
||||
${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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取Java服务中的Core依赖关系
|
||||
*/
|
||||
extractCoreDependencies(javaContent) {
|
||||
const coreDependencies = [];
|
||||
|
||||
// 查找 @Resource ICore*Service 依赖
|
||||
const resourcePattern = /@Resource\s+(\w+)\s+(\w+);/g;
|
||||
const imports = javaContent.match(/import\s+[\w\.]+\.ICore[\w]+Service;?/g) || [];
|
||||
|
||||
imports.forEach(importLine => {
|
||||
const serviceName = importLine.match(/ICore([\w]+)Service/)?.[1];
|
||||
if (serviceName) {
|
||||
coreDependencies.push(`Core${serviceName}Service`);
|
||||
}
|
||||
});
|
||||
|
||||
return coreDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取Java服务的方法
|
||||
*/
|
||||
extractJavaMethods(javaContent) {
|
||||
const methods = [];
|
||||
|
||||
// 简单的Java方法提取 - 查找 public 方法
|
||||
const methodPattern = /public\s+(\w+(?:<\w+>)?)\s+(\w+)\s*\(([^)]*)\)\s*\{/g;
|
||||
let match;
|
||||
|
||||
while ((match = methodPattern.exec(javaContent)) !== null) {
|
||||
const [fullMatch, returnType, methodName, parameters] = match;
|
||||
methods.push({
|
||||
name: methodName,
|
||||
returnType: returnType,
|
||||
parameters: parameters.trim(),
|
||||
body: this.extractMethodBody(javaContent, fullMatch)
|
||||
});
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取方法体(简化版)
|
||||
*/
|
||||
extractMethodBody(javaContent, methodStart) {
|
||||
// 这是一个简化的实现,实际应用中需要更复杂的解析
|
||||
const startIndex = javaContent.indexOf(methodStart);
|
||||
if (startIndex === -1) return '';
|
||||
|
||||
let braceCount = 0;
|
||||
let bodyStart = -1;
|
||||
|
||||
for (let i = startIndex; i < javaContent.length; i++) {
|
||||
if (javaContent[i] === '{') {
|
||||
braceCount++;
|
||||
if (bodyStart === -1) bodyStart = i + 1;
|
||||
} else if (javaContent[i] === '}') {
|
||||
braceCount--;
|
||||
if (braceCount === 0) {
|
||||
return javaContent.substring(bodyStart, i).trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于Java服务生成NestJS服务内容
|
||||
*/
|
||||
generateNestJSServiceFromJava(moduleName, serviceName, layer, javaMethods, coreDependencies) {
|
||||
const infrastructureImports = this.getV1FrameworkInfrastructureImports();
|
||||
const vendorImports = this.getV1FrameworkVendorImports();
|
||||
|
||||
// 生成core服务依赖的导入
|
||||
const coreImports = coreDependencies.map(dep => {
|
||||
const depName = dep.replace('Core', '').replace('Service', '');
|
||||
return `import { ${dep} } from '../core/${this.toKebabCase(depName)}.service';`;
|
||||
}).join('\n');
|
||||
|
||||
// 生成core服务依赖的注入
|
||||
const coreInjections = coreDependencies.map(dep => {
|
||||
const propName = this.toCamelCase(dep.replace('Service', ''));
|
||||
return ` private readonly ${propName}: ${dep},`;
|
||||
}).join('\n');
|
||||
|
||||
const methodImplementations = javaMethods.map(method => {
|
||||
return ` /**
|
||||
* ${method.name}
|
||||
* 对应Java: ${serviceName}ServiceImpl::${method.name}()
|
||||
*/
|
||||
async ${method.name}(${this.convertJavaParametersToTS(method.parameters)}) {
|
||||
// TODO: 实现 ${method.name} 业务逻辑
|
||||
// 原始Java逻辑: ${method.body.substring(0, 100)}...
|
||||
throw new Error('${method.name} not implemented');
|
||||
}`;
|
||||
}).join('\n\n');
|
||||
|
||||
return `import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
${infrastructureImports}
|
||||
${vendorImports}
|
||||
${coreImports}
|
||||
|
||||
/**
|
||||
* ${serviceName}Service - ${layer}层服务
|
||||
* 对应Java: ${serviceName}ServiceImpl
|
||||
* 使用V1框架基础设施和Core服务依赖
|
||||
*/
|
||||
@Injectable()
|
||||
export class ${serviceName}Service {
|
||||
private readonly logger = new Logger(${serviceName}Service.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Object)
|
||||
private readonly repository: Repository<any>,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly metricsService: MetricsService,
|
||||
private readonly tenantService: TenantService,
|
||||
private readonly uploadService: UploadService,
|
||||
private readonly payService: PayService,
|
||||
private readonly smsService: SmsService,
|
||||
private readonly noticeService: NoticeService,
|
||||
${coreInjections}
|
||||
) {}
|
||||
|
||||
${methodImplementations}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换Java参数到TypeScript参数(简化版)
|
||||
*/
|
||||
convertJavaParametersToTS(javaParameters) {
|
||||
if (!javaParameters.trim()) return '';
|
||||
|
||||
// 简单的Java到TS参数转换
|
||||
return javaParameters
|
||||
.split(',')
|
||||
.map(param => {
|
||||
const trimmed = param.trim();
|
||||
const [type, name] = trimmed.split(/\s+/);
|
||||
if (!name) return trimmed;
|
||||
|
||||
const tsType = this.convertJavaTypeToTS(type);
|
||||
return `${name}: ${tsType}`;
|
||||
})
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换Java类型到TypeScript类型(简化版)
|
||||
*/
|
||||
convertJavaTypeToTS(javaType) {
|
||||
const typeMap = {
|
||||
'String': 'string',
|
||||
'Integer': 'number',
|
||||
'Long': 'number',
|
||||
'Boolean': 'boolean',
|
||||
'List': 'any[]',
|
||||
'Map': 'Record<string, any>',
|
||||
'void': 'void'
|
||||
};
|
||||
|
||||
return typeMap[javaType] || 'any';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成统计报告
|
||||
*/
|
||||
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;
|
||||
2
wwjcloud-nest-v1/.gitignore
vendored
2
wwjcloud-nest-v1/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
# Generated startup diagnostics report
|
||||
startup-check.report.json
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
25
wwjcloud-nest-v1/admin/Dockerfile
Normal file
25
wwjcloud-nest-v1/admin/Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
# Admin Frontend Dockerfile
|
||||
FROM node:20-alpine as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
RUN npm ci && npm cache clean --force
|
||||
|
||||
# Copy source code and build
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Production stage with nginx
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy built files
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
|
||||
# Create default nginx config
|
||||
RUN echo 'server { listen 80; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } }' > /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -2,7 +2,7 @@ const fs = require('fs')
|
||||
|
||||
const publish = () => {
|
||||
const src = './dist'
|
||||
const dest = '../webroot/public/admin'
|
||||
const dest = '../wwjcloud-web/public/admin'
|
||||
|
||||
solve()
|
||||
|
||||
|
||||
11
wwjcloud-nest-v1/application-boot.json
Normal file
11
wwjcloud-nest-v1/application-boot.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"NODE_ENV": "development",
|
||||
"GLOBAL_PREFIX": "api",
|
||||
"PORT": 3001,
|
||||
"SWAGGER_ENABLED": true,
|
||||
"AUTH_ENABLED": false,
|
||||
"RBAC_ENABLED": false,
|
||||
"RATE_LIMIT_ENABLED": false,
|
||||
"REDIS_ENABLED": false,
|
||||
"QUEUE_ENABLED": false
|
||||
}
|
||||
@@ -1,9 +1,62 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
# ========================================
|
||||
# MySQL 数据库
|
||||
# ========================================
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: wwjcloud-mysql-v1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: wwjcloud
|
||||
MYSQL_DATABASE: wwjcloud
|
||||
MYSQL_USER: wwjcloud
|
||||
MYSQL_PASSWORD: wwjcloud
|
||||
TZ: Asia/Shanghai
|
||||
ports:
|
||||
- "3307:3306" # 使用3307避免与现有mysql冲突
|
||||
volumes:
|
||||
- wwjcloud_mysql_data_v1:/var/lib/mysql
|
||||
- ../../../sql:/docker-entrypoint-initdb.d
|
||||
command:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
- --default-authentication-plugin=mysql_native_password
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-pwwjcloud"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- wwjcloud-network-v1
|
||||
|
||||
# ========================================
|
||||
# Redis 缓存
|
||||
# ========================================
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: wwjcloud-redis-v1
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6380:6379" # 使用6380避免与现有redis冲突
|
||||
command: redis-server --appendonly yes
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
networks:
|
||||
- wwjcloud-network-v1
|
||||
|
||||
# ========================================
|
||||
# NestJS 后端服务 (包含Core层)
|
||||
# ========================================
|
||||
api:
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: ./docker/Dockerfile
|
||||
context: ../wwjcloud-nest-v1
|
||||
dockerfile: ../docker/Dockerfile
|
||||
container_name: wwjcloud-api-v1
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
@@ -14,8 +67,15 @@ services:
|
||||
- AI_SIMULATE_DIRECT_ENQUEUE=false
|
||||
- PROMETHEUS_ENABLED=true
|
||||
- METRICS_ENABLED=true
|
||||
- SWAGGER_ENABLED=false
|
||||
- SWAGGER_ENABLED=true # 启用Swagger用于API测试
|
||||
- RESPONSE_WRAPPER_ENABLED=true
|
||||
# 数据库配置
|
||||
- DB_HOST=mysql
|
||||
- DB_PORT=3306
|
||||
- DB_USERNAME=wwjcloud
|
||||
- DB_PASSWORD=wwjcloud
|
||||
- DB_DATABASE=wwjcloud
|
||||
- DB_SYNC=false
|
||||
# 安全守卫
|
||||
- AUTH_ENABLED=true
|
||||
- RBAC_ENABLED=true
|
||||
@@ -35,17 +95,66 @@ services:
|
||||
- QUEUE_REDIS_HOST=redis
|
||||
- QUEUE_REDIS_PORT=6379
|
||||
depends_on:
|
||||
- redis
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
command: ["redis-server", "--save", "", "--appendonly", "no"]
|
||||
ports:
|
||||
- "6379:6379"
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
networks:
|
||||
- wwjcloud-network-v1
|
||||
|
||||
# ========================================
|
||||
# Admin 前端管理面板 (暂时注释,等待修复构建问题)
|
||||
# ========================================
|
||||
# admin:
|
||||
# build:
|
||||
# context: ../admin
|
||||
# dockerfile: Dockerfile
|
||||
# container_name: wwjcloud-admin-v1
|
||||
# restart: unless-stopped
|
||||
# ports:
|
||||
# - "8080:80" # Admin面板端口
|
||||
# depends_on:
|
||||
# api:
|
||||
# condition: service_healthy
|
||||
# healthcheck:
|
||||
# test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"]
|
||||
# interval: 30s
|
||||
# timeout: 3s
|
||||
# retries: 3
|
||||
# networks:
|
||||
# - wwjcloud-network-v1
|
||||
|
||||
# ========================================
|
||||
# K6 压测工具
|
||||
# ========================================
|
||||
k6:
|
||||
image: ghcr.io/grafana/k6:0.50.0
|
||||
working_dir: /scripts
|
||||
volumes:
|
||||
- ./k6:/scripts
|
||||
command: ["run", "-e", "VUS=100000", "-e", "ITERATIONS=1000000", "--summary-export=/scripts/summary-100k-vus.json", "/scripts/aio_test.js"]
|
||||
command: ["run", "-e", "VUS=50", "-e", "ITERATIONS=2000", "--summary-export=/scripts/summary-full-test.json", "/scripts/full_test.js"]
|
||||
depends_on:
|
||||
- api
|
||||
api:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- wwjcloud-network-v1
|
||||
|
||||
# ========================================
|
||||
# 数据卷
|
||||
# ========================================
|
||||
volumes:
|
||||
wwjcloud_mysql_data_v1:
|
||||
driver: local
|
||||
|
||||
# ========================================
|
||||
# 网络
|
||||
# ========================================
|
||||
networks:
|
||||
wwjcloud-network-v1:
|
||||
driver: bridge
|
||||
132
wwjcloud-nest-v1/docker/k6/full_test.js
Normal file
132
wwjcloud-nest-v1/docker/k6/full_test.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
|
||||
export const options = {
|
||||
vus: Number(__ENV.VUS || 50),
|
||||
iterations: Number(__ENV.ITERATIONS || 1000),
|
||||
thresholds: {
|
||||
http_req_duration: ['p(95)<1000'],
|
||||
http_req_failed: ['rate<0.1'],
|
||||
checks: ['rate>0.85'],
|
||||
},
|
||||
};
|
||||
|
||||
const BASE_API = __ENV.BASE_URL || 'http://api:3000';
|
||||
const BASE_ADMIN = __ENV.ADMIN_URL || 'http://admin:80';
|
||||
const API = `${BASE_API}/api`;
|
||||
|
||||
function safeJson(res) {
|
||||
try { return res.json(); } catch (e) { return null; }
|
||||
}
|
||||
|
||||
export default function () {
|
||||
// ========================================
|
||||
// 1. 基础健康检查
|
||||
// ========================================
|
||||
const health = http.get(`${API}/health`);
|
||||
const healthJson = safeJson(health);
|
||||
check(health, {
|
||||
'health check 200': (r) => r.status === 200,
|
||||
'health check ok': () => healthJson && healthJson.code === 1,
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// 2. Core层测试 - 系统配置相关
|
||||
// ========================================
|
||||
|
||||
// 测试系统配置接口 (core层)
|
||||
const configTest = Math.random();
|
||||
if (configTest < 0.3) {
|
||||
// 测试网站配置
|
||||
const website = http.get(`${API}/adminapi/sys/config/website`);
|
||||
const websiteJson = safeJson(website);
|
||||
check(website, {
|
||||
'sys config website 200': (r) => r.status === 200,
|
||||
'sys config website response': () => websiteJson && typeof websiteJson === 'object',
|
||||
}, 'Core层 - 系统配置');
|
||||
}
|
||||
|
||||
if (configTest < 0.4 && configTest >= 0.3) {
|
||||
// 测试配置URL接口
|
||||
const url = http.get(`${API}/adminapi/sys/config/url`);
|
||||
check(url, {
|
||||
'sys config url 200': (r) => r.status === 200,
|
||||
}, 'Core层 - 配置URL');
|
||||
}
|
||||
|
||||
if (configTest >= 0.4 && configTest < 0.7) {
|
||||
// 测试其他配置接口
|
||||
const config = http.get(`${API}/adminapi/sys/config/layout`);
|
||||
check(config, {
|
||||
'sys config layout 200': (r) => r.status === 200,
|
||||
}, 'Core层 - 布局配置');
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 3. Admin面板测试
|
||||
// ========================================
|
||||
if (configTest >= 0.7) {
|
||||
// 测试admin面板前端页面
|
||||
const adminPage = http.get(`${BASE_ADMIN}/`);
|
||||
check(adminPage, {
|
||||
'admin page 200': (r) => r.status === 200,
|
||||
'admin page html': () => r.body.includes('html') || r.body.includes('<!DOCTYPE'),
|
||||
}, 'Admin面板 - 前端页面');
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 4. AI治理能力测试
|
||||
// ========================================
|
||||
const aiTest = Math.random();
|
||||
if (aiTest < 0.5) {
|
||||
// AI恢复状态检查
|
||||
const aiStatus = http.get(`${API}/ai/recovery/status`);
|
||||
const aiStatusJson = safeJson(aiStatus);
|
||||
check(aiStatus, {
|
||||
'ai recovery status 200': (r) => r.status === 200,
|
||||
'ai recovery status valid': () => aiStatusJson && aiStatusJson.code === 1,
|
||||
}, 'AI治理 - 恢复状态');
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 5. 基础设施测试
|
||||
// ========================================
|
||||
const infraTest = Math.random();
|
||||
if (infraTest < 0.3) {
|
||||
// 测试缓存系统
|
||||
const cache = http.get(`${API}/cache/ping`);
|
||||
check(cache, {
|
||||
'cache ping 200': (r) => r.status === 200,
|
||||
}, '基础设施 - 缓存');
|
||||
}
|
||||
|
||||
if (infraTest >= 0.3 && infraTest < 0.6) {
|
||||
// 测试队列系统
|
||||
const queue = http.get(`${API}/infra/queue/status`);
|
||||
check(queue, {
|
||||
'queue status 200': (r) => r.status === 200,
|
||||
}, '基础设施 - 队列');
|
||||
}
|
||||
|
||||
if (infraTest >= 0.6) {
|
||||
// 测试指标系统
|
||||
const metrics = http.get(`${API}/metrics`);
|
||||
check(metrics, {
|
||||
'metrics 200': (r) => r.status === 200,
|
||||
}, '基础设施 - 指标');
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 6. 数据库连接测试 (通过API)
|
||||
// ========================================
|
||||
const dbTest = Math.random();
|
||||
if (dbTest < 0.2) {
|
||||
// 测试需要数据库的接口,验证core层数据库连接
|
||||
const secure = http.get(`${API}/secure/ping`);
|
||||
check(secure, {
|
||||
'secure ping 200': (r) => r.status === 200,
|
||||
}, '数据库 - 连接测试');
|
||||
}
|
||||
|
||||
sleep(0.1);
|
||||
}
|
||||
227
wwjcloud-nest-v1/docker/k6/summary-full-test.json
Normal file
227
wwjcloud-nest-v1/docker/k6/summary-full-test.json
Normal file
@@ -0,0 +1,227 @@
|
||||
{
|
||||
"metrics": {
|
||||
"http_reqs": {
|
||||
"count": 6353,
|
||||
"rate": 1886.1963909291426
|
||||
},
|
||||
"http_req_blocked": {
|
||||
"avg": 0.008377710373052018,
|
||||
"min": 0,
|
||||
"med": 0.0005,
|
||||
"max": 1.55625,
|
||||
"p(90)": 0.002042,
|
||||
"p(95)": 0.002958
|
||||
},
|
||||
"iteration_duration": {
|
||||
"med": 102.237708,
|
||||
"max": 317.154958,
|
||||
"p(90)": 125.29083750000001,
|
||||
"p(95)": 161.67191284999998,
|
||||
"avg": 82.89243237149998,
|
||||
"min": 0.548708
|
||||
},
|
||||
"http_req_connecting": {
|
||||
"p(90)": 0,
|
||||
"p(95)": 0,
|
||||
"avg": 0.000852412403588856,
|
||||
"min": 0,
|
||||
"med": 0,
|
||||
"max": 0.488375
|
||||
},
|
||||
"data_sent": {
|
||||
"count": 525622,
|
||||
"rate": 156056.4016044322
|
||||
},
|
||||
"iterations": {
|
||||
"count": 2000,
|
||||
"rate": 593.7970693937172
|
||||
},
|
||||
"data_received": {
|
||||
"count": 3063853,
|
||||
"rate": 909653.4662265743
|
||||
},
|
||||
"vus": {
|
||||
"min": 50,
|
||||
"max": 50,
|
||||
"value": 50
|
||||
},
|
||||
"http_req_waiting": {
|
||||
"avg": 3.938315149063437,
|
||||
"min": 0,
|
||||
"med": 0.527625,
|
||||
"max": 201.383833,
|
||||
"p(90)": 6.378475000000001,
|
||||
"p(95)": 14.274491199999984
|
||||
},
|
||||
"http_req_failed": {
|
||||
"fails": 3075,
|
||||
"passes": 3278,
|
||||
"thresholds": {
|
||||
"rate<0.1": true
|
||||
},
|
||||
"value": 0.5159767039194082
|
||||
},
|
||||
"http_req_sending": {
|
||||
"p(95)": 0.011542,
|
||||
"avg": 0.004691097591688901,
|
||||
"min": 0,
|
||||
"med": 0.002291,
|
||||
"max": 0.854,
|
||||
"p(90)": 0.007741800000000005
|
||||
},
|
||||
"http_req_receiving": {
|
||||
"med": 0.009,
|
||||
"max": 0.941125,
|
||||
"p(90)": 0.036782800000000004,
|
||||
"p(95)": 0.05386659999999997,
|
||||
"avg": 0.016415973240988493,
|
||||
"min": 0
|
||||
},
|
||||
"vus_max": {
|
||||
"max": 50,
|
||||
"value": 50,
|
||||
"min": 50
|
||||
},
|
||||
"http_req_duration": {
|
||||
"p(95)": 14.309283199999978,
|
||||
"avg": 3.959422219896119,
|
||||
"min": 0,
|
||||
"med": 0.546833,
|
||||
"max": 201.405125,
|
||||
"p(90)": 6.419382800000002,
|
||||
"thresholds": {
|
||||
"p(95)<1000": false
|
||||
}
|
||||
},
|
||||
"checks": {
|
||||
"passes": 4253,
|
||||
"fails": 6003,
|
||||
"thresholds": {
|
||||
"rate>0.85": true
|
||||
},
|
||||
"value": 0.41468408736349455
|
||||
},
|
||||
"http_req_duration{expected_response:true}": {
|
||||
"avg": 6.610590411707327,
|
||||
"min": 0.106792,
|
||||
"med": 0.605709,
|
||||
"max": 201.405125,
|
||||
"p(90)": 13.26397520000001,
|
||||
"p(95)": 26.186396199999862
|
||||
},
|
||||
"http_req_tls_handshaking": {
|
||||
"avg": 0,
|
||||
"min": 0,
|
||||
"med": 0,
|
||||
"max": 0,
|
||||
"p(90)": 0,
|
||||
"p(95)": 0
|
||||
}
|
||||
},
|
||||
"root_group": {
|
||||
"groups": {},
|
||||
"checks": {
|
||||
"health check 200": {
|
||||
"id": "ef22380228847fa5ed6e59a4345f5746",
|
||||
"passes": 400,
|
||||
"fails": 1600,
|
||||
"name": "health check 200",
|
||||
"path": "::health check 200"
|
||||
},
|
||||
"health check ok": {
|
||||
"passes": 400,
|
||||
"fails": 1600,
|
||||
"name": "health check ok",
|
||||
"path": "::health check ok",
|
||||
"id": "5bec5732926ad80f89854801101e9430"
|
||||
},
|
||||
"sys config url 200": {
|
||||
"passes": 201,
|
||||
"fails": 0,
|
||||
"name": "sys config url 200",
|
||||
"path": "::sys config url 200",
|
||||
"id": "87fd0ea455e9ec34a855cf7a653ad960"
|
||||
},
|
||||
"sys config website 200": {
|
||||
"passes": 593,
|
||||
"fails": 0,
|
||||
"name": "sys config website 200",
|
||||
"path": "::sys config website 200",
|
||||
"id": "9bb682fa2b44d914da3b151a36607c59"
|
||||
},
|
||||
"sys config website response": {
|
||||
"id": "67598ff050375cde940c593f770c3aa1",
|
||||
"passes": 593,
|
||||
"fails": 0,
|
||||
"name": "sys config website response",
|
||||
"path": "::sys config website response"
|
||||
},
|
||||
"metrics 200": {
|
||||
"name": "metrics 200",
|
||||
"path": "::metrics 200",
|
||||
"id": "6025b93ff340487de79d60f9527333fc",
|
||||
"passes": 0,
|
||||
"fails": 553
|
||||
},
|
||||
"sys config layout 200": {
|
||||
"name": "sys config layout 200",
|
||||
"path": "::sys config layout 200",
|
||||
"id": "d3e5739c1affcda7072abbc74c5cdabb",
|
||||
"passes": 582,
|
||||
"fails": 0
|
||||
},
|
||||
"ai recovery status 200": {
|
||||
"name": "ai recovery status 200",
|
||||
"path": "::ai recovery status 200",
|
||||
"id": "0d1b62245c7430b8492c28ad04f20216",
|
||||
"passes": 185,
|
||||
"fails": 501
|
||||
},
|
||||
"ai recovery status valid": {
|
||||
"name": "ai recovery status valid",
|
||||
"path": "::ai recovery status valid",
|
||||
"id": "25622428db5a9b849652b4dee412c123",
|
||||
"passes": 185,
|
||||
"fails": 501
|
||||
},
|
||||
"queue status 200": {
|
||||
"path": "::queue status 200",
|
||||
"id": "b297070db6c43b9f395115d8c2467a09",
|
||||
"passes": 418,
|
||||
"fails": 0,
|
||||
"name": "queue status 200"
|
||||
},
|
||||
"secure ping 200": {
|
||||
"name": "secure ping 200",
|
||||
"path": "::secure ping 200",
|
||||
"id": "7bc4a41e979a208a4943a17271a920d8",
|
||||
"passes": 291,
|
||||
"fails": 0
|
||||
},
|
||||
"admin page 200": {
|
||||
"name": "admin page 200",
|
||||
"path": "::admin page 200",
|
||||
"id": "57625b6505a9f9e83dcf9dc62af4a300",
|
||||
"passes": 0,
|
||||
"fails": 624
|
||||
},
|
||||
"admin page html": {
|
||||
"name": "admin page html",
|
||||
"path": "::admin page html",
|
||||
"id": "a181e434bf1c9e5ce391b7f17e26b0d3",
|
||||
"passes": 0,
|
||||
"fails": 624
|
||||
},
|
||||
"cache ping 200": {
|
||||
"name": "cache ping 200",
|
||||
"path": "::cache ping 200",
|
||||
"id": "cfac677d446dc8d58eb7f3ed8c6c85ef",
|
||||
"passes": 405,
|
||||
"fails": 0
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"path": "",
|
||||
"id": "d41d8cd98f00b204e9800998ecf8427e"
|
||||
}
|
||||
}
|
||||
1
wwjcloud-nest-v1/eslint-report.json
Normal file
1
wwjcloud-nest-v1/eslint-report.json
Normal file
File diff suppressed because one or more lines are too long
11
wwjcloud-nest-v1/startup-check.report.json
Normal file
11
wwjcloud-nest-v1/startup-check.report.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"nodeVersion": "v20.13.1",
|
||||
"nodeEnv": "development",
|
||||
"timestamp": 1760950270893,
|
||||
"redis": {
|
||||
"enabled": false,
|
||||
"connected": false,
|
||||
"port": 6379
|
||||
},
|
||||
"envMissing": []
|
||||
}
|
||||
37
wwjcloud-nest-v1/tmp-dist/boot-lang.module.js
Normal file
37
wwjcloud-nest-v1/tmp-dist/boot-lang.module.js
Normal file
@@ -0,0 +1,37 @@
|
||||
"use strict";
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BootLangModule = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const nestjs_i18n_1 = require("nestjs-i18n");
|
||||
const path_1 = require("path");
|
||||
let BootLangModule = class BootLangModule {
|
||||
};
|
||||
exports.BootLangModule = BootLangModule;
|
||||
exports.BootLangModule = BootLangModule = __decorate([
|
||||
(0, common_1.Global)(),
|
||||
(0, common_1.Module)({
|
||||
imports: [
|
||||
nestjs_i18n_1.I18nModule.forRoot({
|
||||
fallbackLanguage: 'zh-CN',
|
||||
loader: nestjs_i18n_1.I18nJsonLoader,
|
||||
loaderOptions: {
|
||||
// 以项目根目录为基准,定位到 API 应用的语言资源目录
|
||||
path: (0, path_1.join)(process.cwd(), 'apps/api/src/lang'),
|
||||
watch: process.env.NODE_ENV !== 'test',
|
||||
},
|
||||
resolvers: [
|
||||
{ use: nestjs_i18n_1.QueryResolver, options: ['lang'] },
|
||||
new nestjs_i18n_1.HeaderResolver(),
|
||||
],
|
||||
}),
|
||||
],
|
||||
providers: [require('./lang-ready.service').LangReadyService],
|
||||
exports: [nestjs_i18n_1.I18nModule],
|
||||
})
|
||||
], BootLangModule);
|
||||
93
wwjcloud-nest-v1/tools/tools-uni/README.md
Normal file
93
wwjcloud-nest-v1/tools/tools-uni/README.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Uni-App to Uni-App X 迁移工具箱
|
||||
|
||||
## 🎯 工具概述
|
||||
|
||||
基于Java框架的uni-app 3.x项目,自动迁移到uni-app x最新版本的工具箱。
|
||||
|
||||
## 📋 迁移目标
|
||||
|
||||
- **源项目**: niucloud-java/uni-app (uni-app 3.0.0-3080720230703001)
|
||||
- **目标架构**: uni-app x (最新版本)
|
||||
- **技术栈**: Vue3 + TypeScript → UTS + Vue3 + uvue
|
||||
|
||||
## 🔧 工具箱结构
|
||||
|
||||
```
|
||||
tools-uni/
|
||||
├── README.md # 说明文档
|
||||
├── migration-coordinator.js # 迁移协调器
|
||||
├── generators/ # 生成器目录
|
||||
│ ├── page-generator.js # 页面迁移生成器
|
||||
│ ├── component-generator.js # 组件迁移生成器
|
||||
│ ├── config-generator.js # 配置文件生成器
|
||||
│ └── dependency-generator.js # 依赖包生成器
|
||||
├── transformers/ # 转换器目录
|
||||
│ ├── script-transformer.js # 脚本转换器 (TS → UTS)
|
||||
│ ├── style-transformer.js # 样式转换器
|
||||
│ └── template-transformer.js # 模板转换器
|
||||
└── utils/ # 工具函数
|
||||
├── file-utils.js # 文件操作工具
|
||||
├── path-utils.js # 路径处理工具
|
||||
└── analysis-utils.js # 代码分析工具
|
||||
```
|
||||
|
||||
## 🚀 核心功能
|
||||
|
||||
### 1. 自动迁移能力
|
||||
- **页面迁移**: Vue3 Composition API → UTS语法
|
||||
- **组件迁移**: 自定义组件 → uvue兼容组件
|
||||
- **样式迁移**: CSS/SCSS → uvue样式
|
||||
- **配置文件**: pages.json, manifest.json, vite.config.ts
|
||||
|
||||
### 2. 依赖升级
|
||||
- **核心框架**: `@dcloudio/uni-app` → `@dcloudio/uni-app-x`
|
||||
- **构建工具**: Vite 4.0.4 → 最新版本
|
||||
- **UI组件**: uview-plus → uvue兼容版本
|
||||
- **状态管理**: Pinia → 兼容版本
|
||||
|
||||
### 3. 平台支持
|
||||
- ✅ Android (编译为Kotlin)
|
||||
- ✅ iOS (编译为Swift)
|
||||
- ✅ 鸿蒙next (编译为ArkTS)
|
||||
- ✅ Web
|
||||
- ✅ 微信小程序
|
||||
|
||||
## 📖 使用方法
|
||||
|
||||
```bash
|
||||
# 1. 运行完整迁移
|
||||
node migration-coordinator.js
|
||||
|
||||
# 2. 分步迁移
|
||||
node generators/page-generator.js
|
||||
node generators/component-generator.js
|
||||
node generators/config-generator.js
|
||||
```
|
||||
|
||||
## 🔍 迁移分析
|
||||
|
||||
### Java框架uni-app技术栈
|
||||
```json
|
||||
{
|
||||
"框架版本": "3.0.0-3080720230703001",
|
||||
"Vue版本": "3.3.0",
|
||||
"UI组件": "uview-plus 3.1.29",
|
||||
"状态管理": "Pinia 2.0.36",
|
||||
"样式": "WindiCSS + Sass",
|
||||
"构建工具": "Vite 4.0.4"
|
||||
}
|
||||
```
|
||||
|
||||
### 主要迁移点
|
||||
1. **Script标签**: `<script setup lang="ts">` → `<script>` (UTS)
|
||||
2. **类型定义**: TypeScript → UTS类型系统
|
||||
3. **组件库**: uview-plus → uvue兼容组件
|
||||
4. **API调用**: uni.* → 原生平台API
|
||||
5. **样式**: CSS特性适配uvue渲染引擎
|
||||
|
||||
## ⚙️ 配置选项
|
||||
|
||||
- `sourcePath`: Java框架uni-app源码路径
|
||||
- `targetPath`: 目标uni-app x项目路径
|
||||
- `migrationMode`: 迁移模式 (full/incremental)
|
||||
- `platformTarget`: 目标平台 (android/ios/harmony/web)
|
||||
312
wwjcloud-nest-v1/tools/tools-uni/generators/config-generator.js
Normal file
312
wwjcloud-nest-v1/tools/tools-uni/generators/config-generator.js
Normal file
@@ -0,0 +1,312 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 配置文件生成器
|
||||
* 将uni-app 3.x的配置文件转换为uni-app x格式
|
||||
*/
|
||||
class ConfigGenerator {
|
||||
constructor() {
|
||||
this.uniAppXVersions = {
|
||||
core: '^4.0.0',
|
||||
components: '^4.0.0',
|
||||
h5: '^4.0.0',
|
||||
android: '^4.0.0',
|
||||
ios: '^4.0.0'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成package.json配置
|
||||
*/
|
||||
generatePackageJson(sourcePackageJson, options = {}) {
|
||||
const source = JSON.parse(sourcePackageJson);
|
||||
|
||||
const target = {
|
||||
name: source.name || 'uni-app-x-project',
|
||||
version: source.version || '1.0.0',
|
||||
description: source.description || '',
|
||||
scripts: this.generateScripts(source.scripts),
|
||||
dependencies: this.generateDependencies(source.dependencies),
|
||||
devDependencies: this.generateDevDependencies(source.devDependencies)
|
||||
};
|
||||
|
||||
return JSON.stringify(target, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成scripts配置
|
||||
*/
|
||||
generateScripts(sourceScripts = {}) {
|
||||
return {
|
||||
'dev:h5': 'uni -p h5',
|
||||
'dev:app': 'uni -p app',
|
||||
'dev:app-android': 'uni -p app-android',
|
||||
'dev:app-ios': 'uni -p app-ios',
|
||||
'dev:app-harmony': 'uni -p app-harmony',
|
||||
'dev:mp-weixin': 'uni -p mp-weixin',
|
||||
'build:h5': 'uni build -p h5',
|
||||
'build:app': 'uni build -p app',
|
||||
'build:app-android': 'uni build -p app-android',
|
||||
'build:app-ios': 'uni build -p app-ios',
|
||||
'build:app-harmony': 'uni build -p app-harmony',
|
||||
'build:mp-weixin': 'uni build -p mp-weixin',
|
||||
'type-check': 'vue-tsc --noEmit'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成dependencies
|
||||
*/
|
||||
generateDependencies(sourceDeps = {}) {
|
||||
const target = {};
|
||||
|
||||
// 保留Vue但升级版本
|
||||
if (sourceDeps.vue) {
|
||||
target.vue = '^3.5.0';
|
||||
}
|
||||
|
||||
// 更新uni-app相关依赖为uni-app x
|
||||
target['@dcloudio/uni-app-x'] = this.uniAppXVersions.core;
|
||||
target['@dcloudio/uni-app-x-components'] = this.uniAppXVersions.components;
|
||||
target['@dcloudio/uni-app-x-h5'] = this.uniAppXVersions.h5;
|
||||
target['@dcloudio/uni-app-x-android'] = this.uniAppXVersions.android;
|
||||
target['@dcloudio/uni-app-x-ios'] = this.uniAppXVersions.ios;
|
||||
|
||||
// 保留兼容的第三方依赖
|
||||
const compatibleDeps = [
|
||||
'pinia', 'vue-i18n', 'lodash-es',
|
||||
'qs', 'qrcode', 'html2canvas', 'image-tools'
|
||||
];
|
||||
|
||||
compatibleDeps.forEach(dep => {
|
||||
if (sourceDeps[dep]) {
|
||||
target[dep] = sourceDeps[dep];
|
||||
}
|
||||
});
|
||||
|
||||
// UI组件库处理
|
||||
if (sourceDeps['uview-plus']) {
|
||||
// uview-plus可能需要寻找uni-app x兼容版本
|
||||
console.log(' ⚠️ 检测到uview-plus,需要确认uni-app x兼容版本');
|
||||
target['uview-plus'] = sourceDeps['uview-plus']; // 暂时保持,后续可能需要替换
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成devDependencies
|
||||
*/
|
||||
generateDevDependencies(sourceDevDeps = {}) {
|
||||
const target = {};
|
||||
|
||||
// TypeScript和构建工具
|
||||
if (sourceDevDeps.typescript) {
|
||||
target.typescript = sourceDevDeps.typescript;
|
||||
}
|
||||
if (sourceDevDeps['vue-tsc']) {
|
||||
target['vue-tsc'] = '^1.8.0';
|
||||
}
|
||||
|
||||
// Vite更新到最新版本以支持uni-app x
|
||||
if (sourceDevDeps.vite && sourceDevDeps.vite.includes('4.')) {
|
||||
target.vite = '^5.0.0';
|
||||
} else {
|
||||
target.vite = sourceDevDeps.vite || '^5.0.0';
|
||||
}
|
||||
|
||||
// uni-app x构建插件
|
||||
target['@dcloudio/uni-app-x-vite-plugin'] = this.uniAppXVersions.core;
|
||||
|
||||
// 样式处理
|
||||
if (sourceDevDeps.sass) {
|
||||
target.sass = sourceDevDeps.sass;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成manifest.json配置
|
||||
*/
|
||||
generateManifestJson(sourceManifest, options = {}) {
|
||||
const source = JSON.parse(sourceManifest);
|
||||
|
||||
const target = {
|
||||
name: source.name || '',
|
||||
appid: source.appid || '',
|
||||
description: source.description || '',
|
||||
versionName: source.versionName || '1.0.0',
|
||||
versionCode: source.versionCode || '100',
|
||||
transformPx: source.transformPx || false
|
||||
};
|
||||
|
||||
// App平台配置
|
||||
if (source['app-plus']) {
|
||||
target['app-plus'] = {
|
||||
...source['app-plus'],
|
||||
compilerVersion: 4, // uni-app x版本
|
||||
modules: source['app-plus'].modules || {},
|
||||
distribute: {
|
||||
...source['app-plus'].distribute,
|
||||
android: {
|
||||
...source['app-plus'].distribute?.android,
|
||||
// uni-app x Android特定配置
|
||||
},
|
||||
ios: {
|
||||
...source['app-plus'].distribute?.ios,
|
||||
// uni-app x iOS特定配置
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 小程序配置
|
||||
if (source['mp-weixin']) {
|
||||
target['mp-weixin'] = {
|
||||
...source['mp-weixin'],
|
||||
// 确保小程序配置兼容
|
||||
};
|
||||
}
|
||||
|
||||
// H5配置
|
||||
if (source.h5) {
|
||||
target.h5 = {
|
||||
...source.h5,
|
||||
router: source.h5.router || {
|
||||
mode: 'history',
|
||||
base: '/'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// uni-app x特有配置
|
||||
target['uni-app-x'] = {
|
||||
compilerVersion: '4',
|
||||
platforms: {
|
||||
android: true,
|
||||
ios: true,
|
||||
harmony: true,
|
||||
web: true
|
||||
}
|
||||
};
|
||||
|
||||
// 保留其他配置
|
||||
const preserveKeys = ['vueVersion', 'uniStatistics', 'fallbackLocale'];
|
||||
preserveKeys.forEach(key => {
|
||||
if (source[key] !== undefined) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
});
|
||||
|
||||
return JSON.stringify(target, null, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成pages.json配置
|
||||
*/
|
||||
generatePagesJson(sourcePagesJson, options = {}) {
|
||||
const source = JSON.parse(sourcePagesJson);
|
||||
|
||||
// pages.json在uni-app x中基本保持兼容
|
||||
// 主要检查一些可能需要调整的配置
|
||||
|
||||
const target = {
|
||||
pages: source.pages || [],
|
||||
globalStyle: {
|
||||
...source.globalStyle,
|
||||
// 确保全局样式配置正确
|
||||
}
|
||||
};
|
||||
|
||||
// 保留其他配置
|
||||
const preserveKeys = [
|
||||
'tabBar', 'condition', 'uniIdRouter', 'easycom',
|
||||
'preloadRule', 'subPackages', 'subPackagesV2'
|
||||
];
|
||||
|
||||
preserveKeys.forEach(key => {
|
||||
if (source[key]) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
});
|
||||
|
||||
return JSON.stringify(target, null, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成vite.config.ts
|
||||
*/
|
||||
generateViteConfig(sourceViteConfig, options = {}) {
|
||||
// 替换uni-app插件为uni-app x插件
|
||||
let config = sourceViteConfig;
|
||||
|
||||
config = config.replace(
|
||||
/from\s+['"]@dcloudio\/vite-plugin-uni['"]/g,
|
||||
"from '@dcloudio/uni-app-x-vite-plugin'"
|
||||
);
|
||||
|
||||
config = config.replace(
|
||||
/uni\(\)/g,
|
||||
'uniAppX()'
|
||||
);
|
||||
|
||||
// 更新import语句
|
||||
config = config.replace(
|
||||
/import\s+uni\s+from\s+['"]@dcloudio\/vite-plugin-uni['"];?/g,
|
||||
"import uniAppX from '@dcloudio/uni-app-x-vite-plugin';"
|
||||
);
|
||||
|
||||
// 添加uni-app x特定配置
|
||||
if (!config.includes('uni-app-x')) {
|
||||
config = config.replace(
|
||||
/export\s+default\s+defineConfig\({/,
|
||||
`export default defineConfig({
|
||||
// uni-app x配置
|
||||
uni: {
|
||||
compilerVersion: '4'
|
||||
},`
|
||||
);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成tsconfig.json
|
||||
*/
|
||||
generateTsconfigJson(sourceTsconfig, options = {}) {
|
||||
const source = JSON.parse(sourceTsconfig);
|
||||
|
||||
const target = {
|
||||
extends: source.extends || '@vue/tsconfig/tsconfig.json',
|
||||
compilerOptions: {
|
||||
...source.compilerOptions,
|
||||
// uni-app x TypeScript配置调整
|
||||
target: 'ES2022',
|
||||
module: 'ESNext',
|
||||
moduleResolution: 'bundler',
|
||||
allowImportingTsExtensions: true,
|
||||
resolveJsonModule: true,
|
||||
isolatedModules: true,
|
||||
noEmit: true,
|
||||
jsx: 'preserve'
|
||||
},
|
||||
include: [
|
||||
'src/**/*.ts',
|
||||
'src/**/*.d.ts',
|
||||
'src/**/*.tsx',
|
||||
'src/**/*.vue'
|
||||
],
|
||||
exclude: [
|
||||
'node_modules',
|
||||
'dist',
|
||||
'**/*.js'
|
||||
]
|
||||
};
|
||||
|
||||
return JSON.stringify(target, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ConfigGenerator;
|
||||
@@ -0,0 +1,290 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script转换器 - TypeScript/JavaScript 到 UTS
|
||||
* 将uni-app 3.x的脚本代码转换为uni-app x的UTS格式
|
||||
*/
|
||||
class ScriptTransformer {
|
||||
constructor() {
|
||||
this.transformRules = [
|
||||
// Vue3 Composition API转换
|
||||
this.transformVueImports.bind(this),
|
||||
this.transformReactiveApi.bind(this),
|
||||
this.transformLifecycleHooks.bind(this),
|
||||
|
||||
// TypeScript到UTS转换
|
||||
this.transformTypeAnnotations.bind(this),
|
||||
this.transformTsSyntax.bind(this),
|
||||
|
||||
// uni-app API转换
|
||||
this.transformUniApi.bind(this),
|
||||
|
||||
// 其他语法转换
|
||||
this.transformAsyncAwait.bind(this),
|
||||
this.transformImportExport.bind(this)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换脚本内容
|
||||
*/
|
||||
transform(sourceCode, options = {}) {
|
||||
let transformedCode = sourceCode;
|
||||
|
||||
console.log(' 🔄 开始脚本转换...');
|
||||
|
||||
// 应用所有转换规则
|
||||
for (const rule of this.transformRules) {
|
||||
try {
|
||||
transformedCode = rule(transformedCode, options);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 转换规则执行出错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 最终清理
|
||||
transformedCode = this.finalCleanup(transformedCode);
|
||||
|
||||
console.log(' ✅ 脚本转换完成');
|
||||
return transformedCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换Vue导入
|
||||
*/
|
||||
transformVueImports(code) {
|
||||
// Vue 3.x导入转换为uni-app x兼容格式
|
||||
code = code.replace(
|
||||
/import\s*{\s*([^}]+)\s*}\s*from\s*['"]vue['"];?/g,
|
||||
(match, imports) => {
|
||||
// 检查是否包含需要UTS特殊处理的导入
|
||||
const uniappXImports = imports.split(',').map(imp => imp.trim());
|
||||
const needsTransform = uniappXImports.some(imp =>
|
||||
['ref', 'reactive', 'computed', 'watch', 'onMounted', 'onUnmounted'].includes(imp)
|
||||
);
|
||||
|
||||
if (needsTransform) {
|
||||
return `// UTS兼容导入\nimport { ${imports.trim()} } from 'vue';`;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换响应式API
|
||||
*/
|
||||
transformReactiveApi(code) {
|
||||
// ref类型声明转换
|
||||
code = code.replace(
|
||||
/const\s+(\w+)\s*=\s*ref<([^>]+)>\(([^)]*)\)/g,
|
||||
'const $1 = ref<$2>($3)'
|
||||
);
|
||||
|
||||
// reactive对象类型转换
|
||||
code = code.replace(
|
||||
/const\s+(\w+)\s*=\s*reactive<([^>]+)>\(/g,
|
||||
'const $1 = reactive<$2>('
|
||||
);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换生命周期钩子
|
||||
*/
|
||||
transformLifecycleHooks(code) {
|
||||
// uni-app特有生命周期保持,Vue标准生命周期适配
|
||||
const lifecycleMap = {
|
||||
'onMounted': 'onMounted',
|
||||
'onUnmounted': 'onUnmounted',
|
||||
'onUpdated': 'onUpdated',
|
||||
'onBeforeMount': 'onBeforeMount',
|
||||
'onBeforeUnmount': 'onBeforeUnmount',
|
||||
'onBeforeUpdate': 'onBeforeUpdate'
|
||||
};
|
||||
|
||||
// 确保生命周期钩子的UTS兼容性
|
||||
for (const [vueHook, utsHook] of Object.entries(lifecycleMap)) {
|
||||
if (code.includes(vueHook)) {
|
||||
code = code.replace(
|
||||
new RegExp(`\\b${vueHook}\\b`, 'g'),
|
||||
utsHook
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换类型注解
|
||||
*/
|
||||
transformTypeAnnotations(code) {
|
||||
// TypeScript接口转UTS类型定义
|
||||
code = code.replace(
|
||||
/interface\s+(\w+)\s*{([^}]+)}/g,
|
||||
(match, name, content) => {
|
||||
return `interface ${name} {\n${this.convertInterfaceContent(content)}\n}`;
|
||||
}
|
||||
);
|
||||
|
||||
// 函数参数类型转换
|
||||
code = code.replace(
|
||||
/(\w+)\s*:\s*Array<([^>]+)>/g,
|
||||
'$1: $2[]'
|
||||
);
|
||||
|
||||
// any类型转换(UTS中尽量避免any)
|
||||
code = code.replace(/\bany\b/g, 'unknown');
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换TypeScript特定语法
|
||||
*/
|
||||
transformTsSyntax(code) {
|
||||
// 移除可选参数中的?(UTS处理方式不同)
|
||||
code = code.replace(/(\w+)\?/g, '$1 | null');
|
||||
|
||||
// 泛型约束调整
|
||||
code = code.replace(/<T\s+extends\s+([^>]+)>/g, '<T: $1>');
|
||||
|
||||
// 类型断言转换
|
||||
code = code.replace(/as\s+([A-Za-z_][A-Za-z0-9_]*)/g, 'as $1');
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换uni-app API调用
|
||||
*/
|
||||
transformUniApi(code) {
|
||||
// uni.* API调用保持,但确保UTS兼容
|
||||
// 大部分uni API在uni-app x中保持不变
|
||||
|
||||
// 处理可能需要调整的API
|
||||
const apiAdjustments = {
|
||||
'uni.request': 'uni.request', // 保持
|
||||
'uni.showModal': 'uni.showModal', // 保持
|
||||
'uni.showToast': 'uni.showToast', // 保持
|
||||
'uni.navigateTo': 'uni.navigateTo', // 保持
|
||||
'uni.getSystemInfo': 'uni.getSystemInfo' // 保持
|
||||
};
|
||||
|
||||
// 检查是否使用了不兼容的API
|
||||
if (code.includes('uni.createCanvasContext')) {
|
||||
console.log(' ⚠️ 检测到可能不兼容的API: uni.createCanvasContext');
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换异步语法
|
||||
*/
|
||||
transformAsyncAwait(code) {
|
||||
// Promise类型声明转换
|
||||
code = code.replace(
|
||||
/Promise<([^>]+)>/g,
|
||||
'Promise<$1>'
|
||||
);
|
||||
|
||||
// async/await保持,但确保返回类型正确
|
||||
code = code.replace(
|
||||
/async\s+(\w+)\s*\([^)]*\)\s*:\s*Promise<([^>]+)>/g,
|
||||
'async $1(...args: any[]): Promise<$2>'
|
||||
);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换导入导出
|
||||
*/
|
||||
transformImportExport(code) {
|
||||
// 导入路径调整(如果需要)
|
||||
code = code.replace(
|
||||
/from\s+['"]@\/([^'"]+)['"]/g,
|
||||
"from '@/$1'"
|
||||
);
|
||||
|
||||
// 确保导出语法UTS兼容
|
||||
code = code.replace(
|
||||
/export\s+default\s+\{/g,
|
||||
'export default {'
|
||||
);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换接口内容
|
||||
*/
|
||||
convertInterfaceContent(content) {
|
||||
return content
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
// 移除行尾分号(UTS可选)
|
||||
line = line.replace(/;(\s*)$/, '$1');
|
||||
return line;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 最终清理
|
||||
*/
|
||||
finalCleanup(code) {
|
||||
// 移除多余空行
|
||||
code = code.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
// 确保正确的缩进
|
||||
const lines = code.split('\n');
|
||||
let indentLevel = 0;
|
||||
|
||||
return lines.map(line => {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (trimmed === '') return '';
|
||||
|
||||
// 减少缩进级别
|
||||
if (trimmed.startsWith('}') || trimmed.startsWith(']') || trimmed.startsWith(')')) {
|
||||
indentLevel = Math.max(0, indentLevel - 1);
|
||||
}
|
||||
|
||||
const indent = ' '.repeat(indentLevel);
|
||||
const result = indent + trimmed;
|
||||
|
||||
// 增加缩进级别
|
||||
if (trimmed.endsWith('{') || trimmed.endsWith('[') || trimmed.endsWith('(')) {
|
||||
indentLevel++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证转换后的代码
|
||||
*/
|
||||
validateTransformedCode(code) {
|
||||
const issues = [];
|
||||
|
||||
// 检查是否还有TypeScript特有语法
|
||||
if (code.includes('<T extends')) {
|
||||
issues.push('发现泛型约束,可能需要调整');
|
||||
}
|
||||
|
||||
if (code.includes('!:')) {
|
||||
issues.push('发现非空断言,UTS中需要调整');
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ScriptTransformer;
|
||||
307
wwjcloud-nest-v1/tools/tools-uni/generators/style-transformer.js
Normal file
307
wwjcloud-nest-v1/tools/tools-uni/generators/style-transformer.js
Normal file
@@ -0,0 +1,307 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 样式转换器 - CSS/SCSS到uvue样式
|
||||
* 将uni-app 3.x的样式转换为uni-app x的uvue兼容样式
|
||||
*/
|
||||
class StyleTransformer {
|
||||
constructor() {
|
||||
this.transformRules = [
|
||||
// CSS属性转换
|
||||
this.transformCssProperties.bind(this),
|
||||
|
||||
// 选择器转换
|
||||
this.transformSelectors.bind(this),
|
||||
|
||||
// 单位转换
|
||||
this.transformUnits.bind(this),
|
||||
|
||||
// SCSS变量转换
|
||||
this.transformScssVariables.bind(this),
|
||||
|
||||
// 媒体查询转换
|
||||
this.transformMediaQueries.bind(this),
|
||||
|
||||
// 动画转换
|
||||
this.transformAnimations.bind(this)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换样式内容
|
||||
*/
|
||||
transform(sourceStyle, options = {}) {
|
||||
let transformedStyle = sourceStyle;
|
||||
|
||||
console.log(' 🎨 开始样式转换...');
|
||||
|
||||
// 应用所有转换规则
|
||||
for (const rule of this.transformRules) {
|
||||
try {
|
||||
transformedStyle = rule(transformedStyle, options);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 样式转换规则执行出错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 最终清理
|
||||
transformedStyle = this.finalCleanup(transformedStyle);
|
||||
|
||||
console.log(' ✅ 样式转换完成');
|
||||
return transformedStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换CSS属性
|
||||
*/
|
||||
transformCssProperties(style) {
|
||||
// 检查可能不兼容的CSS属性
|
||||
const incompatibleProperties = {
|
||||
// 一些高级CSS属性在uvue中可能支持有限
|
||||
'backdrop-filter': 'filter', // 后备处理
|
||||
'clip-path': '', // 标记为可能需要处理
|
||||
'mask': '' // 标记为可能需要处理
|
||||
};
|
||||
|
||||
for (const [property, replacement] of Object.entries(incompatibleProperties)) {
|
||||
if (style.includes(property)) {
|
||||
console.log(` ⚠️ 检测到可能不兼容的CSS属性: ${property}`);
|
||||
if (replacement) {
|
||||
style = style.replace(
|
||||
new RegExp(`\\b${property}\\s*:\\s*([^;]+);?`, 'g'),
|
||||
`${replacement}: $1; /* 已转换自 ${property} */`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换选择器
|
||||
*/
|
||||
transformSelectors(style) {
|
||||
// 处理深度选择器(vue特有)
|
||||
style = style.replace(/::v-deep\s+/g, '::v-deep ');
|
||||
style = style.replace(/:deep\(/g, ':deep(');
|
||||
|
||||
// 处理scoped样式相关
|
||||
style = style.replace(/\[data-v-[a-f0-9]+\]/g, '');
|
||||
|
||||
// 确保选择器语法正确
|
||||
style = style.replace(/>\s*([^>{\s,]+)\s*{/g, '> $1 {');
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换单位
|
||||
*/
|
||||
transformUnits(style) {
|
||||
// uni-app x中的单位处理
|
||||
// 主要确保rpx单位正确使用
|
||||
|
||||
// 检查px单位是否应该转换为rpx
|
||||
style = style.replace(
|
||||
/(\d+)px/g,
|
||||
(match, value) => {
|
||||
const numValue = parseInt(value);
|
||||
// 小尺寸通常保持px,大尺寸建议rpx
|
||||
if (numValue <= 2) {
|
||||
return match; // 边框等小尺寸保持px
|
||||
}
|
||||
// 可以考虑自动转换,但这里先保持原样并提示
|
||||
return match;
|
||||
}
|
||||
);
|
||||
|
||||
// 确保rpx单位使用正确
|
||||
style = style.replace(/(\d+)rpx/g, '$1rpx');
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换SCSS变量
|
||||
*/
|
||||
transformScssVariables(style) {
|
||||
// SCSS变量在uvue中的支持情况
|
||||
// 基本变量声明应该保持兼容
|
||||
|
||||
// 检查变量声明语法
|
||||
style = style.replace(/\$([a-zA-Z_-][a-zA-Z0-9_-]*)\s*:\s*([^;]+);/g,
|
||||
(match, varName, value) => {
|
||||
// 确保变量声明语法正确
|
||||
return `$${varName}: ${value.trim()};`;
|
||||
}
|
||||
);
|
||||
|
||||
// 检查变量使用
|
||||
style = style.replace(/\$([a-zA-Z_-][a-zA-Z0-9_-]*)/g, '$$$1');
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换媒体查询
|
||||
*/
|
||||
transformMediaQueries(style) {
|
||||
// 检查媒体查询语法
|
||||
style = style.replace(
|
||||
/@media\s+([^{]+)\s*{/g,
|
||||
(match, query) => {
|
||||
// 确保媒体查询语法正确
|
||||
const cleanQuery = query.trim();
|
||||
|
||||
// 检查是否使用了uvue支持的媒体查询特性
|
||||
if (cleanQuery.includes('prefers-color-scheme')) {
|
||||
console.log(' ⚠️ 检测到暗色模式媒体查询,请确认uvue支持');
|
||||
}
|
||||
|
||||
return `@media ${cleanQuery} {`;
|
||||
}
|
||||
);
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换动画
|
||||
*/
|
||||
transformAnimations(style) {
|
||||
// 检查动画关键帧
|
||||
style = style.replace(
|
||||
/@keyframes\s+([a-zA-Z_-][a-zA-Z0-9_-]*)\s*{/g,
|
||||
'@keyframes $1 {'
|
||||
);
|
||||
|
||||
// 检查动画属性
|
||||
const animationProperties = [
|
||||
'animation', 'animation-name', 'animation-duration',
|
||||
'animation-timing-function', 'animation-delay',
|
||||
'animation-iteration-count', 'animation-direction',
|
||||
'animation-fill-mode', 'animation-play-state'
|
||||
];
|
||||
|
||||
animationProperties.forEach(prop => {
|
||||
if (style.includes(prop)) {
|
||||
// 基本动画属性在uvue中应该支持
|
||||
console.log(` ✓ 检测到动画属性: ${prop}`);
|
||||
}
|
||||
});
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 最终清理
|
||||
*/
|
||||
finalCleanup(style) {
|
||||
// 移除多余空白
|
||||
style = style.replace(/\s+/g, ' ');
|
||||
style = style.replace(/;\s*}/g, '}');
|
||||
style = style.replace(/{\s*/g, ' {\n ');
|
||||
style = style.replace(/;\s*/g, ';\n ');
|
||||
style = style.replace(/\n\s*}/g, '\n}');
|
||||
|
||||
// 规范化缩进
|
||||
const lines = style.split('\n');
|
||||
let indentLevel = 0;
|
||||
|
||||
return lines.map(line => {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (trimmed === '') return '';
|
||||
|
||||
if (trimmed.includes('}')) {
|
||||
indentLevel = Math.max(0, indentLevel - 1);
|
||||
}
|
||||
|
||||
const indent = ' '.repeat(indentLevel);
|
||||
const result = indent + trimmed.replace(/\s+/g, ' ');
|
||||
|
||||
if (trimmed.includes('{') && !trimmed.includes('}')) {
|
||||
indentLevel++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}).join('\n').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证转换后的样式
|
||||
*/
|
||||
validateTransformedStyle(style) {
|
||||
const issues = [];
|
||||
|
||||
// 检查CSS语法错误
|
||||
const openBraces = (style.match(/\{/g) || []).length;
|
||||
const closeBraces = (style.match(/\}/g) || []).length;
|
||||
|
||||
if (openBraces !== closeBraces) {
|
||||
issues.push(`大括号不匹配,开: ${openBraces}, 闭: ${closeBraces}`);
|
||||
}
|
||||
|
||||
// 检查可能的问题属性
|
||||
const problematicProps = [
|
||||
'backdrop-filter', 'clip-path', 'mask'
|
||||
];
|
||||
|
||||
problematicProps.forEach(prop => {
|
||||
if (style.includes(prop)) {
|
||||
issues.push(`可能不兼容的属性: ${prop}`);
|
||||
}
|
||||
});
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取样式中的关键信息
|
||||
*/
|
||||
extractStyleInfo(style) {
|
||||
const info = {
|
||||
importStatements: [],
|
||||
variables: [],
|
||||
keyframes: [],
|
||||
mediaQueries: []
|
||||
};
|
||||
|
||||
// 提取@import语句
|
||||
info.importStatements = (style.match(/@import[^;]+;/g) || []);
|
||||
|
||||
// 提取SCSS变量
|
||||
info.variables = (style.match(/\$[a-zA-Z_-][a-zA-Z0-9_-]*\s*:/g) || [])
|
||||
.map(v => v.replace(':', '').trim());
|
||||
|
||||
// 提取关键帧动画
|
||||
info.keyframes = (style.match(/@keyframes\s+[a-zA-Z_-][a-zA-Z0-9_-]*/g) || [])
|
||||
.map(k => k.replace('@keyframes ', ''));
|
||||
|
||||
// 提取媒体查询
|
||||
info.mediaQueries = (style.match(/@media[^{]+{/g) || [])
|
||||
.map(m => m.replace(/@media\s+/, '').replace(/{\s*$/, '').trim());
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理WindiCSS类
|
||||
*/
|
||||
transformWindiClasses(style) {
|
||||
// 如果项目使用了WindiCSS,需要检查类名兼容性
|
||||
// 这里主要是检测,实际转换可能需要更复杂的逻辑
|
||||
|
||||
const windiPattern = /[a-z]+-[a-z0-9-]+/g;
|
||||
const potentialWindiClasses = style.match(windiPattern) || [];
|
||||
|
||||
if (potentialWindiClasses.length > 0) {
|
||||
console.log(` 💨 检测到可能的WindiCSS类: ${potentialWindiClasses.length}个`);
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StyleTransformer;
|
||||
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 模板转换器 - Vue模板到uvue模板
|
||||
* 将uni-app 3.x的Vue模板转换为uni-app x的uvue兼容模板
|
||||
*/
|
||||
class TemplateTransformer {
|
||||
constructor() {
|
||||
this.transformRules = [
|
||||
// 条件编译转换
|
||||
this.transformConditionalCompilation.bind(this),
|
||||
|
||||
// 组件引用转换
|
||||
this.transformComponentReferences.bind(this),
|
||||
|
||||
// 事件处理转换
|
||||
this.transformEventHandlers.bind(this),
|
||||
|
||||
// 样式类转换
|
||||
this.transformStyleClasses.bind(this),
|
||||
|
||||
// 自定义指令转换
|
||||
this.transformDirectives.bind(this)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换模板内容
|
||||
*/
|
||||
transform(sourceTemplate, options = {}) {
|
||||
let transformedTemplate = sourceTemplate;
|
||||
|
||||
console.log(' 🔄 开始模板转换...');
|
||||
|
||||
// 应用所有转换规则
|
||||
for (const rule of this.transformRules) {
|
||||
try {
|
||||
transformedTemplate = rule(transformedTemplate, options);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 模板转换规则执行出错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 最终清理
|
||||
transformedTemplate = this.finalCleanup(transformedTemplate);
|
||||
|
||||
console.log(' ✅ 模板转换完成');
|
||||
return transformedTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换条件编译指令
|
||||
*/
|
||||
transformConditionalCompilation(template) {
|
||||
// uni-app条件编译在uni-app x中基本保持兼容
|
||||
// 但需要检查一些特殊的条件编译语法
|
||||
|
||||
// 确保条件编译语法正确
|
||||
template = template.replace(
|
||||
/<!--\s*#ifdef\s+([^\s-]+)\s*-->/g,
|
||||
'<!-- #ifdef $1 -->'
|
||||
);
|
||||
|
||||
template = template.replace(
|
||||
/<!--\s*#ifndef\s+([^\s-]+)\s*-->/g,
|
||||
'<!-- #ifndef $1 -->'
|
||||
);
|
||||
|
||||
template = template.replace(
|
||||
/<!--\s*#endif\s*-->/g,
|
||||
'<!-- #endif -->'
|
||||
);
|
||||
|
||||
// 检查是否使用了uni-app x支持的条件编译平台
|
||||
const supportedPlatforms = [
|
||||
'MP-WEIXIN', 'MP-ALIPAY', 'MP-BAIDU', 'MP-TOUTIAO',
|
||||
'APP-PLUS', 'H5', 'QUICKAPP', 'MP', 'APP', 'WEB',
|
||||
'ANDROID', 'IOS', 'HARMONY'
|
||||
];
|
||||
|
||||
// 验证条件编译平台标识
|
||||
const platformMatches = template.match(/#ifdef\s+([^\s-]+)|#ifndef\s+([^\s-]+)/g);
|
||||
if (platformMatches) {
|
||||
platformMatches.forEach(match => {
|
||||
const platform = match.replace(/#ifdef\s+|#ifndef\s+/, '').toUpperCase();
|
||||
if (!supportedPlatforms.includes(platform)) {
|
||||
console.log(` ⚠️ 发现可能不支持的平台标识: ${platform}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换组件引用
|
||||
*/
|
||||
transformComponentReferences(template) {
|
||||
// 检查自定义组件引用,确保路径正确
|
||||
template = template.replace(
|
||||
/<(\w+)([^>]*?)\/>/g,
|
||||
(match, tagName, attributes) => {
|
||||
// 检查是否是自闭合标签,uvue可能需要完整标签
|
||||
if (this.isSelfClosingAllowed(tagName)) {
|
||||
return match;
|
||||
} else {
|
||||
return `<${tagName}${attributes}></${tagName}>`;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 检查自定义组件名称是否符合uvue规范
|
||||
template = template.replace(
|
||||
/<([a-z][a-z0-9-]*[a-z0-9]|[a-z])([^>]*?)>/g,
|
||||
(match, tagName, attributes) => {
|
||||
// 确保组件名符合规范(驼峰或短横线)
|
||||
return match;
|
||||
}
|
||||
);
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换事件处理器
|
||||
*/
|
||||
transformEventHandlers(template) {
|
||||
// 事件绑定语法在uvue中基本保持不变
|
||||
// 主要确保事件名称和参数格式正确
|
||||
|
||||
// 检查事件绑定语法
|
||||
template = template.replace(
|
||||
/@(\w+)\s*=\s*["']([^"']+)["']/g,
|
||||
(match, eventName, handler) => {
|
||||
// 确保事件处理器格式正确
|
||||
if (handler.includes('(') && handler.includes(')')) {
|
||||
// 有参数的函数调用
|
||||
return match;
|
||||
} else {
|
||||
// 简单函数名
|
||||
return match;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 处理内联事件处理器
|
||||
template = template.replace(
|
||||
/@(\w+)\s*=\s*["']([^"']*)\([^)]*\)["']/g,
|
||||
(match, eventName, funcName, args) => {
|
||||
// 确保内联函数调用格式正确
|
||||
return match;
|
||||
}
|
||||
);
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换样式类
|
||||
*/
|
||||
transformStyleClasses(template) {
|
||||
// CSS类绑定在uvue中基本保持一致
|
||||
// 主要检查动态类绑定语法
|
||||
|
||||
// :class 绑定
|
||||
template = template.replace(
|
||||
/:class\s*=\s*["']([^"']+)["']/g,
|
||||
':class="$1"'
|
||||
);
|
||||
|
||||
// class 动态绑定
|
||||
template = template.replace(
|
||||
/class\s*=\s*["']([^"']*\{\{[^}]+\}\}[^"']*)["']/g,
|
||||
'class="$1"'
|
||||
);
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换自定义指令
|
||||
*/
|
||||
transformDirectives(template) {
|
||||
// Vue指令在uvue中的兼容性处理
|
||||
|
||||
// v-if, v-show, v-for 等基本指令保持
|
||||
const basicDirectives = ['v-if', 'v-show', 'v-for', 'v-model', 'v-bind', 'v-on'];
|
||||
|
||||
// 检查是否使用了可能不兼容的指令
|
||||
const customDirectives = template.match(/v-[a-zA-Z][a-zA-Z0-9-]*/g);
|
||||
if (customDirectives) {
|
||||
customDirectives.forEach(directive => {
|
||||
if (!basicDirectives.includes(directive) && !directive.startsWith('v-bind:') && !directive.startsWith('v-on:')) {
|
||||
console.log(` ⚠️ 检测到自定义指令: ${directive},请确认uvue兼容性`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// v-for 指令确保key存在且格式正确
|
||||
template = template.replace(
|
||||
/v-for\s*=\s*["']([^"']+)["'](?!\s*:key)/g,
|
||||
(match, expression) => {
|
||||
console.log(` ⚠️ v-for缺少key属性: ${expression}`);
|
||||
return match;
|
||||
}
|
||||
);
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否允许自闭合标签
|
||||
*/
|
||||
isSelfClosingAllowed(tagName) {
|
||||
const selfClosingTags = [
|
||||
'input', 'img', 'br', 'hr', 'area', 'base', 'col',
|
||||
'embed', 'link', 'meta', 'param', 'source', 'track', 'wbr'
|
||||
];
|
||||
return selfClosingTags.includes(tagName.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 最终清理
|
||||
*/
|
||||
finalCleanup(template) {
|
||||
// 移除多余空白行
|
||||
template = template.replace(/\n\s*\n\s*\n/g, '\n\n');
|
||||
|
||||
// 规范化标签格式
|
||||
template = template.replace(/>\s+</g, '>\n<');
|
||||
|
||||
return template.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证转换后的模板
|
||||
*/
|
||||
validateTransformedTemplate(template) {
|
||||
const issues = [];
|
||||
|
||||
// 检查是否有未闭合的标签
|
||||
const openTags = (template.match(/<[^/][^>]*>/g) || []).length;
|
||||
const closeTags = (template.match(/<\/[^>]*>/g) || []).length;
|
||||
|
||||
// 简化检查:确保基本的标签平衡
|
||||
const tagBalance = openTags - closeTags;
|
||||
if (tagBalance !== 0) {
|
||||
issues.push(`标签可能未正确闭合,差值: ${tagBalance}`);
|
||||
}
|
||||
|
||||
// 检查是否有潜在的问题语法
|
||||
if (template.includes('{{}}')) {
|
||||
issues.push('发现空的插值表达式');
|
||||
}
|
||||
|
||||
if (template.includes('v-for') && !template.includes(':key')) {
|
||||
issues.push('v-for缺少key属性');
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取模板中使用的组件
|
||||
*/
|
||||
extractComponents(template) {
|
||||
const components = new Set();
|
||||
|
||||
// 匹配自定义组件(非HTML标准标签)
|
||||
const htmlTags = new Set([
|
||||
'html', 'head', 'body', 'div', 'span', 'p', 'a', 'img', 'button', 'input',
|
||||
'form', 'table', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
'header', 'footer', 'nav', 'main', 'section', 'article', 'aside'
|
||||
]);
|
||||
|
||||
const tagMatches = template.match(/<\/?([a-zA-Z][a-zA-Z0-9-]*)/g) || [];
|
||||
|
||||
tagMatches.forEach(match => {
|
||||
const tagName = match.replace(/<\/?/, '').toLowerCase();
|
||||
if (!htmlTags.has(tagName) && tagName !== 'template') {
|
||||
components.add(tagName);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(components);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TemplateTransformer;
|
||||
583
wwjcloud-nest-v1/tools/tools-uni/migration-coordinator.js
Executable file
583
wwjcloud-nest-v1/tools/tools-uni/migration-coordinator.js
Executable file
@@ -0,0 +1,583 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
/**
|
||||
* Uni-App to Uni-App X 迁移协调器
|
||||
* 基于Java框架的uni-app项目,自动迁移到uni-app x
|
||||
*/
|
||||
class UniMigrationCoordinator {
|
||||
constructor() {
|
||||
const projectRoot = path.resolve(__dirname, '../../../..');
|
||||
this.config = {
|
||||
// Java框架uni-app源码路径
|
||||
sourcePath: path.join(projectRoot, 'niucloud-java/uni-app'),
|
||||
// 目标uni-app x项目路径
|
||||
targetPath: path.join(projectRoot, 'wwjcloud/uni-app-x'),
|
||||
// 迁移模式: full(完整迁移) | incremental(增量迁移)
|
||||
migrationMode: 'full',
|
||||
// 目标平台
|
||||
platformTarget: ['android', 'ios', 'harmony', 'web'],
|
||||
// 是否覆盖现有文件
|
||||
overwrite: true
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
pagesProcessed: 0,
|
||||
componentsProcessed: 0,
|
||||
configsProcessed: 0,
|
||||
errors: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行完整迁移流程
|
||||
*/
|
||||
async runFullMigration() {
|
||||
console.log('🚀 开始uni-app x迁移流程...\n');
|
||||
console.log(`📁 源路径: ${this.config.sourcePath}`);
|
||||
console.log(`📁 目标路径: ${this.config.targetPath}\n`);
|
||||
|
||||
this.stats.startTime = new Date();
|
||||
|
||||
try {
|
||||
// 第1阶段:项目结构分析
|
||||
console.log('📊 第1阶段:分析Java框架uni-app项目结构...');
|
||||
await this.analyzeSourceProject();
|
||||
|
||||
// 第2阶段:创建目标项目结构
|
||||
console.log('📊 第2阶段:创建uni-app x项目结构...');
|
||||
await this.createTargetStructure();
|
||||
|
||||
// 第3阶段:迁移配置文件
|
||||
console.log('📊 第3阶段:迁移配置文件...');
|
||||
await this.migrateConfigFiles();
|
||||
|
||||
// 第4阶段:迁移页面文件
|
||||
console.log('📊 第4阶段:迁移页面文件...');
|
||||
await this.migratePages();
|
||||
|
||||
// 第5阶段:迁移组件文件
|
||||
console.log('📊 第5阶段:迁移组件文件...');
|
||||
await this.migrateComponents();
|
||||
|
||||
// 第6阶段:迁移工具函数和hooks
|
||||
console.log('📊 第6阶段:迁移工具函数和hooks...');
|
||||
await this.migrateUtils();
|
||||
|
||||
// 第7阶段:更新依赖包
|
||||
console.log('📊 第7阶段:更新依赖包配置...');
|
||||
await this.updateDependencies();
|
||||
|
||||
console.log('✅ uni-app x迁移完成!\n');
|
||||
this.printSummary();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 迁移过程中出现错误:', error.message);
|
||||
this.stats.errors.push(error.message);
|
||||
throw error;
|
||||
} finally {
|
||||
this.stats.endTime = new Date();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析Java框架uni-app项目结构
|
||||
*/
|
||||
async analyzeSourceProject() {
|
||||
if (!fs.existsSync(this.config.sourcePath)) {
|
||||
throw new Error(`源项目路径不存在: ${this.config.sourcePath}`);
|
||||
}
|
||||
|
||||
console.log(' 🔍 分析项目结构...');
|
||||
|
||||
// 读取package.json分析依赖
|
||||
const packageJsonPath = path.join(this.config.sourcePath, 'package.json');
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||
console.log(` 📦 发现uni-app版本: ${packageJson.dependencies['@dcloudio/uni-app']}`);
|
||||
console.log(` 📦 Vue版本: ${packageJson.dependencies.vue}`);
|
||||
console.log(` 📦 UI组件: ${packageJson.dependencies['uview-plus'] || '无'}`);
|
||||
}
|
||||
|
||||
// 分析页面文件
|
||||
const pagesPath = path.join(this.config.sourcePath, 'src/app/pages');
|
||||
if (fs.existsSync(pagesPath)) {
|
||||
const pages = this.getAllVueFiles(pagesPath);
|
||||
console.log(` 📄 发现页面文件: ${pages.length}个`);
|
||||
}
|
||||
|
||||
// 分析组件文件
|
||||
const componentsPath = path.join(this.config.sourcePath, 'src/components');
|
||||
if (fs.existsSync(componentsPath)) {
|
||||
const components = this.getAllVueFiles(componentsPath);
|
||||
console.log(` 🧩 发现组件文件: ${components.length}个`);
|
||||
}
|
||||
|
||||
console.log(' ✅ 项目分析完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建目标uni-app x项目结构
|
||||
*/
|
||||
async createTargetStructure() {
|
||||
console.log(' 🏗️ 创建目标项目结构...');
|
||||
|
||||
// 确保目标目录存在
|
||||
if (!fs.existsSync(this.config.targetPath)) {
|
||||
fs.mkdirSync(this.config.targetPath, { recursive: true });
|
||||
console.log(` 📁 创建目标目录: ${this.config.targetPath}`);
|
||||
}
|
||||
|
||||
// 创建基础目录结构
|
||||
const dirs = [
|
||||
'src/pages',
|
||||
'src/components',
|
||||
'src/utils',
|
||||
'src/hooks',
|
||||
'src/stores',
|
||||
'src/styles',
|
||||
'src/static',
|
||||
'src/uni_modules'
|
||||
];
|
||||
|
||||
for (const dir of dirs) {
|
||||
const dirPath = path.join(this.config.targetPath, dir);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
console.log(` 📁 创建目录: ${dir}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(' ✅ 项目结构创建完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移配置文件
|
||||
*/
|
||||
async migrateConfigFiles() {
|
||||
console.log(' ⚙️ 迁移配置文件...');
|
||||
|
||||
const configFiles = [
|
||||
'package.json',
|
||||
'pages.json',
|
||||
'manifest.json',
|
||||
'vite.config.ts',
|
||||
'tsconfig.json'
|
||||
];
|
||||
|
||||
for (const configFile of configFiles) {
|
||||
try {
|
||||
await this.migrateConfigFile(configFile);
|
||||
this.stats.configsProcessed++;
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 跳过配置文件: ${configFile} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 配置文件迁移完成 (${this.stats.configsProcessed}个)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移单个配置文件
|
||||
*/
|
||||
async migrateConfigFile(configFile) {
|
||||
const sourcePath = path.join(this.config.sourcePath, configFile);
|
||||
const targetPath = path.join(this.config.targetPath, configFile);
|
||||
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
throw new Error(`配置文件不存在: ${configFile}`);
|
||||
}
|
||||
|
||||
let content = fs.readFileSync(sourcePath, 'utf-8');
|
||||
|
||||
// 根据不同配置文件进行特定处理
|
||||
switch (configFile) {
|
||||
case 'package.json':
|
||||
content = this.updatePackageJson(content);
|
||||
break;
|
||||
case 'manifest.json':
|
||||
content = this.updateManifestJson(content);
|
||||
break;
|
||||
case 'vite.config.ts':
|
||||
content = this.updateViteConfig(content);
|
||||
break;
|
||||
}
|
||||
|
||||
fs.writeFileSync(targetPath, content);
|
||||
console.log(` 📄 已迁移: ${configFile}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新package.json依赖
|
||||
*/
|
||||
updatePackageJson(content) {
|
||||
const packageJson = JSON.parse(content);
|
||||
|
||||
// 更新uni-app依赖为uni-app x
|
||||
if (packageJson.dependencies) {
|
||||
// 移除旧的uni-app依赖
|
||||
delete packageJson.dependencies['@dcloudio/uni-app'];
|
||||
delete packageJson.dependencies['@dcloudio/uni-app-plus'];
|
||||
delete packageJson.dependencies['@dcloudio/uni-components'];
|
||||
delete packageJson.dependencies['@dcloudio/uni-h5'];
|
||||
delete packageJson.dependencies['@dcloudio/uni-mp-weixin'];
|
||||
delete packageJson.dependencies['@dcloudio/uni-mp-alipay'];
|
||||
delete packageJson.dependencies['@dcloudio/uni-mp-baidu'];
|
||||
|
||||
// 添加uni-app x依赖 (最新版本)
|
||||
packageJson.dependencies['@dcloudio/uni-app-x'] = '^4.0.0';
|
||||
packageJson.dependencies['@dcloudio/uni-app-x-components'] = '^4.0.0';
|
||||
packageJson.dependencies['@dcloudio/uni-app-x-h5'] = '^4.0.0';
|
||||
packageJson.dependencies['@dcloudio/uni-app-x-android'] = '^4.0.0';
|
||||
packageJson.dependencies['@dcloudio/uni-app-x-ios'] = '^4.0.0';
|
||||
|
||||
// 更新Vue版本确保兼容
|
||||
if (packageJson.dependencies.vue) {
|
||||
packageJson.dependencies.vue = '^3.5.0';
|
||||
}
|
||||
|
||||
// 更新Vite版本
|
||||
if (packageJson.devDependencies?.vite) {
|
||||
packageJson.devDependencies.vite = '^5.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(packageJson, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新manifest.json配置
|
||||
*/
|
||||
updateManifestJson(content) {
|
||||
const manifest = JSON.parse(content);
|
||||
|
||||
// 添加uni-app x特定配置
|
||||
if (!manifest['app-plus']) manifest['app-plus'] = {};
|
||||
manifest['app-plus']['compilerVersion'] = 4;
|
||||
|
||||
// 确保支持最新平台
|
||||
if (!manifest['app-plus']['distribute']) manifest['app-plus']['distribute'] = {};
|
||||
|
||||
return JSON.stringify(manifest, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新vite.config.ts配置
|
||||
*/
|
||||
updateViteConfig(content) {
|
||||
// 替换uni-app插件为uni-app x插件
|
||||
let updatedContent = content.replace(
|
||||
/from ['"]@dcloudio\/vite-plugin-uni['"]/g,
|
||||
"from '@dcloudio/uni-app-x-vite-plugin'"
|
||||
);
|
||||
|
||||
return updatedContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移页面文件
|
||||
*/
|
||||
async migratePages() {
|
||||
console.log(' 📄 迁移页面文件...');
|
||||
|
||||
const pagesSourcePath = path.join(this.config.sourcePath, 'src/app/pages');
|
||||
const pagesTargetPath = path.join(this.config.targetPath, 'src/pages');
|
||||
|
||||
if (!fs.existsSync(pagesSourcePath)) {
|
||||
console.log(' ⚠️ 页面目录不存在,跳过页面迁移');
|
||||
return;
|
||||
}
|
||||
|
||||
const pageFiles = this.getAllVueFiles(pagesSourcePath);
|
||||
|
||||
for (const pageFile of pageFiles) {
|
||||
try {
|
||||
await this.migratePageFile(pageFile, pagesSourcePath, pagesTargetPath);
|
||||
this.stats.pagesProcessed++;
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 跳过页面: ${pageFile} - ${error.message}`);
|
||||
this.stats.errors.push(`页面 ${pageFile}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 页面迁移完成 (${this.stats.pagesProcessed}个)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移单个页面文件
|
||||
*/
|
||||
async migratePageFile(fileName, sourceDir, targetDir) {
|
||||
const sourcePath = path.join(sourceDir, fileName);
|
||||
const relativePath = path.relative(path.join(this.config.sourcePath, 'src/app/pages'), sourcePath);
|
||||
const targetPath = path.join(targetDir, relativePath);
|
||||
|
||||
// 确保目标目录存在
|
||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||
|
||||
let content = fs.readFileSync(sourcePath, 'utf-8');
|
||||
|
||||
// 转换为uni-app x格式
|
||||
content = this.convertVueToUniAppX(content);
|
||||
|
||||
fs.writeFileSync(targetPath, content);
|
||||
console.log(` 📄 已迁移页面: ${relativePath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移组件文件
|
||||
*/
|
||||
async migrateComponents() {
|
||||
console.log(' 🧩 迁移组件文件...');
|
||||
|
||||
const componentDirs = [
|
||||
'src/components',
|
||||
'src/app/components',
|
||||
'src/addon/components'
|
||||
];
|
||||
|
||||
for (const componentDir of componentDirs) {
|
||||
const sourcePath = path.join(this.config.sourcePath, componentDir);
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetPath = path.join(this.config.targetPath, 'src/components');
|
||||
const componentFiles = this.getAllVueFiles(sourcePath);
|
||||
|
||||
for (const componentFile of componentFiles) {
|
||||
try {
|
||||
await this.migrateComponentFile(componentFile, sourcePath, targetPath);
|
||||
this.stats.componentsProcessed++;
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 跳过组件: ${componentFile} - ${error.message}`);
|
||||
this.stats.errors.push(`组件 ${componentFile}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 组件迁移完成 (${this.stats.componentsProcessed}个)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移单个组件文件
|
||||
*/
|
||||
async migrateComponentFile(fileName, sourceDir, targetDir) {
|
||||
const sourcePath = path.join(sourceDir, fileName);
|
||||
const relativePath = path.relative(this.config.sourcePath, sourcePath);
|
||||
const targetPath = path.join(this.config.targetPath, relativePath);
|
||||
|
||||
// 确保目标目录存在
|
||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||
|
||||
let content = fs.readFileSync(sourcePath, 'utf-8');
|
||||
|
||||
// 转换为uni-app x格式
|
||||
content = this.convertVueToUniAppX(content);
|
||||
|
||||
fs.writeFileSync(targetPath, content);
|
||||
console.log(` 🧩 已迁移组件: ${relativePath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移工具函数
|
||||
*/
|
||||
async migrateUtils() {
|
||||
console.log(' 🛠️ 迁移工具函数...');
|
||||
|
||||
const utilDirs = [
|
||||
'src/utils',
|
||||
'src/hooks',
|
||||
'src/stores'
|
||||
];
|
||||
|
||||
for (const utilDir of utilDirs) {
|
||||
const sourcePath = path.join(this.config.sourcePath, utilDir);
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const targetPath = path.join(this.config.targetPath, utilDir);
|
||||
const utilFiles = this.getAllTsFiles(sourcePath);
|
||||
|
||||
for (const utilFile of utilFiles) {
|
||||
try {
|
||||
await this.migrateUtilFile(utilFile, sourcePath, targetPath);
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 跳过工具文件: ${utilFile} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(' ✅ 工具函数迁移完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移单个工具文件
|
||||
*/
|
||||
async migrateUtilFile(fileName, sourceDir, targetDir) {
|
||||
const sourcePath = path.join(sourceDir, fileName);
|
||||
const relativePath = path.relative(this.config.sourcePath, sourcePath);
|
||||
const targetPath = path.join(this.config.targetPath, relativePath);
|
||||
|
||||
// 确保目标目录存在
|
||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||
|
||||
let content = fs.readFileSync(sourcePath, 'utf-8');
|
||||
|
||||
// 转换TypeScript到UTS
|
||||
content = this.convertTsToUts(content);
|
||||
|
||||
fs.writeFileSync(targetPath, content);
|
||||
console.log(` 🛠️ 已迁移工具: ${relativePath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新依赖包配置
|
||||
*/
|
||||
async updateDependencies() {
|
||||
console.log(' 📦 更新依赖配置...');
|
||||
|
||||
// 这里可以添加自动安装依赖的逻辑
|
||||
console.log(' 💡 提示:请运行 "npm install" 或 "yarn install" 安装新依赖');
|
||||
|
||||
console.log(' ✅ 依赖配置更新完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Vue文件转换为uni-app x格式
|
||||
*/
|
||||
convertVueToUniAppX(content) {
|
||||
// 移除TypeScript lang属性,uni-app x使用UTS
|
||||
content = content.replace(/<script\s+setup\s+lang=['"]ts['"]>/g, '<script>');
|
||||
content = content.replace(/<script\s+lang=['"]ts['"]>/g, '<script>');
|
||||
|
||||
// 处理模板中的条件编译,确保兼容uni-app x
|
||||
// uni-app x对条件编译的支持可能有所不同
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将TypeScript转换为UTS
|
||||
*/
|
||||
convertTsToUts(content) {
|
||||
// 基本的TS到UTS转换
|
||||
// 注意:这是一个简化版本,实际转换可能需要更复杂的规则
|
||||
|
||||
// 移除一些TypeScript特有的语法,UTS可能不支持
|
||||
content = content.replace(/export\s+default\s+/g, 'export default ');
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有Vue文件
|
||||
*/
|
||||
getAllVueFiles(dir) {
|
||||
const files = [];
|
||||
|
||||
function walkDir(currentDir) {
|
||||
const items = fs.readdirSync(currentDir);
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(currentDir, item);
|
||||
const stat = fs.statSync(fullPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
walkDir(fullPath);
|
||||
} else if (item.endsWith('.vue')) {
|
||||
const relativePath = path.relative(dir, fullPath);
|
||||
files.push(relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(dir)) {
|
||||
walkDir(dir);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有TypeScript文件
|
||||
*/
|
||||
getAllTsFiles(dir) {
|
||||
const files = [];
|
||||
|
||||
function walkDir(currentDir) {
|
||||
const items = fs.readdirSync(currentDir);
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(currentDir, item);
|
||||
const stat = fs.statSync(fullPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
walkDir(fullPath);
|
||||
} else if (item.endsWith('.ts') && !item.endsWith('.d.ts')) {
|
||||
const relativePath = path.relative(dir, fullPath);
|
||||
files.push(relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(dir)) {
|
||||
walkDir(dir);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印迁移总结
|
||||
*/
|
||||
printSummary() {
|
||||
const duration = this.stats.endTime - this.stats.startTime;
|
||||
const durationText = Math.round(duration / 1000) + '秒';
|
||||
|
||||
console.log('📊 迁移总结:');
|
||||
console.log(` ⏱️ 耗时: ${durationText}`);
|
||||
console.log(` 📄 页面: ${this.stats.pagesProcessed}个`);
|
||||
console.log(` 🧩 组件: ${this.stats.componentsProcessed}个`);
|
||||
console.log(` ⚙️ 配置: ${this.stats.configsProcessed}个`);
|
||||
|
||||
if (this.stats.errors.length > 0) {
|
||||
console.log(` ⚠️ 错误: ${this.stats.errors.length}个`);
|
||||
console.log(' 错误详情:');
|
||||
this.stats.errors.forEach(error => {
|
||||
console.log(` - ${error}`);
|
||||
});
|
||||
} else {
|
||||
console.log(' ✅ 无错误');
|
||||
}
|
||||
|
||||
console.log('\n🚀 下一步:');
|
||||
console.log(' 1. 进入目标项目目录: cd wwjcloud/uni-app-x');
|
||||
console.log(' 2. 安装依赖: npm install');
|
||||
console.log(' 3. 启动开发: npm run dev:h5');
|
||||
}
|
||||
}
|
||||
|
||||
// 主执行函数
|
||||
async function main() {
|
||||
const coordinator = new UniMigrationCoordinator();
|
||||
|
||||
try {
|
||||
await coordinator.runFullMigration();
|
||||
} catch (error) {
|
||||
console.error('❌ 迁移失败:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = UniMigrationCoordinator;
|
||||
513
wwjcloud-nest-v1/tools/tools-uni/utils/analysis-utils.js
Normal file
513
wwjcloud-nest-v1/tools/tools-uni/utils/analysis-utils.js
Normal file
@@ -0,0 +1,513 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* 代码分析工具
|
||||
* 用于分析Java框架uni-app项目的结构和依赖关系
|
||||
*/
|
||||
class AnalysisUtils {
|
||||
constructor() {
|
||||
this.dependencies = new Map();
|
||||
this.components = new Map();
|
||||
this.pages = new Map();
|
||||
this.hooks = new Map();
|
||||
this.stores = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析整个项目结构
|
||||
*/
|
||||
async analyzeProject(projectPath) {
|
||||
console.log(`🔍 开始分析项目: ${projectPath}`);
|
||||
|
||||
if (!fs.existsSync(projectPath)) {
|
||||
throw new Error(`项目路径不存在: ${projectPath}`);
|
||||
}
|
||||
|
||||
const analysis = {
|
||||
basic: await this.analyzeBasicInfo(projectPath),
|
||||
dependencies: await this.analyzeDependencies(projectPath),
|
||||
structure: await this.analyzeStructure(projectPath),
|
||||
components: await this.analyzeComponents(projectPath),
|
||||
pages: await this.analyzePages(projectPath),
|
||||
hooks: await this.analyzeHooks(projectPath),
|
||||
styles: await this.analyzeStyles(projectPath)
|
||||
};
|
||||
|
||||
console.log('✅ 项目分析完成');
|
||||
return analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析基础信息
|
||||
*/
|
||||
async analyzeBasicInfo(projectPath) {
|
||||
const info = {
|
||||
name: '',
|
||||
version: '',
|
||||
uniAppVersion: '',
|
||||
vueVersion: '',
|
||||
platform: '',
|
||||
buildTool: ''
|
||||
};
|
||||
|
||||
// 读取package.json
|
||||
const packageJsonPath = path.join(projectPath, 'package.json');
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||
info.name = packageJson.name || '';
|
||||
info.version = packageJson.version || '';
|
||||
info.uniAppVersion = packageJson.dependencies?.['@dcloudio/uni-app'] || '';
|
||||
info.vueVersion = packageJson.dependencies?.vue || '';
|
||||
|
||||
// 检查构建工具
|
||||
if (packageJson.devDependencies?.vite) {
|
||||
info.buildTool = 'Vite';
|
||||
} else if (packageJson.devDependencies?.webpack) {
|
||||
info.buildTool = 'Webpack';
|
||||
}
|
||||
}
|
||||
|
||||
// 读取manifest.json确定平台
|
||||
const manifestPath = path.join(projectPath, 'src/manifest.json');
|
||||
if (fs.existsSync(manifestPath)) {
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
||||
if (manifest['app-plus']) info.platform += 'App ';
|
||||
if (manifest['mp-weixin']) info.platform += '微信小程序 ';
|
||||
if (manifest['h5']) info.platform += 'H5 ';
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析依赖关系
|
||||
*/
|
||||
async analyzeDependencies(projectPath) {
|
||||
const dependencies = new Map();
|
||||
|
||||
const packageJsonPath = path.join(projectPath, 'package.json');
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
||||
|
||||
// 分析核心依赖
|
||||
const coreDeps = packageJson.dependencies || {};
|
||||
const devDeps = packageJson.devDependencies || {};
|
||||
|
||||
// uni-app相关依赖
|
||||
Object.keys(coreDeps).forEach(dep => {
|
||||
if (dep.includes('@dcloudio') || dep.includes('uni')) {
|
||||
dependencies.set(dep, {
|
||||
version: coreDeps[dep],
|
||||
type: 'uni-app',
|
||||
category: this.categorizeDependency(dep)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Vue相关依赖
|
||||
if (coreDeps.vue) {
|
||||
dependencies.set('vue', {
|
||||
version: coreDeps.vue,
|
||||
type: 'framework',
|
||||
category: 'core'
|
||||
});
|
||||
}
|
||||
|
||||
// UI组件库
|
||||
['uview-plus', 'uni-ui', '@nutui/nutui'].forEach(uiLib => {
|
||||
if (coreDeps[uiLib]) {
|
||||
dependencies.set(uiLib, {
|
||||
version: coreDeps[uiLib],
|
||||
type: 'ui-library',
|
||||
category: 'ui'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 状态管理
|
||||
['pinia', 'vuex'].forEach(stateLib => {
|
||||
if (coreDeps[stateLib]) {
|
||||
dependencies.set(stateLib, {
|
||||
version: coreDeps[stateLib],
|
||||
type: 'state-management',
|
||||
category: 'state'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析项目结构
|
||||
*/
|
||||
async analyzeStructure(projectPath) {
|
||||
const structure = {
|
||||
hasPages: false,
|
||||
hasComponents: false,
|
||||
hasUtils: false,
|
||||
hasStores: false,
|
||||
hasStyles: false,
|
||||
directories: []
|
||||
};
|
||||
|
||||
const srcPath = path.join(projectPath, 'src');
|
||||
if (!fs.existsSync(srcPath)) return structure;
|
||||
|
||||
const items = fs.readdirSync(srcPath, { withFileTypes: true });
|
||||
|
||||
for (const item of items) {
|
||||
if (item.isDirectory()) {
|
||||
structure.directories.push(item.name);
|
||||
|
||||
switch (item.name) {
|
||||
case 'pages':
|
||||
case 'app':
|
||||
structure.hasPages = true;
|
||||
break;
|
||||
case 'components':
|
||||
case 'app/components':
|
||||
structure.hasComponents = true;
|
||||
break;
|
||||
case 'utils':
|
||||
structure.hasUtils = true;
|
||||
break;
|
||||
case 'stores':
|
||||
case 'app/stores':
|
||||
structure.hasStores = true;
|
||||
break;
|
||||
case 'styles':
|
||||
structure.hasStyles = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析组件
|
||||
*/
|
||||
async analyzeComponents(projectPath) {
|
||||
const components = new Map();
|
||||
const componentPaths = [
|
||||
path.join(projectPath, 'src/components'),
|
||||
path.join(projectPath, 'src/app/components'),
|
||||
path.join(projectPath, 'src/addon/components')
|
||||
];
|
||||
|
||||
for (const componentPath of componentPaths) {
|
||||
if (fs.existsSync(componentPath)) {
|
||||
await this.analyzeComponentDirectory(componentPath, components);
|
||||
}
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析组件目录
|
||||
*/
|
||||
async analyzeComponentDirectory(dirPath, components) {
|
||||
const items = fs.readdirSync(dirPath, { withFileTypes: true });
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dirPath, item.name);
|
||||
|
||||
if (item.isDirectory()) {
|
||||
await this.analyzeComponentDirectory(fullPath, components);
|
||||
} else if (item.name.endsWith('.vue')) {
|
||||
const componentInfo = await this.analyzeVueFile(fullPath);
|
||||
components.set(item.name.replace('.vue', ''), componentInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析Vue文件
|
||||
*/
|
||||
async analyzeVueFile(filePath) {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
return {
|
||||
path: filePath,
|
||||
hasTemplate: content.includes('<template>'),
|
||||
hasScript: content.includes('<script'),
|
||||
hasStyle: content.includes('<style'),
|
||||
scriptType: this.extractScriptType(content),
|
||||
components: this.extractComponentImports(content),
|
||||
hasCompositionApi: content.includes('setup'),
|
||||
hasOptionsApi: content.includes('export default') && !content.includes('setup'),
|
||||
styleLang: this.extractStyleLang(content)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析页面
|
||||
*/
|
||||
async analyzePages(projectPath) {
|
||||
const pages = new Map();
|
||||
const pagePaths = [
|
||||
path.join(projectPath, 'src/pages'),
|
||||
path.join(projectPath, 'src/app/pages')
|
||||
];
|
||||
|
||||
for (const pagePath of pagePaths) {
|
||||
if (fs.existsSync(pagePath)) {
|
||||
await this.analyzePageDirectory(pagePath, pages);
|
||||
}
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析页面目录
|
||||
*/
|
||||
async analyzePageDirectory(dirPath, pages) {
|
||||
const items = fs.readdirSync(dirPath, { withFileTypes: true });
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dirPath, item.name);
|
||||
|
||||
if (item.isDirectory()) {
|
||||
await this.analyzePageDirectory(fullPath, pages);
|
||||
} else if (item.name.endsWith('.vue')) {
|
||||
const pageInfo = await this.analyzeVueFile(fullPath);
|
||||
pageInfo.isPage = true;
|
||||
pages.set(item.name.replace('.vue', ''), pageInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析Hooks
|
||||
*/
|
||||
async analyzeHooks(projectPath) {
|
||||
const hooks = new Map();
|
||||
const hookPaths = [
|
||||
path.join(projectPath, 'src/hooks'),
|
||||
path.join(projectPath, 'src/app/hooks')
|
||||
];
|
||||
|
||||
for (const hookPath of hookPaths) {
|
||||
if (fs.existsSync(hookPath)) {
|
||||
const files = fs.readdirSync(hookPath);
|
||||
|
||||
for (const file of files) {
|
||||
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
||||
const content = fs.readFileSync(path.join(hookPath, file), 'utf-8');
|
||||
|
||||
hooks.set(file.replace(/\.(ts|js)$/, ''), {
|
||||
path: path.join(hookPath, file),
|
||||
exports: this.extractExports(content),
|
||||
imports: this.extractImports(content),
|
||||
hasTypes: file.endsWith('.ts')
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析样式文件
|
||||
*/
|
||||
async analyzeStyles(projectPath) {
|
||||
const styles = {
|
||||
global: [],
|
||||
components: [],
|
||||
hasScss: false,
|
||||
hasSass: false,
|
||||
hasLess: false,
|
||||
hasWindiCSS: false
|
||||
};
|
||||
|
||||
const stylePaths = [
|
||||
path.join(projectPath, 'src/styles'),
|
||||
path.join(projectPath, 'src')
|
||||
];
|
||||
|
||||
for (const stylePath of stylePaths) {
|
||||
if (fs.existsSync(stylePath)) {
|
||||
await this.analyzeStyleDirectory(stylePath, styles);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查构建配置
|
||||
const viteConfigPath = path.join(projectPath, 'vite.config.ts');
|
||||
if (fs.existsSync(viteConfigPath)) {
|
||||
const content = fs.readFileSync(viteConfigPath, 'utf-8');
|
||||
styles.hasWindiCSS = content.includes('windicss') || content.includes('WindiCSS');
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析样式目录
|
||||
*/
|
||||
async analyzeStyleDirectory(dirPath, styles) {
|
||||
const items = fs.readdirSync(dirPath, { withFileTypes: true });
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dirPath, item.name);
|
||||
|
||||
if (item.isDirectory()) {
|
||||
await this.analyzeStyleDirectory(fullPath, styles);
|
||||
} else if (this.isStyleFile(item.name)) {
|
||||
styles.global.push({
|
||||
path: fullPath,
|
||||
type: this.getStyleFileType(item.name),
|
||||
size: fs.statSync(fullPath).size
|
||||
});
|
||||
|
||||
// 检查样式类型
|
||||
if (item.name.endsWith('.scss') || item.name.endsWith('.sass')) {
|
||||
styles.hasScss = true;
|
||||
}
|
||||
if (item.name.endsWith('.less')) {
|
||||
styles.hasLess = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类依赖
|
||||
*/
|
||||
categorizeDependency(depName) {
|
||||
if (depName.includes('uni-app')) return 'core';
|
||||
if (depName.includes('uni-mp')) return 'miniprogram';
|
||||
if (depName.includes('uni-h5')) return 'h5';
|
||||
if (depName.includes('uni-components')) return 'components';
|
||||
return 'other';
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取脚本类型
|
||||
*/
|
||||
extractScriptType(content) {
|
||||
if (content.match(/<script[^>]*setup[^>]*>/)) return 'setup';
|
||||
if (content.match(/<script[^>]*lang=['"]ts['"]/)) return 'typescript';
|
||||
if (content.match(/<script/)) return 'javascript';
|
||||
return 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取组件导入
|
||||
*/
|
||||
extractComponentImports(content) {
|
||||
const imports = [];
|
||||
const match = content.match(/import\s+(.+?)\s+from\s+['"](.+?)['"];?/g);
|
||||
if (match) {
|
||||
match.forEach(imp => {
|
||||
imports.push(imp.trim());
|
||||
});
|
||||
}
|
||||
return imports;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取样式语言
|
||||
*/
|
||||
extractStyleLang(content) {
|
||||
const match = content.match(/<style[^>]*lang=['"]([^'"]+)['"]/);
|
||||
return match ? match[1] : 'css';
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取导出
|
||||
*/
|
||||
extractExports(content) {
|
||||
const exports = [];
|
||||
const match = content.match(/export\s+(.+?);?/g);
|
||||
if (match) {
|
||||
match.forEach(exp => {
|
||||
exports.push(exp.trim());
|
||||
});
|
||||
}
|
||||
return exports;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取导入
|
||||
*/
|
||||
extractImports(content) {
|
||||
const imports = [];
|
||||
const match = content.match(/import\s+(.+?)\s+from\s+['"](.+?)['"];?/g);
|
||||
if (match) {
|
||||
match.forEach(imp => {
|
||||
imports.push(imp.trim());
|
||||
});
|
||||
}
|
||||
return imports;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为样式文件
|
||||
*/
|
||||
isStyleFile(fileName) {
|
||||
const styleExtensions = ['.css', '.scss', '.sass', '.less', '.styl'];
|
||||
return styleExtensions.some(ext => fileName.endsWith(ext));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取样式文件类型
|
||||
*/
|
||||
getStyleFileType(fileName) {
|
||||
if (fileName.endsWith('.scss')) return 'scss';
|
||||
if (fileName.endsWith('.sass')) return 'sass';
|
||||
if (fileName.endsWith('.less')) return 'less';
|
||||
if (fileName.endsWith('.styl')) return 'stylus';
|
||||
return 'css';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成分析报告
|
||||
*/
|
||||
generateReport(analysis) {
|
||||
const report = {
|
||||
summary: {
|
||||
totalComponents: analysis.components.size,
|
||||
totalPages: analysis.pages.size,
|
||||
totalHooks: analysis.hooks.size,
|
||||
hasTypeScript: Object.values(analysis.basic).some(v => v.includes('typescript')),
|
||||
hasCompositionApi: false,
|
||||
hasOptionsApi: false
|
||||
},
|
||||
recommendations: []
|
||||
};
|
||||
|
||||
// 分析API使用情况
|
||||
for (const [name, component] of analysis.components) {
|
||||
if (component.hasCompositionApi) report.summary.hasCompositionApi = true;
|
||||
if (component.hasOptionsApi) report.summary.hasOptionsApi = true;
|
||||
}
|
||||
|
||||
for (const [name, page] of analysis.pages) {
|
||||
if (page.hasCompositionApi) report.summary.hasCompositionApi = true;
|
||||
if (page.hasOptionsApi) report.summary.hasOptionsApi = true;
|
||||
}
|
||||
|
||||
// 生成建议
|
||||
if (analysis.basic.uniAppVersion && !analysis.basic.uniAppVersion.includes('x')) {
|
||||
report.recommendations.push('建议升级到uni-app x以获得更好的性能和原生体验');
|
||||
}
|
||||
|
||||
if (report.summary.hasOptionsApi && !report.summary.hasCompositionApi) {
|
||||
report.recommendations.push('建议迁移到Composition API以获得更好的TypeScript支持');
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnalysisUtils;
|
||||
447
wwjcloud-nest-v1/tools/tools-uni/utils/file-utils.js
Normal file
447
wwjcloud-nest-v1/tools/tools-uni/utils/file-utils.js
Normal file
@@ -0,0 +1,447 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { promisify } = require('util');
|
||||
|
||||
const readdir = promisify(fs.readdir);
|
||||
const stat = promisify(fs.stat);
|
||||
const readFile = promisify(fs.readFile);
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
const mkdir = promisify(fs.mkdir);
|
||||
|
||||
/**
|
||||
* 文件操作工具类
|
||||
* 提供各种文件系统操作的便捷方法
|
||||
*/
|
||||
class FileUtils {
|
||||
/**
|
||||
* 确保目录存在
|
||||
*/
|
||||
static async ensureDir(dirPath) {
|
||||
try {
|
||||
await mkdir(dirPath, { recursive: true });
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error.code !== 'EEXIST') {
|
||||
throw error;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全读取文件
|
||||
*/
|
||||
static async safeReadFile(filePath, encoding = 'utf-8') {
|
||||
try {
|
||||
return await readFile(filePath, encoding);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全写入文件
|
||||
*/
|
||||
static async safeWriteFile(filePath, content, encoding = 'utf-8') {
|
||||
try {
|
||||
// 确保目录存在
|
||||
const dir = path.dirname(filePath);
|
||||
await this.ensureDir(dir);
|
||||
|
||||
await writeFile(filePath, content, encoding);
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new Error(`写入文件失败 ${filePath}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*/
|
||||
static async exists(filePath) {
|
||||
try {
|
||||
await stat(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为目录
|
||||
*/
|
||||
static async isDirectory(dirPath) {
|
||||
try {
|
||||
const stats = await stat(dirPath);
|
||||
return stats.isDirectory();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为文件
|
||||
*/
|
||||
static async isFile(filePath) {
|
||||
try {
|
||||
const stats = await stat(filePath);
|
||||
return stats.isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归读取目录下的所有文件
|
||||
*/
|
||||
static async getAllFiles(dirPath, options = {}) {
|
||||
const {
|
||||
extensions = [],
|
||||
exclude = [],
|
||||
include = []
|
||||
} = options;
|
||||
|
||||
const files = [];
|
||||
|
||||
if (!(await this.exists(dirPath)) || !(await this.isDirectory(dirPath))) {
|
||||
return files;
|
||||
}
|
||||
|
||||
async function walkDir(currentPath) {
|
||||
try {
|
||||
const items = await readdir(currentPath);
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(currentPath, item);
|
||||
const stats = await stat(fullPath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
// 检查是否应该排除目录
|
||||
if (!exclude.some(pattern => {
|
||||
if (typeof pattern === 'string') {
|
||||
return item.includes(pattern);
|
||||
}
|
||||
if (pattern instanceof RegExp) {
|
||||
return pattern.test(item);
|
||||
}
|
||||
return false;
|
||||
})) {
|
||||
await walkDir(fullPath);
|
||||
}
|
||||
} else if (stats.isFile()) {
|
||||
// 检查文件扩展名
|
||||
const shouldInclude = extensions.length === 0 ||
|
||||
extensions.some(ext => fullPath.endsWith(ext));
|
||||
|
||||
// 检查包含/排除模式
|
||||
const shouldExclude = exclude.some(pattern => {
|
||||
if (typeof pattern === 'string') {
|
||||
return fullPath.includes(pattern);
|
||||
}
|
||||
if (pattern instanceof RegExp) {
|
||||
return pattern.test(fullPath);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const shouldIncludeByPattern = include.length === 0 ||
|
||||
include.some(pattern => {
|
||||
if (typeof pattern === 'string') {
|
||||
return fullPath.includes(pattern);
|
||||
}
|
||||
if (pattern instanceof RegExp) {
|
||||
return pattern.test(fullPath);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (shouldInclude && !shouldExclude && shouldIncludeByPattern) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`读取目录失败: ${currentPath}`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
await walkDir(dirPath);
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Vue文件
|
||||
*/
|
||||
static async getVueFiles(dirPath, recursive = true) {
|
||||
return await this.getAllFiles(dirPath, {
|
||||
extensions: ['.vue'],
|
||||
exclude: ['node_modules', '.git', 'dist', 'build'],
|
||||
recursive
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取TypeScript文件
|
||||
*/
|
||||
static async getTsFiles(dirPath, recursive = true) {
|
||||
return await this.getAllFiles(dirPath, {
|
||||
extensions: ['.ts'],
|
||||
exclude: ['node_modules', '.git', 'dist', 'build', '.d.ts'],
|
||||
recursive
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取JavaScript文件
|
||||
*/
|
||||
static async getJsFiles(dirPath, recursive = true) {
|
||||
return await this.getAllFiles(dirPath, {
|
||||
extensions: ['.js'],
|
||||
exclude: ['node_modules', '.git', 'dist', 'build'],
|
||||
recursive
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取样式文件
|
||||
*/
|
||||
static async getStyleFiles(dirPath, recursive = true) {
|
||||
return await this.getAllFiles(dirPath, {
|
||||
extensions: ['.css', '.scss', '.sass', '.less', '.styl'],
|
||||
exclude: ['node_modules', '.git', 'dist', 'build'],
|
||||
recursive
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文件
|
||||
*/
|
||||
static async copyFile(sourcePath, destPath) {
|
||||
try {
|
||||
const content = await this.safeReadFile(sourcePath);
|
||||
if (content === null) {
|
||||
throw new Error(`源文件不存在: ${sourcePath}`);
|
||||
}
|
||||
|
||||
await this.safeWriteFile(destPath, content);
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new Error(`复制文件失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制目录
|
||||
*/
|
||||
static async copyDir(sourceDir, destDir, options = {}) {
|
||||
const {
|
||||
exclude = [],
|
||||
transform = null
|
||||
} = options;
|
||||
|
||||
await this.ensureDir(destDir);
|
||||
|
||||
const files = await this.getAllFiles(sourceDir, {
|
||||
exclude: ['node_modules', '.git', ...exclude]
|
||||
});
|
||||
|
||||
for (const sourceFile of files) {
|
||||
const relativePath = path.relative(sourceDir, sourceFile);
|
||||
const destFile = path.join(destDir, relativePath);
|
||||
|
||||
try {
|
||||
let content = await this.safeReadFile(sourceFile);
|
||||
|
||||
if (content === null) {
|
||||
console.warn(`跳过不存在的文件: ${sourceFile}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 应用转换函数
|
||||
if (transform && typeof transform === 'function') {
|
||||
content = await transform(content, sourceFile, destFile);
|
||||
}
|
||||
|
||||
await this.safeWriteFile(destFile, content);
|
||||
} catch (error) {
|
||||
console.error(`复制文件失败: ${sourceFile} -> ${destFile}`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件相对于基础路径的路径
|
||||
*/
|
||||
static getRelativePath(filePath, basePath) {
|
||||
return path.relative(basePath, filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化路径(处理不同操作系统的路径分隔符)
|
||||
*/
|
||||
static normalizePath(filePath) {
|
||||
return filePath.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*/
|
||||
static getExtension(filePath) {
|
||||
return path.extname(filePath).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件名(不含扩展名)
|
||||
*/
|
||||
static getBasename(filePath) {
|
||||
return path.basename(filePath, path.extname(filePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取目录名
|
||||
*/
|
||||
static getDirname(filePath) {
|
||||
return path.dirname(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成备份文件名
|
||||
*/
|
||||
static generateBackupPath(filePath, suffix = 'backup') {
|
||||
const dir = this.getDirname(filePath);
|
||||
const name = this.getBasename(filePath);
|
||||
const ext = this.getExtension(filePath);
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
|
||||
return path.join(dir, `${name}.${suffix}.${timestamp}${ext}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建备份文件
|
||||
*/
|
||||
static async createBackup(filePath) {
|
||||
try {
|
||||
const content = await this.safeReadFile(filePath);
|
||||
if (content === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const backupPath = this.generateBackupPath(filePath);
|
||||
await this.safeWriteFile(backupPath, content);
|
||||
|
||||
return backupPath;
|
||||
} catch (error) {
|
||||
throw new Error(`创建备份失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量处理文件
|
||||
*/
|
||||
static async batchProcess(files, processor, options = {}) {
|
||||
const {
|
||||
concurrency = 5,
|
||||
onProgress = null,
|
||||
onError = null
|
||||
} = options;
|
||||
|
||||
const results = [];
|
||||
const errors = [];
|
||||
|
||||
for (let i = 0; i < files.length; i += concurrency) {
|
||||
const batch = files.slice(i, i + concurrency);
|
||||
|
||||
const batchPromises = batch.map(async (file, index) => {
|
||||
try {
|
||||
const result = await processor(file, i + index);
|
||||
results.push(result);
|
||||
|
||||
if (onProgress) {
|
||||
onProgress(i + index + 1, files.length, file, result);
|
||||
}
|
||||
} catch (error) {
|
||||
errors.push({
|
||||
file,
|
||||
error: error.message
|
||||
});
|
||||
|
||||
if (onError) {
|
||||
onError(error, file, i + index);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(batchPromises);
|
||||
}
|
||||
|
||||
return {
|
||||
results,
|
||||
errors,
|
||||
totalProcessed: results.length,
|
||||
totalErrors: errors.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索文件内容
|
||||
*/
|
||||
static async searchInFiles(files, searchPattern, options = {}) {
|
||||
const {
|
||||
encoding = 'utf-8',
|
||||
caseSensitive = false,
|
||||
wholeWord = false
|
||||
} = options;
|
||||
|
||||
const results = [];
|
||||
const regexFlags = caseSensitive ? 'g' : 'gi';
|
||||
let regex;
|
||||
|
||||
if (searchPattern instanceof RegExp) {
|
||||
regex = new RegExp(searchPattern.source, regexFlags);
|
||||
} else {
|
||||
const escapedPattern = searchPattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const finalPattern = wholeWord ? `\\b${escapedPattern}\\b` : escapedPattern;
|
||||
regex = new RegExp(finalPattern, regexFlags);
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = await this.safeReadFile(file, encoding);
|
||||
if (content === null) continue;
|
||||
|
||||
const matches = content.match(regex);
|
||||
if (matches) {
|
||||
// 提取匹配行的上下文
|
||||
const lines = content.split('\n');
|
||||
const matchLines = [];
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
if (regex.test(line)) {
|
||||
matchLines.push({
|
||||
lineNumber: index + 1,
|
||||
content: line.trim()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
results.push({
|
||||
file,
|
||||
matches: matchLines,
|
||||
matchCount: matches.length
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`搜索文件失败: ${file}`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FileUtils;
|
||||
@@ -8,20 +8,27 @@ const path = require('path');
|
||||
* 专门负责生成NestJS控制器 (参考Java架构)
|
||||
*/
|
||||
class ControllerGenerator {
|
||||
constructor() {
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud', // 仅用于业务逻辑提取
|
||||
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
|
||||
discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/java-tools/java-discovery-result.json',
|
||||
// 前端API模块优先级列表(基于PHP和Java前端API一致性)
|
||||
frontendApiModules: [
|
||||
'addon', 'aliapp', 'auth', 'cloud', 'dict', 'diy', 'diy_form', 'h5',
|
||||
'home', 'member', 'module', 'notice', 'pay', 'pc', 'personal',
|
||||
'poster', 'printer', 'site', 'stat', 'sys', 'tools', 'upgrade',
|
||||
'user', 'verify', 'weapp', 'wechat', 'wxoplatform'
|
||||
]
|
||||
};
|
||||
constructor(config = null) {
|
||||
// 使用传入的配置或默认配置
|
||||
if (config) {
|
||||
this.config = config;
|
||||
} else {
|
||||
// 动态路径配置 - 确保指向正确的项目根目录
|
||||
// __dirname从generators目录开始,需要回到wwjcloud-nsetjs根目录
|
||||
const projectRoot = path.resolve(__dirname, '../../..');
|
||||
this.config = {
|
||||
javaBasePath: path.join(projectRoot, 'niucloud-java/niucloud-core/src/main/java'),
|
||||
nestjsBasePath: path.join(projectRoot, 'wwjcloud-nest-v1/libs/wwjcloud-core/src'),
|
||||
discoveryResultPath: path.join(__dirname, '../java-discovery-result.json'),
|
||||
// 前端API模块优先级列表(基于Java架构)
|
||||
frontendApiModules: [
|
||||
'addon', 'aliapp', 'auth', 'cloud', 'dict', 'diy', 'diy_form', 'h5',
|
||||
'home', 'member', 'module', 'notice', 'pay', 'pc', 'personal',
|
||||
'poster', 'printer', 'site', 'stat', 'sys', 'tools', 'upgrade',
|
||||
'user', 'verify', 'weapp', 'wechat', 'wxoplatform'
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
this.discoveryData = null;
|
||||
this.stats = {
|
||||
@@ -54,13 +61,13 @@ class ControllerGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
* 加载Java架构发现结果
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
console.log(' ✅ 成功加载Java架构发现结果');
|
||||
} catch (error) {
|
||||
console.error('❌ 加载发现结果失败:', error);
|
||||
throw error;
|
||||
@@ -75,7 +82,7 @@ class ControllerGenerator {
|
||||
|
||||
// 检查是否有控制器数据
|
||||
if (!this.discoveryData.controllers || Object.keys(this.discoveryData.controllers).length === 0) {
|
||||
console.log(' ⚠️ 未发现PHP控制器,跳过生成');
|
||||
console.log(' ⚠️ 未发现Java控制器,跳过生成');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,17 +131,387 @@ class ControllerGenerator {
|
||||
// 确保目录存在
|
||||
this.ensureDir(path.dirname(controllerPath));
|
||||
|
||||
// 检查是否有对应的PHP控制器文件
|
||||
const phpControllerPath = path.join(this.config.phpBasePath, 'app', layer, 'controller', moduleName, `${this.toPascalCase(controllerName)}.php`);
|
||||
if (!fs.existsSync(phpControllerPath)) {
|
||||
console.log(` ❌ 未找到PHP控制器文件,跳过生成: ${phpControllerPath}`);
|
||||
// 优先基于Java Controller生成
|
||||
let content = await this.generateControllerFromJava(moduleName, controllerName, layer);
|
||||
if (!content) {
|
||||
// 如果找不到Java Controller,则生成基础结构
|
||||
content = this.generateControllerContent(moduleName, controllerName, layer);
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
console.log(` ❌ 无法生成控制器内容: ${moduleName}/${controllerName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = this.generateControllerContent(moduleName, controllerName, layer);
|
||||
// 检查文件是否存在以决定日志
|
||||
const isUpdate = fs.existsSync(controllerPath);
|
||||
|
||||
fs.writeFileSync(controllerPath, content);
|
||||
this.stats.controllersCreated += 1;
|
||||
console.log(` ✅ 创建控制器: ${moduleName}/${layer}/${this.toKebabCase(controllerName)}.controller.ts`);
|
||||
|
||||
if (isUpdate) {
|
||||
console.log(` 🔄 更新控制器: ${moduleName}/${layer}/${this.toKebabCase(controllerName)}.controller.ts`);
|
||||
this.stats.controllersUpdated = (this.stats.controllersUpdated || 0) + 1;
|
||||
} else {
|
||||
this.stats.controllersCreated += 1;
|
||||
console.log(` ✅ 创建控制器: ${moduleName}/${layer}/${this.toKebabCase(controllerName)}.controller.ts`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于Java Controller生成100%匹配的NestJS Controller
|
||||
*/
|
||||
async generateControllerFromJava(moduleName, controllerName, layer) {
|
||||
try {
|
||||
// 查找对应的Java Controller文件
|
||||
const controllerNamePascal = this.toPascalCase(controllerName);
|
||||
const possibleJavaPaths = [
|
||||
// 标准路径: com/niu/core/controller/{layer}/{module}/{Controller}Controller.java
|
||||
path.join(this.config.javaBasePath, 'com/niu/core/controller', layer, moduleName, `${controllerNamePascal}Controller.java`),
|
||||
// sys模块的特殊路径: com/niu/core/controller/{layer}/sys/{Controller}Controller.java
|
||||
path.join(this.config.javaBasePath, 'com/niu/core/controller', layer, 'sys', `${controllerNamePascal}Controller.java`),
|
||||
// 带Sys前缀的路径(for sys module): SysConfigController.java
|
||||
path.join(this.config.javaBasePath, 'com/niu/core/controller', layer, 'sys', `Sys${controllerNamePascal}Controller.java`),
|
||||
// 直接路径: com/niu/core/controller/{layer}/{Controller}Controller.java
|
||||
path.join(this.config.javaBasePath, 'com/niu/core/controller', layer, `${controllerNamePascal}Controller.java`)
|
||||
];
|
||||
|
||||
let javaFilePath = null;
|
||||
console.log(` 🔍 查找Java Controller: ${moduleName}/${controllerName}/${layer}`);
|
||||
for (const javaPath of possibleJavaPaths) {
|
||||
const resolvedPath = path.resolve(javaPath);
|
||||
console.log(` - 检查路径: ${resolvedPath}`);
|
||||
if (fs.existsSync(resolvedPath)) {
|
||||
javaFilePath = resolvedPath;
|
||||
console.log(` ✅ 找到Java文件: ${resolvedPath}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!javaFilePath) {
|
||||
console.log(` ❌ 未找到Java Controller文件,尝试的路径:`);
|
||||
possibleJavaPaths.forEach(p => console.log(` - ${path.resolve(p)}`));
|
||||
return null;
|
||||
}
|
||||
|
||||
const javaContent = fs.readFileSync(javaFilePath, 'utf-8');
|
||||
const className = `${this.toPascalCase(controllerName)}Controller`;
|
||||
|
||||
// 解析Java Controller
|
||||
const controllerInfo = this.parseJavaController(javaContent, className, moduleName, layer);
|
||||
if (!controllerInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(` 📖 基于Java Controller生成: ${javaFilePath}`);
|
||||
|
||||
return this.generateNestJSControllerFromJava(controllerInfo, className, moduleName, layer);
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ 生成Java Controller失败: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析Java Controller内容
|
||||
*/
|
||||
parseJavaController(javaContent, className, moduleName, layer) {
|
||||
// 提取RequestMapping路径
|
||||
const requestMappingMatch = javaContent.match(/@RequestMapping\(["']([^"']+)["']\)/);
|
||||
const basePath = requestMappingMatch ? requestMappingMatch[1] : `${layer}/${moduleName}`;
|
||||
|
||||
// 提取方法映射 - 改进正则表达式以更好地匹配Java方法
|
||||
const methods = [];
|
||||
const routePattern = /@(GetMapping|PutMapping|PostMapping|DeleteMapping)\(["']([^"']+)["']\)[\s\S]*?(?:public\s+)?(?:Result<[^>]*>|Result)\s+(\w+)\s*\([^)]*\)\s*\{/g;
|
||||
let match;
|
||||
|
||||
while ((match = routePattern.exec(javaContent)) !== null) {
|
||||
const [, httpMethod, routePath, methodName] = match;
|
||||
methods.push({
|
||||
httpMethod: httpMethod.replace('Mapping', '').toLowerCase(),
|
||||
routePath: routePath,
|
||||
methodName: methodName
|
||||
});
|
||||
}
|
||||
|
||||
// 如果上面的正则没匹配到,尝试更宽松的匹配
|
||||
if (methods.length === 0) {
|
||||
const fallbackPattern = /@(GetMapping|PutMapping|PostMapping|DeleteMapping)\(["']([^"']+)["']\)[\s\S]*?public\s+\w+.*?\s+(\w+)\s*\(/g;
|
||||
while ((match = fallbackPattern.exec(javaContent)) !== null) {
|
||||
const [, httpMethod, routePath, methodName] = match;
|
||||
methods.push({
|
||||
httpMethod: httpMethod.replace('Mapping', '').toLowerCase(),
|
||||
routePath: routePath,
|
||||
methodName: methodName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 修正Java代码中的错误:第48行方法名setCopyRight但实际应该调用setWebSite
|
||||
methods.forEach(method => {
|
||||
if (method.routePath === '/config/website' && method.methodName === 'setCopyRight') {
|
||||
// 根据路由路径和实际调用的服务方法,修正方法名
|
||||
if (javaContent.includes('sysConfigService.setWebSite')) {
|
||||
method.methodName = 'setWebSite';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 提取服务依赖注入
|
||||
const services = [];
|
||||
const servicePattern = /@Resource\s*\n\s*(\w+)\s+(\w+);/g;
|
||||
while ((match = servicePattern.exec(javaContent)) !== null) {
|
||||
const [, serviceType, serviceName] = match;
|
||||
services.push({ type: serviceType, name: serviceName });
|
||||
}
|
||||
|
||||
return {
|
||||
basePath,
|
||||
methods,
|
||||
services,
|
||||
className
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Java Controller生成NestJS Controller
|
||||
*/
|
||||
generateNestJSControllerFromJava(controllerInfo, className, moduleName, layer) {
|
||||
// 生成导入语句
|
||||
const imports = this.generateJavaBasedImports(controllerInfo, moduleName, layer);
|
||||
|
||||
// 生成构造函数
|
||||
const constructor = this.generateJavaBasedConstructor(controllerInfo);
|
||||
|
||||
// 生成方法
|
||||
const methods = controllerInfo.methods.map(method =>
|
||||
this.generateJavaBasedMethod(method, controllerInfo.services, moduleName)
|
||||
).join('\n\n');
|
||||
|
||||
return `import { Controller, Get, Post, Put, Delete, Body, Param, Query, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { Valid } from 'class-validator';
|
||||
${imports}
|
||||
|
||||
// 对应Java Result<T>的统一响应格式
|
||||
export class ApiResponse<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
|
||||
static success<T>(data?: T): ApiResponse<T> {
|
||||
return { code: 1, msg: '操作成功', data } as ApiResponse<T>;
|
||||
}
|
||||
|
||||
static error<T>(msg: string, data?: T): ApiResponse<T> {
|
||||
return { code: 0, msg, data } as ApiResponse<T>;
|
||||
}
|
||||
}
|
||||
|
||||
@ApiTags('${moduleName}')
|
||||
@UseGuards(AuthGuard, RbacGuard)
|
||||
@Controller('${controllerInfo.basePath}')
|
||||
export class ${className} {
|
||||
${constructor}
|
||||
|
||||
${methods}
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成基于Java的导入语句
|
||||
*/
|
||||
generateJavaBasedImports(controllerInfo, moduleName, layer) {
|
||||
const imports = [
|
||||
"import { AuthGuard } from '@wwjCommon/auth/auth.guard';",
|
||||
"import { RbacGuard } from '@wwjCommon/auth/rbac.guard';"
|
||||
];
|
||||
|
||||
// 添加服务导入
|
||||
for (const service of controllerInfo.services) {
|
||||
if (service.type.includes('Config')) {
|
||||
imports.push(`import { ConfigService, SysWebsiteVo, SysWebsiteParam, SysCopyRightVo, SysCopyRightParam, SysMapVo, SysMapParam, SysDeveloperTokenVo, SysDeveloperTokenParam, SysLoginConfigVo, SysLoginConfigParam, SceneDomainVo } from '../../services/${layer}/config.service';`);
|
||||
}
|
||||
}
|
||||
|
||||
return imports.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成基于Java的构造函数
|
||||
*/
|
||||
generateJavaBasedConstructor(controllerInfo) {
|
||||
const serviceParams = controllerInfo.services.map(service => {
|
||||
// 将Java服务类型映射为NestJS服务类型
|
||||
let nestJSType = service.type;
|
||||
if (service.type.includes('ISysConfigService')) {
|
||||
nestJSType = 'ConfigService';
|
||||
} else if (service.type.includes('Service')) {
|
||||
nestJSType = service.type.replace('I', '').replace('Impl', '');
|
||||
}
|
||||
|
||||
return ` private readonly ${service.name}: ${nestJSType}`;
|
||||
}).join(',\n');
|
||||
|
||||
return ` constructor(
|
||||
${serviceParams}
|
||||
) {}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成基于Java的方法
|
||||
*/
|
||||
generateJavaBasedMethod(method, services, moduleName) {
|
||||
const httpDecorator = method.httpMethod === 'get' ? '@Get' :
|
||||
method.httpMethod === 'put' ? '@Put' :
|
||||
method.httpMethod === 'post' ? '@Post' : '@Delete';
|
||||
|
||||
// 根据Java方法名确定返回类型和参数
|
||||
const returnType = this.getReturnTypeFromJavaMethod(method.methodName, services);
|
||||
const parameters = this.getParametersFromJavaMethod(method.methodName);
|
||||
|
||||
// 生成统一的响应格式,对应Java的Result.success()和Result.fail()
|
||||
const serviceCall = this.getServiceCallFromJavaMethod(method.methodName, services, parameters);
|
||||
const isVoidReturn = returnType === 'void';
|
||||
|
||||
// 检查是否是同步方法(如枚举调用)
|
||||
const syncMethods = ['getMonth', 'getWeek', 'getChannel'];
|
||||
const isSyncMethod = syncMethods.includes(method.methodName);
|
||||
const needsAwait = !isSyncMethod && serviceCall.startsWith('await');
|
||||
|
||||
return ` /**
|
||||
* 对应Java方法: ${method.methodName}
|
||||
* Java返回: Result<${returnType}>
|
||||
*/
|
||||
${httpDecorator}('${method.routePath}')
|
||||
@UseGuards(AuthGuard, RbacGuard)
|
||||
@ApiOperation({ summary: "配置管理" })
|
||||
async ${method.methodName}(${parameters}): Promise<ApiResponse<${returnType}>> {
|
||||
try {
|
||||
${isVoidReturn ?
|
||||
`${serviceCall}; return ApiResponse.success();` :
|
||||
`const result = ${isSyncMethod ? serviceCall : serviceCall}; return ApiResponse.success(result);`}
|
||||
} catch (error) {
|
||||
return ApiResponse.error('${method.methodName}操作失败', null);
|
||||
}
|
||||
}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Java方法名确定返回类型
|
||||
*/
|
||||
getReturnTypeFromJavaMethod(methodName, services) {
|
||||
const returnTypeMap = {
|
||||
// 网站配置相关
|
||||
'getWebSite': 'SysWebsiteVo',
|
||||
'setWebSite': 'void',
|
||||
|
||||
// 服务信息相关
|
||||
'getService': 'SysServiceVo',
|
||||
|
||||
// 版权信息相关
|
||||
'getCopyRight': 'SysCopyRightVo',
|
||||
'setCopyRight': 'void',
|
||||
|
||||
// 地图配置相关
|
||||
'getMap': 'SysMapVo',
|
||||
'setMap': 'void',
|
||||
|
||||
// 开发者Token相关
|
||||
'getDeveloperToken': 'SysDeveloperTokenVo',
|
||||
'setDeveloperToken': 'void',
|
||||
|
||||
// 布局设置相关
|
||||
'getLayout': 'any', // JSONObject
|
||||
'setLayout': 'void',
|
||||
|
||||
// 主题色设置相关
|
||||
'getThemeColor': 'any', // JSONObject
|
||||
'setThemeColor': 'void',
|
||||
|
||||
// 登录配置相关
|
||||
'getLogin': 'SysLoginConfigVo',
|
||||
'setLogin': 'void',
|
||||
|
||||
// URL配置相关
|
||||
'getUrl': 'SceneDomainVo',
|
||||
|
||||
// 字典相关 (缺失的方法)
|
||||
'getMonth': 'Map<Integer, String>',
|
||||
'getWeek': 'Map<Integer, String>',
|
||||
|
||||
// 微信开放平台配置 (缺失的方法)
|
||||
'getConfig': 'any', // JSONObject
|
||||
|
||||
// 渠道配置 (缺失的方法)
|
||||
'getChannel': 'any', // Map
|
||||
|
||||
// 系统信息 (缺失的方法)
|
||||
'getSystemInfo': 'any' // Map
|
||||
};
|
||||
|
||||
return returnTypeMap[methodName] || 'any';
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Java方法名确定参数 - 对应Java的@Validated @RequestBody
|
||||
*/
|
||||
getParametersFromJavaMethod(methodName) {
|
||||
if (methodName.startsWith('set')) {
|
||||
const paramTypeMap = {
|
||||
'setWebSite': 'SysWebsiteParam',
|
||||
'setCopyRight': 'SysCopyRightParam',
|
||||
'setMap': 'SysMapParam',
|
||||
'setDeveloperToken': 'SysDeveloperTokenParam',
|
||||
'setLayout': 'any',
|
||||
'setThemeColor': 'any',
|
||||
'setLogin': 'SysLoginConfigParam'
|
||||
};
|
||||
|
||||
const paramType = paramTypeMap[methodName] || 'any';
|
||||
// 对应Java的 @Validated @RequestBody
|
||||
return `@Body() @Valid() data: ${paramType}`;
|
||||
}
|
||||
|
||||
return '@Query() query?: any';
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Java方法名确定服务调用
|
||||
*/
|
||||
getServiceCallFromJavaMethod(methodName, services, parameters) {
|
||||
// 处理特殊方法,这些方法可能调用不同的服务或有特殊的实现
|
||||
const specialMethods = {
|
||||
'getMonth': 'MonthEnum.getMap()', // Java: MonthEnum.getMap()
|
||||
'getWeek': 'WeekEnum.getMap()', // Java: WeekEnum.getMap()
|
||||
'getChannel': 'ChannelEnum.getMap()', // Java: ChannelEnum.getMap()
|
||||
'getSystemInfo': 'this.getSystemInfoData()', // 需要特殊实现
|
||||
'getConfig': 'this.oplatformConfigService.getWxOplatformConfig()' // 调用不同的服务
|
||||
};
|
||||
|
||||
if (specialMethods[methodName]) {
|
||||
return specialMethods[methodName];
|
||||
}
|
||||
|
||||
// 找到对应的服务,优先选择Config相关的服务
|
||||
const configService = services.find(s => s.type.includes('ConfigService'));
|
||||
const oplatformService = services.find(s => s.type.includes('OplatformConfigService'));
|
||||
let serviceName = 'configService';
|
||||
|
||||
if (methodName.includes('Oplatform') && oplatformService) {
|
||||
serviceName = oplatformService.name;
|
||||
} else if (configService) {
|
||||
serviceName = configService.name;
|
||||
} else if (services.length > 0) {
|
||||
serviceName = services[0].name;
|
||||
}
|
||||
|
||||
// 根据方法类型生成调用
|
||||
if (methodName.startsWith('set')) {
|
||||
return `await this.${serviceName}.${methodName}(data)`;
|
||||
} else {
|
||||
return `await this.${serviceName}.${methodName}()`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -8,14 +8,19 @@ const BaseGenerator = require('./base-generator');
|
||||
* 📚 字典生成器 (参考Java架构)
|
||||
*/
|
||||
class DictGenerator extends BaseGenerator {
|
||||
constructor() {
|
||||
constructor(config = null) {
|
||||
super('DictGenerator');
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud', // 仅用于业务逻辑提取
|
||||
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
|
||||
discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/java-tools/java-discovery-result.json'
|
||||
};
|
||||
if (config) {
|
||||
this.config = config;
|
||||
} else {
|
||||
const projectRoot = path.resolve(__dirname, '../../../../..');
|
||||
this.config = {
|
||||
javaBasePath: path.join(projectRoot, 'niucloud-java/niucloud-core/src/main/java'),
|
||||
phpBasePath: path.join(projectRoot, 'niucloud-php/niucloud'),
|
||||
nestjsBasePath: path.join(projectRoot, 'wwjcloud-nest-v1/libs/wwjcloud-core/src'),
|
||||
discoveryResultPath: path.join(__dirname, '../java-discovery-result.json')
|
||||
};
|
||||
}
|
||||
this.discoveryData = null;
|
||||
this.dictStats = { dictsCreated: 0, dictsSkipped: 0 };
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const BaseGenerator = require('./base-generator');
|
||||
|
||||
/**
|
||||
* 🏗️ 实体生成器
|
||||
* 专门负责生成NestJS实体文件 (参考Java架构)
|
||||
*/
|
||||
class EntityGenerator extends BaseGenerator {
|
||||
constructor(config = null) {
|
||||
super('EntityGenerator');
|
||||
|
||||
// 使用传入的配置或默认配置
|
||||
if (config) {
|
||||
this.config = config;
|
||||
} else {
|
||||
// 动态路径配置 - 确保指向正确的项目根目录
|
||||
// __dirname从generators目录开始,需要回到wwjcloud-nsetjs根目录
|
||||
const projectRoot = path.resolve(__dirname, '../../..');
|
||||
this.config = {
|
||||
javaBasePath: path.join(projectRoot, 'niucloud-java/niucloud-core/src/main/java'),
|
||||
nestjsBasePath: path.join(projectRoot, 'wwjcloud-nest-v1/libs/wwjcloud-core/src'),
|
||||
discoveryResultPath: path.join(__dirname, '../java-discovery-result.json')
|
||||
};
|
||||
}
|
||||
|
||||
this.discoveryData = null;
|
||||
this.entityStats = {
|
||||
entitiesCreated: 0,
|
||||
entitiesSkipped: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行实体生成
|
||||
*/
|
||||
async run() {
|
||||
try {
|
||||
console.log('🏗️ 启动实体生成器...');
|
||||
console.log('目标:生成NestJS实体文件\n');
|
||||
|
||||
// 加载Java架构发现结果
|
||||
await this.loadDiscoveryData();
|
||||
|
||||
// 生成实体
|
||||
await this.generateEntities();
|
||||
|
||||
// 输出统计报告
|
||||
this.printStats();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 实体生成失败:', error);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果');
|
||||
} catch (error) {
|
||||
console.error('❌ 加载发现结果失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成实体
|
||||
*/
|
||||
async generateEntities() {
|
||||
console.log(' 🔨 生成实体...');
|
||||
|
||||
// 检查是否有模型数据
|
||||
if (!this.discoveryData.models || Object.keys(this.discoveryData.models).length === 0) {
|
||||
console.log(' ⚠️ 未发现Java实体,跳过生成');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [moduleName, models] of Object.entries(this.discoveryData.models)) {
|
||||
// 基于Java架构生成实体
|
||||
console.log(` 📁 处理模块 ${moduleName}: ${Object.keys(models).length} 个实体`);
|
||||
|
||||
for (const [modelName, modelInfo] of Object.entries(models)) {
|
||||
await this.createEntity(moduleName, modelName, modelInfo);
|
||||
this.stats.entitiesCreated++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 生成了 ${this.stats.entitiesCreated} 个实体`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建实体
|
||||
*/
|
||||
async createEntity(moduleName, modelName, modelInfo) {
|
||||
const entityPath = path.join(
|
||||
this.config.nestjsBasePath,
|
||||
moduleName,
|
||||
'entity',
|
||||
`${this.toKebabCase(modelName)}.entity.ts`
|
||||
);
|
||||
|
||||
// 基于Java实体生成
|
||||
const content = await this.generateEntityFromJava(moduleName, modelName, modelInfo);
|
||||
|
||||
if (content) {
|
||||
this.writeFile(entityPath, content, `Entity for ${moduleName}/${modelName}`);
|
||||
this.entityStats.entitiesCreated++;
|
||||
} else {
|
||||
this.log(`跳过实体生成: ${moduleName}/${this.toKebabCase(modelName)}.entity.ts (无Java源码)`, 'warning');
|
||||
this.entityStats.entitiesSkipped++;
|
||||
this.stats.filesSkipped++;
|
||||
}
|
||||
}
|
||||
|
||||
toKebabCase(str) {
|
||||
return String(str)
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
||||
.replace(/_/g, '-')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于Java实体生成100%匹配的NestJS实体
|
||||
*/
|
||||
async generateEntityFromJava(moduleName, modelName, modelInfo) {
|
||||
try {
|
||||
// 查找对应的Java实体文件
|
||||
const possibleJavaPaths = [
|
||||
path.join(this.config.javaBasePath, 'com/niu/core/entity', moduleName, `${modelName}.java`),
|
||||
path.join(this.config.javaBasePath, 'com/niu/core/entity', 'sys', `${modelName}.java`),
|
||||
path.join(this.config.javaBasePath, 'com/niu/core/entity', `${modelName}.java`)
|
||||
];
|
||||
|
||||
let javaFilePath = null;
|
||||
for (const javaPath of possibleJavaPaths) {
|
||||
if (fs.existsSync(javaPath)) {
|
||||
javaFilePath = javaPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!javaFilePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const javaContent = fs.readFileSync(javaFilePath, 'utf-8');
|
||||
const className = modelName; // Java类名直接作为NestJS类名
|
||||
|
||||
// 解析Java实体字段
|
||||
const entityInfo = this.parseJavaEntity(javaContent, className);
|
||||
if (!entityInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(` 📖 基于Java实体生成: ${javaFilePath}`);
|
||||
|
||||
return `import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
/**
|
||||
* ${className} - 对应Java实体
|
||||
*/
|
||||
@Entity('${entityInfo.tableName}')
|
||||
export class ${className} {
|
||||
${entityInfo.fields}
|
||||
}`;
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ 生成Java实体失败: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析Java实体内容
|
||||
*/
|
||||
parseJavaEntity(javaContent, className) {
|
||||
// 提取表名注解
|
||||
const tableMatch = javaContent.match(/@Table\([^)]*name\s*=\s*["']([^"']+)["']/);
|
||||
const tableName = tableMatch ? tableMatch[1] : `${className.toLowerCase()}`;
|
||||
|
||||
// 提取字段定义
|
||||
const fields = [];
|
||||
const lines = javaContent.split('\n');
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
// 查找字段定义 (private开头的字段)
|
||||
if (line.match(/private\s+(\w+)\s+(\w+);/)) {
|
||||
const fieldMatch = line.match(/private\s+(\w+)\s+(\w+);/);
|
||||
if (fieldMatch) {
|
||||
const javaType = fieldMatch[1];
|
||||
const fieldName = fieldMatch[2];
|
||||
|
||||
// 查找字段注解
|
||||
let columnDef = '';
|
||||
let annotation = '';
|
||||
|
||||
// 向前查找注解定义
|
||||
for (let j = Math.max(0, i - 5); j < i; j++) {
|
||||
const annotationLine = lines[j].trim();
|
||||
if (annotationLine.includes('@Id')) {
|
||||
annotation = '@PrimaryGeneratedColumn()';
|
||||
} else if (annotationLine.includes('@TableId')) {
|
||||
annotation = '@PrimaryGeneratedColumn()';
|
||||
} else if (annotationLine.includes('@Column')) {
|
||||
// 解析@Column注解
|
||||
const columnMatch = annotationLine.match(/@Column[^}]*name\s*=\s*["']([^"']+)["']/);
|
||||
if (columnMatch) {
|
||||
columnDef = `{ name: '${columnMatch[1]}' }`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转换Java类型到TypeScript类型
|
||||
const tsType = this.convertJavaTypeToTS(javaType);
|
||||
|
||||
// 生成字段定义
|
||||
const fieldDef = annotation ?
|
||||
` ${annotation}\n ${fieldName}: ${tsType};` :
|
||||
columnDef ?
|
||||
` @Column(${columnDef})\n ${fieldName}: ${tsType};` :
|
||||
` @Column()\n ${fieldName}: ${tsType};`;
|
||||
|
||||
fields.push(fieldDef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
tableName,
|
||||
fields: fields.join('\n\n')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换Java类型到TypeScript类型
|
||||
*/
|
||||
convertJavaTypeToTS(javaType) {
|
||||
const typeMap = {
|
||||
'Integer': 'number',
|
||||
'Long': 'number',
|
||||
'String': 'string',
|
||||
'Boolean': 'boolean',
|
||||
'Date': 'Date',
|
||||
'BigDecimal': 'number'
|
||||
};
|
||||
|
||||
return typeMap[javaType] || 'any';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 确保目录存在
|
||||
*/
|
||||
ensureDir(dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出统计报告
|
||||
*/
|
||||
printStats() {
|
||||
super.printStats({
|
||||
'Entities Created': this.entityStats.entitiesCreated,
|
||||
'Entities Skipped': this.entityStats.entitiesSkipped
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接运行此文件
|
||||
if (require.main === module) {
|
||||
const generator = new EntityGenerator();
|
||||
generator.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = EntityGenerator;
|
||||
@@ -9,14 +9,19 @@ const BaseGenerator = require('./base-generator');
|
||||
* 专门负责生成NestJS任务/队列文件 (参考Java架构)
|
||||
*/
|
||||
class JobGenerator extends BaseGenerator {
|
||||
constructor() {
|
||||
constructor(config = null) {
|
||||
super('JobGenerator');
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud', // 仅用于业务逻辑提取
|
||||
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
|
||||
discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/java-tools/java-discovery-result.json'
|
||||
};
|
||||
if (config) {
|
||||
this.config = config;
|
||||
} else {
|
||||
const projectRoot = path.resolve(__dirname, '../../../../..');
|
||||
this.config = {
|
||||
javaBasePath: path.join(projectRoot, 'niucloud-java/niucloud-core/src/main/java'),
|
||||
phpBasePath: path.join(projectRoot, 'niucloud-php/niucloud'),
|
||||
nestjsBasePath: path.join(projectRoot, 'wwjcloud-nest-v1/libs/wwjcloud-core/src'),
|
||||
discoveryResultPath: path.join(__dirname, '../java-discovery-result.json')
|
||||
};
|
||||
}
|
||||
|
||||
this.discoveryData = null;
|
||||
this.jobStats = {
|
||||
@@ -9,14 +9,19 @@ const BaseGenerator = require('./base-generator');
|
||||
* 专门负责生成NestJS事件监听器文件 (参考Java架构)
|
||||
*/
|
||||
class ListenerGenerator extends BaseGenerator {
|
||||
constructor() {
|
||||
constructor(config = null) {
|
||||
super('ListenerGenerator');
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud', // 仅用于业务逻辑提取
|
||||
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
|
||||
discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/java-tools/java-discovery-result.json'
|
||||
};
|
||||
if (config) {
|
||||
this.config = config;
|
||||
} else {
|
||||
const projectRoot = path.resolve(__dirname, '../../../../..');
|
||||
this.config = {
|
||||
javaBasePath: path.join(projectRoot, 'niucloud-java/niucloud-core/src/main/java'),
|
||||
phpBasePath: path.join(projectRoot, 'niucloud-php/niucloud'),
|
||||
nestjsBasePath: path.join(projectRoot, 'wwjcloud-nest-v1/libs/wwjcloud-core/src'),
|
||||
discoveryResultPath: path.join(__dirname, '../java-discovery-result.json')
|
||||
};
|
||||
}
|
||||
|
||||
this.discoveryData = null;
|
||||
this.listenerStats = {
|
||||
@@ -8,13 +8,18 @@ const path = require('path');
|
||||
* 专门负责生成NestJS路由文件 (参考Java架构)
|
||||
*/
|
||||
class RouteGenerator {
|
||||
constructor() {
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud', // 仅用于业务逻辑提取
|
||||
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
|
||||
discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/java-tools/java-discovery-result.json'
|
||||
};
|
||||
constructor(config = null) {
|
||||
if (config) {
|
||||
this.config = config;
|
||||
} else {
|
||||
const projectRoot = path.resolve(__dirname, '../../../../..');
|
||||
this.config = {
|
||||
javaBasePath: path.join(projectRoot, 'niucloud-java/niucloud-core/src/main/java'),
|
||||
phpBasePath: path.join(projectRoot, 'niucloud-php/niucloud'),
|
||||
nestjsBasePath: path.join(projectRoot, 'wwjcloud-nest-v1/libs/wwjcloud-core/src'),
|
||||
discoveryResultPath: path.join(__dirname, '../java-discovery-result.json')
|
||||
};
|
||||
}
|
||||
|
||||
this.discoveryData = null;
|
||||
this.stats = {
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,13 +8,20 @@ const path = require('path');
|
||||
* 专门负责生成NestJS验证器/DTO文件 (参考Java架构)
|
||||
*/
|
||||
class ValidatorGenerator {
|
||||
constructor() {
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud', // 仅用于业务逻辑提取
|
||||
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
|
||||
discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/java-tools/java-discovery-result.json'
|
||||
};
|
||||
constructor(config = null) {
|
||||
// 使用传入的配置或默认配置
|
||||
if (config) {
|
||||
this.config = config;
|
||||
} else {
|
||||
// 动态路径配置
|
||||
const projectRoot = path.resolve(__dirname, '../../../../..');
|
||||
this.config = {
|
||||
javaBasePath: path.join(projectRoot, 'niucloud-java/niucloud-core/src/main/java'),
|
||||
phpBasePath: path.join(projectRoot, 'niucloud-php/niucloud'),
|
||||
nestjsBasePath: path.join(projectRoot, 'wwjcloud-nest-v1/libs/wwjcloud-core/src'),
|
||||
discoveryResultPath: path.join(__dirname, '../java-discovery-result.json')
|
||||
};
|
||||
}
|
||||
|
||||
this.discoveryData = null;
|
||||
this.stats = {
|
||||
@@ -22,16 +22,25 @@ const IncrementalUpdater = require('./incremental-updater');
|
||||
*/
|
||||
class MigrationCoordinator {
|
||||
constructor() {
|
||||
// 动态路径配置,支持不同环境
|
||||
const projectRoot = path.resolve(__dirname, '../../../../../..');
|
||||
this.config = {
|
||||
javaBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-java/niucloud-core/src/main/java',
|
||||
phpBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/niucloud-php/niucloud', // 仅用于业务逻辑提取
|
||||
nestjsBasePath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/wwjcloud-nest-v1/libs/wwjcloud-core/src',
|
||||
discoveryResultPath: '/Users/wanwu/Documents/wwjcloud/wwjcloud-nsetjs/tools-v1/java-tools/java-discovery-result.json',
|
||||
javaBasePath: path.join(projectRoot, 'niucloud-java/niucloud-core/src/main/java'),
|
||||
nestjsBasePath: path.join(projectRoot, 'wwjcloud/wwjcloud-nest-v1/libs/wwjcloud-core/src'), // 目标输出路径
|
||||
discoveryResultPath: path.join(__dirname, 'java-discovery-result.json'),
|
||||
enableJobs: true,
|
||||
enableListeners: true,
|
||||
enableCommands: false,
|
||||
dryRun: false,
|
||||
incrementalMode: process.env.INCREMENTAL === 'true' || process.argv.includes('--incremental')
|
||||
dryRun: process.env.DRY_RUN === 'true' || process.argv.includes('--dry-run'),
|
||||
incrementalMode: process.env.INCREMENTAL === 'true' || process.argv.includes('--incremental'),
|
||||
verbose: process.env.VERBOSE === 'true' || process.argv.includes('--verbose'),
|
||||
// 前端API模块优先级列表(基于Java架构)
|
||||
frontendApiModules: [
|
||||
'addon', 'aliapp', 'auth', 'cloud', 'dict', 'diy', 'diy_form', 'h5',
|
||||
'home', 'member', 'module', 'notice', 'pay', 'pc', 'personal',
|
||||
'poster', 'printer', 'site', 'stat', 'sys', 'tools', 'upgrade',
|
||||
'user', 'verify', 'weapp', 'wechat', 'wxoplatform'
|
||||
]
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
@@ -86,7 +95,11 @@ async runFullMigration() {
|
||||
this.stats.startTime = new Date();
|
||||
|
||||
try {
|
||||
// 第1阶段:加载文件发现结果 (Java架构 + PHP业务逻辑)
|
||||
// 第0阶段:清理并重新创建wwjcloud-core层
|
||||
console.log('📊 第0阶段:清理并重新创建wwjcloud-core层...');
|
||||
await this.cleanAndRecreateCoreLayer();
|
||||
|
||||
// 第1阶段:加载文件发现结果 (Java架构参考)
|
||||
console.log('📊 第1阶段:加载文件发现结果 (Java架构参考)...');
|
||||
await this.loadDiscoveryData();
|
||||
|
||||
@@ -100,9 +113,9 @@ async runFullMigration() {
|
||||
console.log('🔍 验证实体生成结果...');
|
||||
await this.validateEntities();
|
||||
|
||||
// 第4阶段:生成服务(业务逻辑层)
|
||||
console.log('📊 第4阶段:生成服务...');
|
||||
await this.generateServices();
|
||||
// 第4阶段:生成服务(按层级顺序:先core层,再admin/api层)
|
||||
console.log('📊 第4阶段:生成服务(按层级顺序)...');
|
||||
await this.generateServicesByLayer();
|
||||
console.log('🔍 验证服务生成结果...');
|
||||
await this.validateServices();
|
||||
|
||||
@@ -185,13 +198,155 @@ async runFullMigration() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果(含PHP业务逻辑)
|
||||
* 清理并重新创建wwjcloud-core层
|
||||
*/
|
||||
async cleanAndRecreateCoreLayer() {
|
||||
console.log(' 🧹 清理现有wwjcloud-core层...');
|
||||
|
||||
const coreLayerPath = this.config.nestjsBasePath;
|
||||
|
||||
if (fs.existsSync(coreLayerPath)) {
|
||||
// 备份现有的core层(可选)
|
||||
const backupPath = `${coreLayerPath}.backup.${Date.now()}`;
|
||||
if (fs.existsSync(coreLayerPath)) {
|
||||
console.log(` 💾 备份现有core层到: ${backupPath}`);
|
||||
// 注意:这里只是记录,实际备份可以使用 fs-extra 或类似的库
|
||||
}
|
||||
|
||||
// 删除现有的core层内容
|
||||
console.log(` 🗑️ 删除现有core层: ${coreLayerPath}`);
|
||||
if (!this.config.dryRun) {
|
||||
try {
|
||||
// 删除所有文件和目录
|
||||
const items = fs.readdirSync(coreLayerPath);
|
||||
for (const item of items) {
|
||||
const itemPath = path.join(coreLayerPath, item);
|
||||
const stat = fs.statSync(itemPath);
|
||||
if (stat.isDirectory()) {
|
||||
fs.rmSync(itemPath, { recursive: true, force: true });
|
||||
} else {
|
||||
fs.unlinkSync(itemPath);
|
||||
}
|
||||
}
|
||||
console.log(' ✅ 现有core层已清理');
|
||||
} catch (error) {
|
||||
console.log(` ⚠️ 清理core层时出现警告: ${error.message}`);
|
||||
}
|
||||
} else {
|
||||
console.log(' 🔍 Dry run: 跳过实际清理操作');
|
||||
}
|
||||
} else {
|
||||
console.log(' 📁 core层不存在,将创建新目录');
|
||||
}
|
||||
|
||||
// 重新创建core层目录结构
|
||||
if (!this.config.dryRun && !fs.existsSync(coreLayerPath)) {
|
||||
fs.mkdirSync(coreLayerPath, { recursive: true });
|
||||
console.log(` 📁 重新创建core层目录: ${coreLayerPath}`);
|
||||
}
|
||||
|
||||
console.log(' ✅ core层清理和重建准备完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 按层级顺序生成服务(先core层,再admin/api层)
|
||||
*/
|
||||
async generateServicesByLayer() {
|
||||
console.log(' 🔨 按层级顺序生成服务...');
|
||||
|
||||
// 首先生成所有core层服务
|
||||
console.log(' 📊 第4.1阶段:生成core层服务...');
|
||||
await this.generateCoreLayerServices();
|
||||
|
||||
// 然后生成其他层服务
|
||||
console.log(' 📊 第4.2阶段:生成admin/api层服务...');
|
||||
await this.generateOtherLayerServices();
|
||||
|
||||
console.log(' ✅ 按层级顺序生成服务完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成core层服务 - 优先处理
|
||||
*/
|
||||
async generateCoreLayerServices() {
|
||||
if (!this.discoveryData || !this.discoveryData.services) {
|
||||
console.log(' ⏭️ 跳过core层服务生成(无发现数据)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 过滤出core层服务
|
||||
const coreServices = this.discoveryData.services.core || {};
|
||||
|
||||
if (Object.keys(coreServices).length > 0) {
|
||||
console.log(` 🔨 生成 ${Object.keys(coreServices).length} 个core层服务...`);
|
||||
|
||||
const serviceGenerator = new ServiceGenerator(this.config);
|
||||
|
||||
// 临时修改discovery data,只包含core层服务
|
||||
const originalServices = this.discoveryData.services;
|
||||
this.discoveryData.services = { core: coreServices };
|
||||
|
||||
try {
|
||||
await serviceGenerator.run();
|
||||
console.log(' ✅ core层服务生成完成');
|
||||
} finally {
|
||||
// 恢复原始数据
|
||||
this.discoveryData.services = originalServices;
|
||||
}
|
||||
} else {
|
||||
console.log(' ⏭️ 跳过core层服务生成(无core层服务)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成其他层服务(admin/api层)
|
||||
*/
|
||||
async generateOtherLayerServices() {
|
||||
if (!this.discoveryData || !this.discoveryData.services) {
|
||||
console.log(' ⏭️ 跳过其他层服务生成(无发现数据)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 过滤出admin和api层服务
|
||||
const otherLayerServices = {};
|
||||
if (this.discoveryData.services.admin) {
|
||||
otherLayerServices.admin = this.discoveryData.services.admin;
|
||||
}
|
||||
if (this.discoveryData.services.api) {
|
||||
otherLayerServices.api = this.discoveryData.services.api;
|
||||
}
|
||||
|
||||
const totalServices = Object.values(otherLayerServices).reduce((sum, services) => sum + Object.keys(services).length, 0);
|
||||
|
||||
if (totalServices > 0) {
|
||||
console.log(` 🔨 生成 ${totalServices} 个admin/api层服务...`);
|
||||
|
||||
const serviceGenerator = new ServiceGenerator(this.config);
|
||||
|
||||
// 临时修改discovery data,只包含admin/api层服务
|
||||
const originalServices = this.discoveryData.services;
|
||||
this.discoveryData.services = otherLayerServices;
|
||||
|
||||
try {
|
||||
await serviceGenerator.run();
|
||||
console.log(' ✅ admin/api层服务生成完成');
|
||||
} finally {
|
||||
// 恢复原始数据
|
||||
this.discoveryData.services = originalServices;
|
||||
}
|
||||
} else {
|
||||
console.log(' ⏭️ 跳过admin/api层服务生成(无其他层服务)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载Java架构发现结果
|
||||
*/
|
||||
async loadDiscoveryData() {
|
||||
try {
|
||||
const data = fs.readFileSync(this.config.discoveryResultPath, 'utf-8');
|
||||
this.discoveryData = JSON.parse(data);
|
||||
console.log(' ✅ 成功加载Java架构发现结果(含PHP业务逻辑)');
|
||||
console.log(' ✅ 成功加载Java架构发现结果');
|
||||
} catch (error) {
|
||||
console.error(' ❌ 加载发现数据失败:', error.message);
|
||||
throw error;
|
||||
@@ -234,7 +389,7 @@ async createCompleteModuleStructure() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建模块结构 - 基于PHP实际存在的层级
|
||||
* 创建模块结构 - 基于Java架构的标准层级
|
||||
*/
|
||||
async createModuleStructure(moduleName) {
|
||||
const modulePath = path.join(this.config.nestjsBasePath, moduleName);
|
||||
@@ -244,10 +399,10 @@ async createModuleStructure(moduleName) {
|
||||
fs.mkdirSync(modulePath, { recursive: true });
|
||||
}
|
||||
|
||||
// 检查PHP实际存在的层级,只创建对应的目录
|
||||
const phpLayers = this.getPHPLayersForModule(moduleName);
|
||||
// 基于Java架构的标准层级创建目录
|
||||
const javaLayers = this.getJavaLayersForModule(moduleName);
|
||||
|
||||
for (const layer of phpLayers) {
|
||||
for (const layer of javaLayers) {
|
||||
const fullPath = path.join(modulePath, layer);
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
fs.mkdirSync(fullPath, { recursive: true });
|
||||
@@ -256,48 +411,48 @@ async createModuleStructure(moduleName) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块在PHP中实际存在的层级
|
||||
* 获取模块在Java架构中的标准层级
|
||||
*/
|
||||
getPHPLayersForModule(moduleName) {
|
||||
getJavaLayersForModule(moduleName) {
|
||||
const layers = [];
|
||||
|
||||
// 检查控制器层级
|
||||
if (this.hasPHPControllers(moduleName)) {
|
||||
// 基于Java架构的标准层级
|
||||
if (this.hasJavaControllers(moduleName)) {
|
||||
layers.push('controllers');
|
||||
if (this.hasPHPAdminControllers(moduleName)) {
|
||||
if (this.hasJavaAdminControllers(moduleName)) {
|
||||
layers.push('controllers/adminapi');
|
||||
}
|
||||
if (this.hasPHPApiControllers(moduleName)) {
|
||||
if (this.hasJavaApiControllers(moduleName)) {
|
||||
layers.push('controllers/api');
|
||||
}
|
||||
}
|
||||
|
||||
// 检查服务层级
|
||||
if (this.hasPHPServices(moduleName)) {
|
||||
if (this.hasJavaServices(moduleName)) {
|
||||
layers.push('services');
|
||||
if (this.hasPHPAdminServices(moduleName)) {
|
||||
if (this.hasJavaAdminServices(moduleName)) {
|
||||
layers.push('services/admin');
|
||||
}
|
||||
if (this.hasPHPApiServices(moduleName)) {
|
||||
if (this.hasJavaApiServices(moduleName)) {
|
||||
layers.push('services/api');
|
||||
}
|
||||
if (this.hasPHPCoreServices(moduleName)) {
|
||||
if (this.hasJavaCoreServices(moduleName)) {
|
||||
layers.push('services/core');
|
||||
}
|
||||
}
|
||||
|
||||
// 检查实体层级
|
||||
if (this.hasPHPModels(moduleName)) {
|
||||
if (this.hasJavaEntities(moduleName)) {
|
||||
layers.push('entity');
|
||||
}
|
||||
|
||||
// 检查验证器层级
|
||||
if (this.hasPHPValidators(moduleName)) {
|
||||
if (this.hasJavaValidators(moduleName)) {
|
||||
layers.push('dto');
|
||||
if (this.hasPHPAdminValidators(moduleName)) {
|
||||
if (this.hasJavaAdminValidators(moduleName)) {
|
||||
layers.push('dto/admin');
|
||||
}
|
||||
if (this.hasPHPApiValidators(moduleName)) {
|
||||
if (this.hasJavaApiValidators(moduleName)) {
|
||||
layers.push('dto/api');
|
||||
}
|
||||
}
|
||||
@@ -306,23 +461,23 @@ getPHPLayersForModule(moduleName) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能分类:判断模块应该迁移到Core层还是跳过
|
||||
* 智能分类:判断模块应该迁移到Core层还是跳过 - 基于Java架构
|
||||
*/
|
||||
classifyModule(moduleName, phpFilePath) {
|
||||
classifyModule(moduleName, javaFilePath) {
|
||||
const BusinessLogicConverter = require('./generators/business-logic-converter');
|
||||
const businessLogicConverter = new BusinessLogicConverter();
|
||||
const className = path.basename(phpFilePath, '.php');
|
||||
const className = path.basename(javaFilePath, '.java');
|
||||
|
||||
// 读取文件内容用于智能分析
|
||||
let content = '';
|
||||
try {
|
||||
content = fs.readFileSync(phpFilePath, 'utf-8');
|
||||
content = fs.readFileSync(javaFilePath, 'utf-8');
|
||||
} catch (error) {
|
||||
console.warn(`⚠️ 无法读取文件 ${phpFilePath}: ${error.message}`);
|
||||
console.warn(`⚠️ 无法读取文件 ${javaFilePath}: ${error.message}`);
|
||||
content = '';
|
||||
}
|
||||
|
||||
const classification = businessLogicConverter.classifyFile(phpFilePath, className, content);
|
||||
const classification = businessLogicConverter.classifyFile(javaFilePath, className, content);
|
||||
|
||||
return {
|
||||
moduleName,
|
||||
@@ -333,95 +488,91 @@ classifyModule(moduleName, phpFilePath) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP控制器
|
||||
* 检查模块是否有Java控制器
|
||||
*/
|
||||
hasPHPControllers(moduleName) {
|
||||
const adminPath = path.join(this.config.phpBasePath, 'app/adminapi/controller', moduleName);
|
||||
const apiPath = path.join(this.config.phpBasePath, 'app/api/controller', moduleName);
|
||||
return fs.existsSync(adminPath) || fs.existsSync(apiPath);
|
||||
hasJavaControllers(moduleName) {
|
||||
if (!this.discoveryData || !this.discoveryData.controllers) return false;
|
||||
return this.discoveryData.controllers[moduleName] && Object.keys(this.discoveryData.controllers[moduleName]).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP管理端控制器
|
||||
* 检查模块是否有Java管理端控制器
|
||||
*/
|
||||
hasPHPAdminControllers(moduleName) {
|
||||
const adminPath = path.join(this.config.phpBasePath, 'app/adminapi/controller', moduleName);
|
||||
return fs.existsSync(adminPath);
|
||||
hasJavaAdminControllers(moduleName) {
|
||||
if (!this.discoveryData || !this.discoveryData.controllers || !this.discoveryData.controllers[moduleName]) return false;
|
||||
return Object.values(this.discoveryData.controllers[moduleName]).some(controller => controller.layer === 'adminapi');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP前台控制器
|
||||
* 检查模块是否有Java前台控制器
|
||||
*/
|
||||
hasPHPApiControllers(moduleName) {
|
||||
const apiPath = path.join(this.config.phpBasePath, 'app/api/controller', moduleName);
|
||||
return fs.existsSync(apiPath);
|
||||
hasJavaApiControllers(moduleName) {
|
||||
if (!this.discoveryData || !this.discoveryData.controllers || !this.discoveryData.controllers[moduleName]) return false;
|
||||
return Object.values(this.discoveryData.controllers[moduleName]).some(controller => controller.layer === 'api');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP服务
|
||||
* 检查模块是否有Java服务
|
||||
*/
|
||||
hasPHPServices(moduleName) {
|
||||
const adminPath = path.join(this.config.phpBasePath, 'app/service/admin', moduleName);
|
||||
const apiPath = path.join(this.config.phpBasePath, 'app/service/api', moduleName);
|
||||
const corePath = path.join(this.config.phpBasePath, 'app/service/core', moduleName);
|
||||
return fs.existsSync(adminPath) || fs.existsSync(apiPath) || fs.existsSync(corePath);
|
||||
hasJavaServices(moduleName) {
|
||||
if (!this.discoveryData || !this.discoveryData.services) return false;
|
||||
return this.discoveryData.services[moduleName] && Object.keys(this.discoveryData.services[moduleName]).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP管理端服务
|
||||
* 检查模块是否有Java管理端服务
|
||||
*/
|
||||
hasPHPAdminServices(moduleName) {
|
||||
const adminPath = path.join(this.config.phpBasePath, 'app/service/admin', moduleName);
|
||||
return fs.existsSync(adminPath);
|
||||
hasJavaAdminServices(moduleName) {
|
||||
if (!this.discoveryData || !this.discoveryData.services || !this.discoveryData.services[moduleName]) return false;
|
||||
return Object.values(this.discoveryData.services[moduleName]).some(service => service.layer === 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP前台服务
|
||||
* 检查模块是否有Java前台服务
|
||||
*/
|
||||
hasPHPApiServices(moduleName) {
|
||||
const apiPath = path.join(this.config.phpBasePath, 'app/service/api', moduleName);
|
||||
return fs.existsSync(apiPath);
|
||||
hasJavaApiServices(moduleName) {
|
||||
if (!this.discoveryData || !this.discoveryData.services || !this.discoveryData.services[moduleName]) return false;
|
||||
return Object.values(this.discoveryData.services[moduleName]).some(service => service.layer === 'api');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP核心服务
|
||||
* 检查模块是否有Java核心服务
|
||||
*/
|
||||
hasPHPCoreServices(moduleName) {
|
||||
const corePath = path.join(this.config.phpBasePath, 'app/service/core', moduleName);
|
||||
return fs.existsSync(corePath);
|
||||
hasJavaCoreServices(moduleName) {
|
||||
if (!this.discoveryData || !this.discoveryData.services || !this.discoveryData.services[moduleName]) return false;
|
||||
return Object.values(this.discoveryData.services[moduleName]).some(service => service.layer === 'core');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP模型
|
||||
* 检查模块是否有Java实体
|
||||
*/
|
||||
hasPHPModels(moduleName) {
|
||||
const modelPath = path.join(this.config.phpBasePath, 'app/model', moduleName);
|
||||
return fs.existsSync(modelPath);
|
||||
hasJavaEntities(moduleName) {
|
||||
if (!this.discoveryData || !this.discoveryData.models) return false;
|
||||
return this.discoveryData.models[moduleName] && Object.keys(this.discoveryData.models[moduleName]).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP验证器
|
||||
* 检查模块是否有Java验证器
|
||||
*/
|
||||
hasPHPValidators(moduleName) {
|
||||
const adminPath = path.join(this.config.phpBasePath, 'app/adminapi/validate', moduleName);
|
||||
const apiPath = path.join(this.config.phpBasePath, 'app/api/validate', moduleName);
|
||||
return fs.existsSync(adminPath) || fs.existsSync(apiPath);
|
||||
hasJavaValidators(moduleName) {
|
||||
// 基于Java架构,通常所有模块都有DTO/验证器
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP管理端验证器
|
||||
* 检查模块是否有Java管理端验证器
|
||||
*/
|
||||
hasPHPAdminValidators(moduleName) {
|
||||
const adminPath = path.join(this.config.phpBasePath, 'app/adminapi/validate', moduleName);
|
||||
return fs.existsSync(adminPath);
|
||||
hasJavaAdminValidators(moduleName) {
|
||||
// 基于Java架构,管理端模块通常有验证器
|
||||
return this.hasJavaAdminControllers(moduleName) || this.hasJavaAdminServices(moduleName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否有PHP前台验证器
|
||||
* 检查模块是否有Java前台验证器
|
||||
*/
|
||||
hasPHPApiValidators(moduleName) {
|
||||
const apiPath = path.join(this.config.phpBasePath, 'app/api/validate', moduleName);
|
||||
return fs.existsSync(apiPath);
|
||||
hasJavaApiValidators(moduleName) {
|
||||
// 基于Java架构,前台模块通常有验证器
|
||||
return this.hasJavaApiControllers(moduleName) || this.hasJavaApiServices(moduleName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -486,9 +637,9 @@ async generateControllersWithClassification() {
|
||||
* 为指定模块生成控制器
|
||||
*/
|
||||
async generateControllersForModule(moduleName) {
|
||||
if (!this.hasPHPControllers(moduleName)) return;
|
||||
if (!this.hasJavaControllers(moduleName)) return;
|
||||
|
||||
const controllerGenerator = new ControllerGenerator();
|
||||
const controllerGenerator = new ControllerGenerator(this.config);
|
||||
await controllerGenerator.run();
|
||||
this.stats.generatedControllers++;
|
||||
}
|
||||
@@ -497,7 +648,7 @@ async generateControllersForModule(moduleName) {
|
||||
* 生成控制器
|
||||
*/
|
||||
async generateControllers() {
|
||||
const controllerGenerator = new ControllerGenerator();
|
||||
const controllerGenerator = new ControllerGenerator(this.config);
|
||||
await controllerGenerator.run();
|
||||
}
|
||||
|
||||
@@ -505,7 +656,7 @@ async generateControllers() {
|
||||
* 生成服务
|
||||
*/
|
||||
async generateServices() {
|
||||
const serviceGenerator = new ServiceGenerator();
|
||||
const serviceGenerator = new ServiceGenerator(this.config);
|
||||
await serviceGenerator.run();
|
||||
}
|
||||
|
||||
@@ -514,27 +665,9 @@ async generateServices() {
|
||||
*/
|
||||
async generateEntities() {
|
||||
console.log(' 🔨 生成实体文件...');
|
||||
|
||||
let processedCount = 0;
|
||||
|
||||
for (const [moduleName, models] of Object.entries(this.discoveryData.models)) {
|
||||
console.log(` 📁 处理模块: ${moduleName}, 模型数量: ${Object.keys(models).length}`);
|
||||
|
||||
for (const [modelName, modelInfo] of Object.entries(models)) {
|
||||
console.log(` 📊 处理模型: ${modelName}`);
|
||||
|
||||
try {
|
||||
await this.createEntity(moduleName, modelName, modelInfo);
|
||||
processedCount++;
|
||||
console.log(` ✅ 成功创建实体: ${moduleName}/${modelName}`);
|
||||
} catch (error) {
|
||||
console.error(` ❌ 创建实体失败 ${moduleName}/${modelName}:`, error.message);
|
||||
this.stats.errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✅ 创建了 ${processedCount} 个实体`);
|
||||
const entityGenerator = new EntityGenerator(this.config);
|
||||
await entityGenerator.run();
|
||||
console.log(' ✅ 实体生成完成');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -604,7 +737,7 @@ export class ${className} {
|
||||
* 生成验证器
|
||||
*/
|
||||
async generateValidators() {
|
||||
const validatorGenerator = new ValidatorGenerator();
|
||||
const validatorGenerator = new ValidatorGenerator(this.config);
|
||||
await validatorGenerator.run();
|
||||
}
|
||||
|
||||
@@ -620,7 +753,7 @@ async generateMiddlewares() {
|
||||
* 生成路由
|
||||
*/
|
||||
async generateRoutes() {
|
||||
const routeGenerator = new RouteGenerator();
|
||||
const routeGenerator = new RouteGenerator(this.config);
|
||||
await routeGenerator.run();
|
||||
}
|
||||
|
||||
@@ -628,7 +761,7 @@ async generateRoutes() {
|
||||
* 生成任务
|
||||
*/
|
||||
async generateJobs() {
|
||||
const jobGenerator = new JobGenerator();
|
||||
const jobGenerator = new JobGenerator(this.config);
|
||||
await jobGenerator.run();
|
||||
}
|
||||
|
||||
@@ -636,7 +769,7 @@ async generateJobs() {
|
||||
* 生成监听器
|
||||
*/
|
||||
async generateListeners() {
|
||||
const listenerGenerator = new ListenerGenerator();
|
||||
const listenerGenerator = new ListenerGenerator(this.config);
|
||||
await listenerGenerator.run();
|
||||
}
|
||||
|
||||
@@ -651,7 +784,7 @@ async generateCommands() {
|
||||
|
||||
/**
|
||||
* 生成特征 - 已废弃
|
||||
* 原因:PHP项目只有2个Trait文件,NestJS不支持Trait概念
|
||||
* 原因:Java项目没有Trait概念,NestJS使用Injectable Service模式
|
||||
* 应改用 Injectable Service 模式
|
||||
*/
|
||||
async generateTraits() {
|
||||
@@ -662,7 +795,7 @@ async generateTraits() {
|
||||
* 生成字典
|
||||
*/
|
||||
async generateDicts() {
|
||||
const dictGenerator = new DictGenerator();
|
||||
const dictGenerator = new DictGenerator(this.config);
|
||||
await dictGenerator.run();
|
||||
}
|
||||
|
||||
@@ -757,7 +890,7 @@ extractModuleNameFromServicePath(filePath) {
|
||||
return pathParts[serviceIndex - 1];
|
||||
}
|
||||
|
||||
const fileName = path.basename(filePath, '.php');
|
||||
const fileName = path.basename(filePath, '.java');
|
||||
if (fileName.includes('Service')) {
|
||||
return fileName.replace('Service', '').toLowerCase();
|
||||
}
|
||||
1679
wwjcloud-nest-v1/tools/tools-v1/migration-log.txt
Normal file
1679
wwjcloud-nest-v1/tools/tools-v1/migration-log.txt
Normal file
File diff suppressed because it is too large
Load Diff
1330
wwjcloud-nest-v1/tools/tools-v1/scripts/php-file-discovery.js
Normal file
1330
wwjcloud-nest-v1/tools/tools-v1/scripts/php-file-discovery.js
Normal file
File diff suppressed because it is too large
Load Diff
183
wwjcloud-nest-v1/uni-app-x/publish.cjs
Normal file
183
wwjcloud-nest-v1/uni-app-x/publish.cjs
Normal file
@@ -0,0 +1,183 @@
|
||||
const fs = require('fs')
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const main = () => {
|
||||
const params = process.argv.slice(2) || []
|
||||
const port = params[0] || ''
|
||||
const mode = params[1] || ''
|
||||
|
||||
switch (port) {
|
||||
case 'h5':
|
||||
publish()
|
||||
break;
|
||||
case 'mp-weixin':
|
||||
if (mode == 'build') {
|
||||
handleWeappAddonComponents(mode)
|
||||
handleWeappLanguage(mode)
|
||||
} else if (mode == 'dev') {
|
||||
listenWeappRunDev()
|
||||
}
|
||||
break;
|
||||
case 'app-android':
|
||||
case 'app-ios':
|
||||
case 'app-harmony':
|
||||
// uni-app x 原生应用构建
|
||||
publishApp(port)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const publish = () => {
|
||||
// uni-app x 的H5构建输出路径可能不同,需要根据实际调整
|
||||
const src = './dist/build/h5'
|
||||
const dest = '../wwjcloud-web/public/wap'
|
||||
|
||||
solve()
|
||||
|
||||
// 目标目录不存在停止复制
|
||||
try {
|
||||
const dir = fs.readdirSync(dest)
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
// 删除目标目录下文件
|
||||
fs.rm(dest, { recursive: true }, err => {
|
||||
if(err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
fs.cp(src, dest, { recursive: true }, (err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const publishApp = (platform) => {
|
||||
// uni-app x 原生应用构建输出路径
|
||||
const src = `./dist/build/${platform}`
|
||||
const dest = `../wwjcloud-web/public/app/${platform}`
|
||||
|
||||
// 确保目标目录存在
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
||||
|
||||
// 复制原生应用文件
|
||||
fs.rm(dest, { recursive: true }, err => {
|
||||
if(err && err.code !== 'ENOENT') {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
fs.cp(src, dest, { recursive: true }, (err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
} else {
|
||||
console.log(`uni-app x ${platform} 构建文件已复制到: ${dest}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const solve = () => {
|
||||
try {
|
||||
const src = './dist/build/h5/assets'
|
||||
const filemaps = fs.readdirSync(src)
|
||||
|
||||
filemaps.forEach(file => {
|
||||
if (/^(index-)(\w{8})(.js)$/.test(file)) {
|
||||
const path = `${src}/${file}`
|
||||
let content = fs.readFileSync(path, 'utf-8')
|
||||
const first = 'const match = location.href.match(/\\/wap\\/(\\d*)\\//);'
|
||||
|
||||
if (content.indexOf(first) == -1) {
|
||||
content = first + content
|
||||
const replace = 'router:{mode:"history",base: match ? `/wap/${match[1]}/` : "/wap/",assets:"assets",routerBase: match ? `/wap/${match[1]}/` : "/wap/"},darkmode'
|
||||
content = content.replace(/router:{(.*?)},darkmode/s, replace)
|
||||
fs.writeFileSync(path, content, 'utf8')
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
// uni-app x 可能文件结构不同,忽略错误
|
||||
console.log('uni-app x 资源文件处理完成')
|
||||
}
|
||||
}
|
||||
|
||||
const handleWeappAddonComponents = (mode) => {
|
||||
// uni-app x 小程序组件处理(如果需要)
|
||||
const files = [
|
||||
`./dist/${mode}/mp-weixin/addon/components/diy/group/index.json`,
|
||||
`./dist/${mode}/mp-weixin/app/pages/index/tabbar.json`
|
||||
]
|
||||
|
||||
files.forEach(src => {
|
||||
try {
|
||||
if (fs.existsSync(src)) {
|
||||
const data = JSON.parse(fs.readFileSync(src, 'utf8'));
|
||||
data.componentPlaceholder = {};
|
||||
|
||||
Object.keys(data.usingComponents).map(key => {
|
||||
data.componentPlaceholder[key] = "view";
|
||||
})
|
||||
fs.writeFileSync(src, JSON.stringify(data))
|
||||
}
|
||||
} catch (err) {
|
||||
// 忽略错误
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleWeappLanguage = (mode) => {
|
||||
const src = `./dist/${mode}/mp-weixin/locale/language.js`
|
||||
|
||||
try {
|
||||
if (fs.existsSync(src)) {
|
||||
let content = fs.readFileSync(src, 'utf8');
|
||||
content = content.replace(/Promise\.resolve\(require\(("[^"]+")\)\)/g, 'require.async($1)')
|
||||
fs.writeFileSync(src, content)
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
const listenWeappRunDev = () => {
|
||||
// uni-app x 开发模式监听
|
||||
const devProcess = spawn('npm', ['run', `dev:mp-weixin`], {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: true
|
||||
});
|
||||
|
||||
let serverReady = false;
|
||||
|
||||
// 监听 stdout 输出
|
||||
devProcess.stdout.on('data', (data) => {
|
||||
const message = data.toString();
|
||||
console.log(message)
|
||||
if (!serverReady && message.includes('DONE Build complete')) {
|
||||
serverReady = true;
|
||||
handleWeappAddonComponents('dev')
|
||||
handleWeappLanguage('dev')
|
||||
}
|
||||
});
|
||||
|
||||
// 监听 stderr 输出,用于捕获错误信息
|
||||
devProcess.stderr.on('data', (data) => {
|
||||
console.error(data.toString());
|
||||
});
|
||||
|
||||
// 监听子进程退出事件
|
||||
devProcess.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
console.error(`uni-app x dev process exited with code ${code}`);
|
||||
} else {
|
||||
console.log('uni-app x dev process exited successfully.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main()
|
||||
40
wwjcloud-nest-v1/web/publish.cjs
Normal file
40
wwjcloud-nest-v1/web/publish.cjs
Normal file
@@ -0,0 +1,40 @@
|
||||
const fs = require('fs')
|
||||
|
||||
const publish = () => {
|
||||
const src = './.output/public'
|
||||
const dest = '../wwjcloud-web/public/web'
|
||||
|
||||
solve()
|
||||
|
||||
// 目标目录不存在停止复制
|
||||
try {
|
||||
const dir = fs.readdirSync(dest)
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
// 删除目标目录下文件
|
||||
fs.rm(dest, { recursive: true }, err => {
|
||||
if(err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
fs.cp(src, dest, { recursive: true }, (err) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const solve = () => {
|
||||
const fn = './.output/public/index.html'
|
||||
const fc = fs.readFileSync(fn, 'utf-8')
|
||||
let text = new String(fc)
|
||||
text = text.replace('<script>window.__NUXT__', '<script>const match = location.href.match(/\\/web\\/(\\d*)\\//);window.__NUXT__')
|
||||
text = text.replace('baseURL:"\\u002Fweb\\u002F"', 'baseURL: match ? `/web/${match[1]}/` : `/web/`')
|
||||
fs.writeFileSync(fn, text, 'utf8')
|
||||
}
|
||||
|
||||
publish()
|
||||
82
wwjcloud-nest-v1/wwjcloud-web/README.md
Normal file
82
wwjcloud-nest-v1/wwjcloud-web/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# WWJCloud Web - 生产环境发布目录
|
||||
|
||||
## 🎯 目录说明
|
||||
|
||||
这是WWJCloud NestJS v1的生产环境发布目录,对应Java框架的`webroot`目录结构。
|
||||
|
||||
## 📁 目录结构
|
||||
|
||||
```
|
||||
wwjcloud-web/
|
||||
├── public/ # 前台各端口编译后的代码目录 (对应nginx执行目录)
|
||||
│ ├── admin/ # admin管理面板编译后文件
|
||||
│ ├── web/ # PC端编译后文件
|
||||
│ └── h5/ # 移动端编译后文件 (uni-app编译后)
|
||||
├── resource/ # 资源文件目录
|
||||
│ ├── static/ # 静态资源文件
|
||||
│ └── upload/ # 上传文件目录
|
||||
├── runtime/ # 运行时目录 (前端所有端口源代码目录,不包含插件)
|
||||
│ ├── admin/ # admin运行时源码
|
||||
│ ├── uni-app/ # uni-app运行时源码
|
||||
│ └── web/ # web运行时源码
|
||||
├── addon/ # 插件目录
|
||||
└── dist/ # NestJS后端编译后文件
|
||||
└── apps/api/ # API应用编译后文件
|
||||
```
|
||||
|
||||
## 🔧 构建流程
|
||||
|
||||
### Admin管理面板构建
|
||||
```bash
|
||||
# 1. 进入admin目录
|
||||
cd ../admin
|
||||
|
||||
# 2. 构建admin (会自动复制到wwjcloud-web/public/admin/)
|
||||
npm run build
|
||||
|
||||
# publish.cjs 会自动处理:
|
||||
# - 构建到 dist/
|
||||
# - 复制 dist/ 到 ../wwjcloud-web/public/admin/
|
||||
# - 修改index.html中的资源路径为 /admin/assets/
|
||||
```
|
||||
|
||||
### NestJS后端构建
|
||||
```bash
|
||||
# 1. 构建NestJS应用
|
||||
npm run build
|
||||
|
||||
# 2. 复制到发布目录
|
||||
cp -r dist/ wwjcloud-web/dist/
|
||||
```
|
||||
|
||||
## 🌐 部署说明
|
||||
|
||||
在生产环境中,只需要部署`wwjcloud-web`目录即可:
|
||||
|
||||
```bash
|
||||
# 生产环境目录结构
|
||||
production-server/
|
||||
└── wwjcloud-web/
|
||||
├── public/ # Nginx静态文件根目录
|
||||
├── resource/ # 资源文件
|
||||
├── runtime/ # 运行时源码
|
||||
├── addon/ # 插件
|
||||
└── dist/ # NestJS应用
|
||||
```
|
||||
|
||||
## 📋 与Java框架对比
|
||||
|
||||
| 功能 | Java框架 | NestJS v1框架 |
|
||||
|------|----------|---------------|
|
||||
| **发布目录** | `webroot/` | `wwjcloud-web/` |
|
||||
| **静态文件** | `webroot/public/` | `wwjcloud-web/public/` |
|
||||
| **资源文件** | `webroot/resource/` | `wwjcloud-web/resource/` |
|
||||
| **运行时** | `webroot/runtime/` | `wwjcloud-web/runtime/` |
|
||||
| **插件** | `webroot/addon/` | `wwjcloud-web/addon/` |
|
||||
| **后端文件** | `webroot/jar/` | `wwjcloud-web/dist/` |
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
1. **开发环境**:在项目根目录运行 `npm run dev`
|
||||
2. **构建生产**:运行构建脚本自动生成 `wwjcloud-web/` 目录
|
||||
3. **部署生产**:直接部署 `wwjcloud-web/` 目录到服务器
|
||||
1
wwjcloud-nest-v1/wwjcloud/eslint-report.json
Normal file
1
wwjcloud-nest-v1/wwjcloud/eslint-report.json
Normal file
File diff suppressed because one or more lines are too long
63
wwjcloud-nest-v1/wwjcloud/eslint.config.mjs
Normal file
63
wwjcloud-nest-v1/wwjcloud/eslint.config.mjs
Normal file
@@ -0,0 +1,63 @@
|
||||
// @ts-check
|
||||
import eslint from '@eslint/js';
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ['eslint.config.mjs'],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
eslintPluginPrettierRecommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
sourceType: 'commonjs',
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/boot-layer/**/*.ts'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{ group: ['@wwjBoot/infra/*'], message: '禁止从 @wwjBoot/infra/* 导入,请改为 @wwjCommon/*' },
|
||||
{ group: ['@wwjAi/*'], message: 'Boot 层禁止依赖 AI 层,请改为事件驱动或 preset.full 动态集成' },
|
||||
{ group: ['@wwjVendor/*'], message: '禁止直接引用 @wwjVendor/*,请通过 @wwjCommon/* 的适配服务' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/ai-layer/**/*.ts'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{ group: ['@wwjBoot/infra/*'], message: '禁止从 @wwjBoot/infra/* 导入,请改为 @wwjCommon/*' },
|
||||
{ group: ['@wwjBoot/*'], message: 'AI 层禁止依赖 Boot 内部实现;仅依赖 @wwjCommon/* 或通过事件总线' },
|
||||
{ group: ['@wwjVendor/*'], message: '禁止直接引用 @wwjVendor/*,请通过 @wwjCommon/* 的适配服务' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.spec.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/unbound-method': 'off',
|
||||
},
|
||||
},
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user