import { getMainWindow } from "ee-core/electron"; import { app, dialog, shell } from "electron"; import { logger } from "ee-core/log"; import path from "path"; import fs from "fs"; import http from "http"; import https from "https"; import FormData from "form-data"; class Os { async minimizeWindow() { getMainWindow().minimize(); } async maximizeWindow() { getMainWindow().maximize(); } async unmaximizeWindow() { getMainWindow().unmaximize(); } async closeWindow() { getMainWindow().close(); } async isMaximized() { return getMainWindow().isMaximized(); } async toggleDevTools() { getMainWindow().webContents.toggleDevTools(); } async openUserDataFolder() { const userDataPath = app.getPath('userData'); await shell.openPath(userDataPath); } async openInstallFolder() { const exePath = app.getPath('exe'); const installDir = path.dirname(exePath); await shell.openPath(installDir); } async selectDirectory() { const win = getMainWindow(); const result = await dialog.showOpenDialog(win, { title: '选择目录', properties: ['openDirectory', 'createDirectory'], }); if (result.canceled || !result.filePaths || result.filePaths.length === 0) { return ''; } return result.filePaths[0]; } async getAppInfo() { return { version: app.getVersion(), name: app.getName(), electronVersion: process.versions.electron, chromeVersion: process.versions.chrome, nodeVersion: process.versions.node, platform: process.platform, arch: process.arch, userDataPath: app.getPath('userData'), exePath: app.getPath('exe'), }; } async openExternal(url) { if (!url || typeof url !== 'string') return false; try { await shell.openExternal(url); return true; } catch (e) { logger.error('[os] openExternal failed:', e); return false; } } /** * 通过 Node.js 主进程流式上传清鉴数据源文件 * - 校验选中目录最后一级必须为 QingJian * - 在 QingJian 目录下拼接 workspace 作为扫描根 * - 扫描 workspace 下一层文件夹,再读取每个文件夹下的文件(不递归更深层) * - 用 fs.createReadStream 流式读取,避免大文件占内存 * - 用 form-data 构建 multipart,http/https 模块发送 * @param params.dirPath 目录完整磁盘路径(最后一级必须为 QingJian) * @param params.uploadUrl 后端接口完整 URL * @param params.token 鉴权 token(可选) */ async uploadQingJianFiles(params) { logger.info('[os] uploadQingJianFiles params:', params); const { dirPath, uploadUrl, token } = params || {}; if (!dirPath || !uploadUrl) { logger.error('[os] uploadQingJianFiles params missing:', params); throw new Error('请选择 QingJian 目录'); } // 1. 校验选中目录最后一级必须为 QingJian const dirName = path.basename(dirPath); if (dirName !== 'QingJian') { throw new Error('请选择 QingJian 目录'); } // 2. 在选中目录下拼接 workspace,作为扫描根目录 const workspacePath = path.join(dirPath, 'workspace'); const platPath = path.join(dirPath, 'db',"plat"); // 3. 扫描 workspace 下一层文件夹,再读取每个文件夹下的文件(不递归更深层) // id = 文件直接所在的父级目录名(即子文件夹名) const subEntries = await fs.promises.readdir(workspacePath, { withFileTypes: true }); const subDirs = subEntries.filter(e => e.isDirectory()); if (subDirs.length === 0) { throw new Error('workspace 目录下没有子文件夹'); } const files = []; for (const sub of subDirs) { const subDirPath = path.join(workspacePath, sub.name); const subFiles = await fs.promises.readdir(subDirPath, { withFileTypes: true }); for (const f of subFiles) { if (f.isFile()) { files.push({ id: sub.name, name: f.name, filePath: path.join(subDirPath, f.name) }); } } } files.push({ id: 'plat', name: 'plat', filePath: platPath }); if (files.length === 0) { throw new Error('workspace 的子文件夹下没有可上传的文件'); } // 4. 构建 multipart FormData const form = new FormData(); form.append('path', dirPath); for (let i = 0; i < files.length; i++) { const { id, name: fileName, filePath } = files[i]; const stream = fs.createReadStream(filePath); form.append(`items[${i}].file`, stream, { filename: fileName }); form.append(`items[${i}].name`, fileName); form.append(`items[${i}].id`, id); } // 3. 发送 HTTP 请求(流式 pipe,不占内存) return await new Promise((resolve, reject) => { const urlObj = new URL(uploadUrl); const lib = urlObj.protocol === 'https:' ? https : http; const headers = form.getHeaders(); if (token) { headers['Authorization'] = token; } const req = lib.request(uploadUrl, { method: 'POST', headers, }, (res) => { let data = ''; res.setEncoding('utf8'); res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { const status = res.statusCode || 0; if (status >= 200 && status < 300) { try { resolve(JSON.parse(data)); } catch { resolve(data); } } else { reject(new Error(`HTTP ${status}: ${data}`)); } }); }); req.on('error', (e) => { logger.error('[os] uploadQingJianFiles request error:', e); reject(e.message); }); form.pipe(req); }); } } Os.toString = () => 'Os'; export const os = new Os();