os.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import { getMainWindow } from "ee-core/electron";
  2. import { app, dialog, shell } from "electron";
  3. import { logger } from "ee-core/log";
  4. import path from "path";
  5. import fs from "fs";
  6. import http from "http";
  7. import https from "https";
  8. import FormData from "form-data";
  9. class Os {
  10. async minimizeWindow() {
  11. getMainWindow().minimize();
  12. }
  13. async maximizeWindow() {
  14. getMainWindow().maximize();
  15. }
  16. async unmaximizeWindow() {
  17. getMainWindow().unmaximize();
  18. }
  19. async closeWindow() {
  20. getMainWindow().close();
  21. }
  22. async isMaximized() {
  23. return getMainWindow().isMaximized();
  24. }
  25. async toggleDevTools() {
  26. getMainWindow().webContents.toggleDevTools();
  27. }
  28. async openUserDataFolder() {
  29. const userDataPath = app.getPath('userData');
  30. await shell.openPath(userDataPath);
  31. }
  32. async openInstallFolder() {
  33. const exePath = app.getPath('exe');
  34. const installDir = path.dirname(exePath);
  35. await shell.openPath(installDir);
  36. }
  37. async selectDirectory() {
  38. const win = getMainWindow();
  39. const result = await dialog.showOpenDialog(win, {
  40. title: '选择目录',
  41. properties: ['openDirectory', 'createDirectory'],
  42. });
  43. if (result.canceled || !result.filePaths || result.filePaths.length === 0) {
  44. return '';
  45. }
  46. return result.filePaths[0];
  47. }
  48. async getAppInfo() {
  49. return {
  50. version: app.getVersion(),
  51. name: app.getName(),
  52. electronVersion: process.versions.electron,
  53. chromeVersion: process.versions.chrome,
  54. nodeVersion: process.versions.node,
  55. platform: process.platform,
  56. arch: process.arch,
  57. userDataPath: app.getPath('userData'),
  58. exePath: app.getPath('exe'),
  59. };
  60. }
  61. async openExternal(url) {
  62. if (!url || typeof url !== 'string') return false;
  63. try {
  64. await shell.openExternal(url);
  65. return true;
  66. } catch (e) {
  67. logger.error('[os] openExternal failed:', e);
  68. return false;
  69. }
  70. }
  71. /**
  72. * 通过 Node.js 主进程流式上传清鉴数据源文件
  73. * - 校验选中目录最后一级必须为 QingJian
  74. * - 在 QingJian 目录下拼接 workspace 作为扫描根
  75. * - 扫描 workspace 下一层文件夹,再读取每个文件夹下的文件(不递归更深层)
  76. * - 用 fs.createReadStream 流式读取,避免大文件占内存
  77. * - 用 form-data 构建 multipart,http/https 模块发送
  78. * @param params.dirPath 目录完整磁盘路径(最后一级必须为 QingJian)
  79. * @param params.uploadUrl 后端接口完整 URL
  80. * @param params.token 鉴权 token(可选)
  81. */
  82. async uploadQingJianFiles(params) {
  83. logger.info('[os] uploadQingJianFiles params:', params);
  84. const { dirPath, uploadUrl, token } = params || {};
  85. if (!dirPath || !uploadUrl) {
  86. logger.error('[os] uploadQingJianFiles params missing:', params);
  87. throw new Error('请选择 QingJian 目录');
  88. }
  89. // 1. 校验选中目录最后一级必须为 QingJian
  90. const dirName = path.basename(dirPath);
  91. if (dirName !== 'QingJian') {
  92. throw new Error('请选择 QingJian 目录');
  93. }
  94. // 2. 在选中目录下拼接 workspace,作为扫描根目录
  95. const workspacePath = path.join(dirPath, 'workspace');
  96. const platPath = path.join(dirPath, 'db',"plat");
  97. // 3. 扫描 workspace 下一层文件夹,再读取每个文件夹下的文件(不递归更深层)
  98. // id = 文件直接所在的父级目录名(即子文件夹名)
  99. const subEntries = await fs.promises.readdir(workspacePath, { withFileTypes: true });
  100. const subDirs = subEntries.filter(e => e.isDirectory());
  101. if (subDirs.length === 0) {
  102. throw new Error('workspace 目录下没有子文件夹');
  103. }
  104. const files = [];
  105. for (const sub of subDirs) {
  106. const subDirPath = path.join(workspacePath, sub.name);
  107. const subFiles = await fs.promises.readdir(subDirPath, { withFileTypes: true });
  108. for (const f of subFiles) {
  109. if (f.isFile()) {
  110. files.push({ id: sub.name, name: f.name, filePath: path.join(subDirPath, f.name) });
  111. }
  112. }
  113. }
  114. files.push({ id: 'plat', name: 'plat', filePath: platPath });
  115. if (files.length === 0) {
  116. throw new Error('workspace 的子文件夹下没有可上传的文件');
  117. }
  118. // 4. 构建 multipart FormData
  119. const form = new FormData();
  120. form.append('path', dirPath);
  121. for (let i = 0; i < files.length; i++) {
  122. const { id, name: fileName, filePath } = files[i];
  123. const stream = fs.createReadStream(filePath);
  124. form.append(`items[${i}].file`, stream, { filename: fileName });
  125. form.append(`items[${i}].name`, fileName);
  126. form.append(`items[${i}].id`, id);
  127. }
  128. // 3. 发送 HTTP 请求(流式 pipe,不占内存)
  129. return await new Promise((resolve, reject) => {
  130. const urlObj = new URL(uploadUrl);
  131. const lib = urlObj.protocol === 'https:' ? https : http;
  132. const headers = form.getHeaders();
  133. if (token) {
  134. headers['Authorization'] = token;
  135. }
  136. const req = lib.request(uploadUrl, {
  137. method: 'POST',
  138. headers,
  139. }, (res) => {
  140. let data = '';
  141. res.setEncoding('utf8');
  142. res.on('data', (chunk) => { data += chunk; });
  143. res.on('end', () => {
  144. const status = res.statusCode || 0;
  145. if (status >= 200 && status < 300) {
  146. try {
  147. resolve(JSON.parse(data));
  148. } catch {
  149. resolve(data);
  150. }
  151. } else {
  152. reject(new Error(`HTTP ${status}: ${data}`));
  153. }
  154. });
  155. });
  156. req.on('error', (e) => {
  157. logger.error('[os] uploadQingJianFiles request error:', e);
  158. reject(e.message);
  159. });
  160. form.pipe(req);
  161. });
  162. }
  163. }
  164. Os.toString = () => 'Os';
  165. export const os = new Os();