🔧 API接口测试
🔄 重建索引
扫描目录并重建文件索引
🔍 搜索文件
📊 统计信息
查看文件索引统计数据
import pkg from 'node-sqlite3-wasm'; const { Database: SQLite3Database } = pkg; import { fileURLToPath } from 'url'; import path from 'path'; import fs from 'fs'; import Fastify from 'fastify'; // 获取当前脚本所在目录 const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const DB_PATH = path.join(__dirname, 'index.db'); /** * 文件索引器类 */ class FileIndexer { constructor() { this.db = null; this.baseDirectory = __dirname; } /** * 初始化数据库连接 */ async initDatabase() { try { console.log('正在初始化数据库:', DB_PATH); this.db = new SQLite3Database(DB_PATH); // 创建文件索引表 this.db.run(` CREATE TABLE IF NOT EXISTS file_index ( id INTEGER PRIMARY KEY AUTOINCREMENT, file_path TEXT NOT NULL UNIQUE, file_name TEXT NOT NULL, file_size INTEGER NOT NULL, file_type TEXT NOT NULL, is_directory INTEGER NOT NULL DEFAULT 0, relative_path TEXT NOT NULL, last_modified INTEGER NOT NULL, created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) ) `); // 创建索引以提高查询性能 this.db.run(`CREATE INDEX IF NOT EXISTS idx_file_name ON file_index(file_name)`); this.db.run(`CREATE INDEX IF NOT EXISTS idx_file_type ON file_index(file_type)`); this.db.run(`CREATE INDEX IF NOT EXISTS idx_relative_path ON file_index(relative_path)`); console.log('数据库初始化成功'); } catch (error) { console.error('数据库初始化失败:', error); throw error; } } /** * 关闭数据库连接 */ async closeDatabase() { if (this.db) { await this.db.close(); this.db = null; } } /** * 获取文件类型 */ getFileType(filePath) { const ext = path.extname(filePath).toLowerCase(); if (!ext) return 'unknown'; const typeMap = { '.js': 'javascript', '.mjs': 'javascript', '.ts': 'typescript', '.json': 'json', '.txt': 'text', '.md': 'markdown', '.html': 'html', '.css': 'css', '.png': 'image', '.jpg': 'image', '.jpeg': 'image', '.gif': 'image', '.svg': 'image', '.mp4': 'video', '.avi': 'video', '.mkv': 'video', '.mp3': 'audio', '.wav': 'audio', '.pdf': 'document', '.doc': 'document', '.docx': 'document', '.zip': 'archive', '.rar': 'archive', '.7z': 'archive' }; return typeMap[ext] || 'other'; } /** * 递归扫描目录 */ async scanDirectory(dirPath = this.baseDirectory) { const files = []; try { const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); const relativePath = path.relative(this.baseDirectory, fullPath); // 排除自己和index.db文件 if (entry.name === 'file-indexer.mjs' || entry.name === 'index.db') { continue; } try { const stats = fs.statSync(fullPath); const fileInfo = { file_path: fullPath, file_name: entry.name, file_size: stats.size, file_type: entry.isDirectory() ? 'directory' : this.getFileType(entry.name), is_directory: entry.isDirectory() ? 1 : 0, relative_path: relativePath, last_modified: Math.floor(stats.mtime.getTime() / 1000) }; files.push(fileInfo); // 如果是目录,递归扫描 if (entry.isDirectory()) { const subFiles = await this.scanDirectory(fullPath); files.push(...subFiles); } } catch (error) { console.warn(`无法访问文件 ${fullPath}:`, error.message); } } } catch (error) { console.error(`扫描目录失败 ${dirPath}:`, error); } return files; } /** * 清空并重建索引 */ async rebuildIndex() { const startTime = Date.now(); try { console.log('开始重建文件索引...'); // 清空现有数据 this.db.run('DELETE FROM file_index'); // 扫描文件 const files = await this.scanDirectory(); console.log(`发现 ${files.length} 个文件/目录`); // 批量插入数据 const stmt = this.db.prepare(` INSERT OR REPLACE INTO file_index (file_path, file_name, file_size, file_type, is_directory, relative_path, last_modified) VALUES (?, ?, ?, ?, ?, ?, ?) `); for (const file of files) { stmt.run([ file.file_path, file.file_name, file.file_size, file.file_type, file.is_directory, file.relative_path, file.last_modified ]); } stmt.finalize(); const endTime = Date.now(); const duration = endTime - startTime; const durationFormatted = `${duration}ms`; console.log(`文件索引重建完成,耗时: ${durationFormatted}`); return { success: true, count: files.length, duration: duration, durationFormatted: durationFormatted }; } catch (error) { const endTime = Date.now(); const duration = endTime - startTime; console.error(`重建索引失败,耗时: ${duration}ms,错误:`, error); throw error; } } /** * 搜索文件 */ searchFiles(query = '', fileType = '', limit = 100, offset = 0) { try { let sql = ` SELECT file_path, file_name, file_size, file_type, is_directory, relative_path, last_modified, created_at, updated_at FROM file_index WHERE 1=1 `; const params = []; // 文件名模糊搜索 if (query) { sql += ` AND (file_name LIKE ? OR relative_path LIKE ?)`; params.push(`%${query}%`, `%${query}%`); } // 文件类型过滤 if (fileType) { sql += ` AND file_type = ?`; params.push(fileType); } sql += ` ORDER BY file_name ASC LIMIT ? OFFSET ?`; params.push(limit, offset); const results = this.db.all(sql, params); // 获取总数 let countSql = `SELECT COUNT(*) as total FROM file_index WHERE 1=1`; const countParams = []; if (query) { countSql += ` AND (file_name LIKE ? OR relative_path LIKE ?)`; countParams.push(`%${query}%`, `%${query}%`); } if (fileType) { countSql += ` AND file_type = ?`; countParams.push(fileType); } const countResult = this.db.get(countSql, countParams); return { files: results.map(file => ({ ...file, file_size_formatted: this.formatFileSize(file.file_size), last_modified_formatted: new Date(file.last_modified * 1000).toLocaleString(), is_directory: Boolean(file.is_directory) })), total: countResult.total, limit, offset }; } catch (error) { console.error('搜索文件失败:', error); throw error; } } /** * 格式化文件大小 */ formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } /** * 获取统计信息 */ getStats() { try { const totalFiles = this.db.get('SELECT COUNT(*) as count FROM file_index WHERE is_directory = 0'); const totalDirs = this.db.get('SELECT COUNT(*) as count FROM file_index WHERE is_directory = 1'); const totalSize = this.db.get('SELECT SUM(file_size) as size FROM file_index WHERE is_directory = 0'); const fileTypes = this.db.all(` SELECT file_type, COUNT(*) as count FROM file_index WHERE is_directory = 0 GROUP BY file_type ORDER BY count DESC `); return { total_files: totalFiles.count, total_directories: totalDirs.count, total_size: totalSize.size || 0, total_size_formatted: this.formatFileSize(totalSize.size || 0), file_types: fileTypes }; } catch (error) { console.error('获取统计信息失败:', error); throw error; } } } /** * 创建Fastify服务器 */ async function createServer() { const server = Fastify({ logger: true }); const indexer = new FileIndexer(); // 初始化数据库 await indexer.initDatabase(); // 设置CORS server.addHook('onRequest', async (request, reply) => { reply.header('Access-Control-Allow-Origin', '*'); reply.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); reply.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (request.method === 'OPTIONS') { reply.code(200).send(); } }); // 根路径 - HTML界面 server.get('/', async (request, reply) => { reply.type('text/html'); return `
API测试界面 - 轻松管理和搜索文件
扫描目录并重建文件索引
查看文件索引统计数据