Files
LagZero/electron/services/process.ts
T
ZenEcho 23938d6479 refactor(electron): 重构项目结构并引入服务层架构
- 将 electron 代码重构为基于服务的模块化架构,提升可维护性
- 将 `electron/games/manager.ts` 移至 `electron/services/game.ts` 并重构为 GameService
- 新增多个服务模块:category, database, game-scanner, node, process, proxy-monitor, singbox, system, updater
- 新增工具模块:command, format, id, ping, port, process-helper
- 新增公共模块:store(基于 JSON 的持久化存储)
- 新增文档:singbox-config.md 和 electron-structure.md 说明新架构
- 更新主入口文件 `electron/main/index.ts` 以初始化所有服务
- 更新预加载脚本 `electron/preload/index.ts` 暴露新的 API
- 更新构建配置以支持新的目录结构
- 修复主入口文件路径配置
2026-02-18 15:49:27 +08:00

135 lines
3.9 KiB
TypeScript

import { exec } from 'child_process'
import { ipcMain } from 'electron'
import { promisify } from 'util'
const execAsync = promisify(exec)
/**
* 进程节点结构
*/
export interface ProcessNode {
path: string
name: string
ppid: number
pid: number
children: ProcessNode[]
}
/**
* 进程管理服务
*
* 提供跨平台的进程扫描和进程树获取功能。
* 主要用于识别游戏进程及其父子关系。
*/
export class ProcessService {
constructor() {
this.setupIPC()
}
private setupIPC() {
ipcMain.handle('process-scan', async () => {
return await this.scanProcesses()
})
ipcMain.handle('process-tree', async () => {
return await this.getProcessTree()
})
}
/**
* 扫描当前运行的所有进程
*
* @returns Promise<string[]> - 返回进程名列表 (如 ['chrome.exe', 'svchost.exe'])
*/
async scanProcesses(): Promise<string[]> {
try {
let command = ''
if (process.platform === 'win32') {
command = 'tasklist /FO CSV /NH'
} else if (process.platform === 'darwin' || process.platform === 'linux') {
command = 'ps -e -o comm='
}
if (!command) return []
const { stdout } = await execAsync(command)
if (process.platform === 'win32') {
// Parse CSV format from tasklist
// "Image Name","PID","Session Name","Session#","Mem Usage"
return stdout
.split('\r\n')
.map(line => {
const match = line.match(/^"([^"]+)"/)
return match ? match[1] : ''
})
.filter(name => name)
} else {
// Parse simple list for unix
return stdout
.split('\n')
.map(line => line.trim())
.filter(name => name)
.map(path => {
// Extract filename from path
const parts = path.split('/')
return parts[parts.length - 1]
})
}
} catch (error) {
console.error('Failed to scan processes:', error)
return []
}
}
/**
* 获取系统进程树
*
* 仅支持 Windows 平台 (使用 wmic)。
* 返回包含 PID, PPID, Name, ExecutablePath 的树状结构或扁平列表。
*
* @returns Promise<any[]> - 进程树根节点列表
*/
async getProcessTree(): Promise<ProcessNode[]> {
try {
if (process.platform === 'win32') {
// Use wmic to get process tree info
const { stdout } = await execAsync('wmic process get ProcessId,ParentProcessId,Name,ExecutablePath /FORMAT:CSV')
const lines = stdout.trim().split('\r\n').slice(1) // Skip header
const processes = lines.map(line => {
const parts = line.split(',')
// Node,ExecutablePath,Name,ParentProcessId,ProcessId
// wmic CSV output might contain empty lines or different column order depending on locale?
// Actually wmic /FORMAT:CSV output order is fixed: Node, ...columns sorted alphabetically
// Columns: ExecutablePath, Name, ParentProcessId, ProcessId
if (parts.length < 5) return null
return {
path: parts[1],
name: parts[2],
ppid: parseInt(parts[3], 10),
pid: parseInt(parts[4], 10),
children: []
} as ProcessNode
}).filter((p): p is ProcessNode => !!p && !!p.pid)
// Build tree
const processMap = new Map<number, ProcessNode>()
processes.forEach(p => processMap.set(p.pid, p))
const rootProcesses: ProcessNode[] = []
processes.forEach(p => {
if (p.ppid && processMap.has(p.ppid)) {
processMap.get(p.ppid)!.children.push(p)
} else {
rootProcesses.push(p)
}
})
return rootProcesses
}
return []
} catch (error) {
console.error('Failed to get process tree:', error)
return []
}
}
}