Files
wwjcloud-nest-v1/wwjcloud-nest-v1/tools/tools-uni/utils/file-utils.js
wanwujie 0f105d3a21 🎯 重构目录结构:完成项目组织优化
- 将wwjcloud目录重命名为wwjcloud-nest-v1作为项目根目录
- 将原nestjs目录重命名为wwjcloud作为NestJS后端目录
- 实现真正的前后端分离架构
- 恢复工作区中丢失的目录结构
- 更新相关配置文件路径引用
- 清理重复和嵌套目录问题

目录结构:
wwjcloud-nest-v1/
├── wwjcloud/          # NestJS 后端
├── admin/             # 管理端前端
├── web/               # PC端前端
├── uni-app-x/         # 移动端前端
├── wwjcloud-web/      # 部署根目录
├── docker/            # Docker 配置
├── docs/              # 文档
└── tools/             # 工具集
2025-10-21 13:38:58 +08:00

448 lines
13 KiB
JavaScript

#!/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;