From 6d9f9269b7ba5f5d47b7e260ec7ce8422e980019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=98=89=E4=BC=9F?= <8473136@qq.com> Date: Mon, 24 Feb 2025 14:35:11 +0800 Subject: [PATCH] :building_construction: refactor FrpcProcessService and enhance process management --- electron/controller/LaunchController.ts | 16 +++- electron/controller/VersionController.ts | 11 ++- electron/core/IpcRouter.ts | 16 ++++ electron/dao/BaseDao.ts | 2 +- electron/dao/ServerDao.ts | 2 +- electron/main/index.ts | 7 -- electron/service/FrpcProcessService.ts | 59 +++++++++---- electron/service/ServerService.ts | 26 +++--- electron/service/VersionService.ts | 103 +++++++++++++---------- electron/utils/PathUtils.ts | 6 ++ src/views/config/index.vue | 38 ++++----- src/views/home/index.vue | 62 ++++++++------ types/frpc-desktop.d.ts | 2 +- 13 files changed, 214 insertions(+), 136 deletions(-) diff --git a/electron/controller/LaunchController.ts b/electron/controller/LaunchController.ts index b742216..ed6b8e1 100644 --- a/electron/controller/LaunchController.ts +++ b/electron/controller/LaunchController.ts @@ -1,5 +1,6 @@ import BaseController from "./BaseController"; import FrpcProcessService from "../service/FrpcProcessService"; +import { success } from "../utils/response"; class LaunchController extends BaseController { private readonly _frpcProcessService: FrpcProcessService; @@ -10,7 +11,20 @@ class LaunchController extends BaseController { } launch(req: ControllerParam) { - this._frpcProcessService.startFrpcProcess().then(r => {}); + this._frpcProcessService.startFrpcProcess().then(r => { + req.event.reply(req.channel, success()); + }); + } + + terminate(req: ControllerParam) { + this._frpcProcessService.stopFrpcProcess().then(r => { + req.event.reply(req.channel, success()); + }); + } + + getStatus(req: ControllerParam) { + const running = this._frpcProcessService.isRunning(); + req.event.reply(req.channel, success(running)); } } diff --git a/electron/controller/VersionController.ts b/electron/controller/VersionController.ts index 5c546d2..415f6aa 100644 --- a/electron/controller/VersionController.ts +++ b/electron/controller/VersionController.ts @@ -63,8 +63,15 @@ class VersionController extends BaseController { }); } - importLocalFrpcVersion (){ - + importLocalFrpcVersion(req: ControllerParam) { + this._versionService + .importLocalFrpcVersion(req.win) + .then(data => { + req.event.reply(req.channel, success()); + }) + .catch(err => { + req.event.reply(req.channel, fail()); + }); } } diff --git a/electron/core/IpcRouter.ts b/electron/core/IpcRouter.ts index a5ab6a1..2f28788 100644 --- a/electron/core/IpcRouter.ts +++ b/electron/core/IpcRouter.ts @@ -52,12 +52,24 @@ export const ipcRouters: IpcRouters = { deleteDownloadedVersion: { path: "version/deleteDownloadedVersion", controller: "versionController.deleteDownloadedVersion" + }, + importLocalFrpcVersion: { + path: "version/importLocalFrpcVersion", + controller: "versionController.importLocalFrpcVersion" } }, LAUNCH: { launch: { path: "launch/launch", controller: "launchController.launch" + }, + terminate: { + path: "launch/terminate", + controller: "launchController.terminate" + }, + getStatus: { + path: "launch/getStatus", + controller: "launchController.getStatus" } }, PROXY: { @@ -92,6 +104,10 @@ export const listeners: Listeners = { watchFrpcLog: { listenerMethod: "logService.watchFrpcLog", channel: "log:watchFrpcLog" + }, + watchFrpcProcess: { + listenerMethod: "frpcProcessService.watchFrpcProcess", + channel: "frpcProcess:watchFrpcLog" } }; diff --git a/electron/dao/BaseDao.ts b/electron/dao/BaseDao.ts index 31d58a2..692e88a 100644 --- a/electron/dao/BaseDao.ts +++ b/electron/dao/BaseDao.ts @@ -24,7 +24,7 @@ class BaseDao { protected readonly db: Datastore; constructor(dbName: string) { - const dbFilename = path.join(PathUtils.getAppData(), `${dbName}-v2.db`); + const dbFilename = path.join(PathUtils.getDataBaseStoragePath(), `${dbName}-v2.db`); this.db = new Datastore({ autoload: true, filename: dbFilename diff --git a/electron/dao/ServerDao.ts b/electron/dao/ServerDao.ts index d3d101d..7c812cc 100644 --- a/electron/dao/ServerDao.ts +++ b/electron/dao/ServerDao.ts @@ -1,6 +1,6 @@ import BaseDao from "./BaseDao"; -class ServerDao extends BaseDao { +class ServerDao extends BaseDao { constructor() { super("server"); } diff --git a/electron/main/index.ts b/electron/main/index.ts index 0a7efe5..db63ba6 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -199,19 +199,12 @@ app.whenReady().then(() => { initConfigApi(win); logInfo(LogModule.APP, `Config API initialized.`); - - initFrpcApi(); - logInfo(LogModule.APP, `FRPC API initialized.`); - - // logInfo(LogModule.APP, `Logger API initialized.`); - initFileApi(); logInfo(LogModule.APP, `File API initialized.`); initCommonApi(); logInfo(LogModule.APP, `Common API initialized.`); - // initUpdaterApi(win); logInfo(LogModule.APP, `Updater API initialization skipped.`); } catch (error) { diff --git a/electron/service/FrpcProcessService.ts b/electron/service/FrpcProcessService.ts index 7c6a3cb..de5e374 100644 --- a/electron/service/FrpcProcessService.ts +++ b/electron/service/FrpcProcessService.ts @@ -3,15 +3,14 @@ import VersionDao from "../dao/VersionDao"; import PathUtils from "../utils/PathUtils"; import GlobalConstant from "../core/GlobalConstant"; import { Notification } from "electron"; -import { logDebug, LogModule } from "../utils/log"; - -const { exec, spawn } = require("child_process"); +import { success } from "../utils/response"; +import treeKill from "tree-kill"; class FrpcProcessService { private readonly _serverService: ServerService; private readonly _versionDao: VersionDao; private _frpcProcess: any; - private _FrpcProcessListener: any; + private _frpcProcessListener: any; constructor(serverService: ServerService, versionDao: VersionDao) { this._serverService = serverService; @@ -19,7 +18,15 @@ class FrpcProcessService { } isRunning(): boolean { - return false; + if (!this._frpcProcess) { + return false; + } + try { + process.kill(this._frpcProcess.pid, 0); + return true; + } catch (err) { + return false; + } } async startFrpcProcess() { @@ -28,35 +35,47 @@ class FrpcProcessService { config.frpcVersion ); // todo genConfigfile. - await this._serverService.genTomlConfig(); - const configPath = ""; - const command = `${PathUtils.getFrpcFilename()} -c ${configPath}`; - this._frpcProcess = spawn(command, { + const configPath = await this._serverService.genTomlConfig(); + const command = `./${PathUtils.getFrpcFilename()} -c "${configPath}"`; + this._frpcProcess = require("child_process").spawn(command, { cwd: version.localPath, shell: true }); - this._frpcProcess.stdout.on("data", data => { - logDebug(LogModule.FRP_CLIENT, `Frpc process output: ${data}`); + console.log(`stdout: ${data}`); }); - this._frpcProcess.stdout.on("error", data => { - // logError(LogModule.FRP_CLIENT, `Frpc process error: ${data}`); - // stopFrpcProcess(() => {}); - this.stopFrpcProcess(); + this._frpcProcess.stderr.on("data", data => { + console.error(`stderr: ${data}`); }); + // this._frpcProcess.on("close",function(code){ + // console.log("out code:" + code) + // }) } - stopFrpcProcess() {} + async stopFrpcProcess() { + if (this._frpcProcess) { + treeKill(this._frpcProcess.pid, (error: Error) => { + if (error) { + throw error; + } else { + this._frpcProcess = null; + // clearInterval(this._frpcProcessListener); + } + }); + } else { + } + } watchFrpcProcess(listenerParam: ListenerParam) { - this._FrpcProcessListener = setInterval(() => { + this._frpcProcessListener = setInterval(() => { const running = this.isRunning(); // todo return status to view. // logDebug( // LogModule.FRP_CLIENT, // `Monitoring frpc process status: ${status}, Listener ID: ${frpcStatusListener}` // ); + console.log("running", running); if (!running) { new Notification({ title: GlobalConstant.APP_NAME, @@ -66,8 +85,12 @@ class FrpcProcessService { // LogModule.FRP_CLIENT, // "Frpc process status check failed. Connection lost." // ); - clearInterval(this._FrpcProcessListener); + // clearInterval(this._frpcProcessListener); } + listenerParam.win.webContents.send( + listenerParam.channel, + success(running) + ); }, GlobalConstant.FRPC_PROCESS_STATUS_CHECK_INTERVAL); } } diff --git a/electron/service/ServerService.ts b/electron/service/ServerService.ts index 6a89992..6b9079c 100644 --- a/electron/service/ServerService.ts +++ b/electron/service/ServerService.ts @@ -5,7 +5,7 @@ import fs from "fs"; import PathUtils from "../utils/PathUtils"; import ProxyDao from "../dao/ProxyDao"; -class ServerService extends BaseService { +class ServerService extends BaseService { private readonly _serverDao: ServerDao; private readonly _proxyDao: ProxyDao; private readonly _serverId: string = "1"; @@ -17,13 +17,13 @@ class ServerService extends BaseService { } async saveServerConfig( - frpcServer: FrpcDesktopServer - ): Promise { + frpcServer: OpenSourceFrpcDesktopServer + ): Promise { frpcServer._id = this._serverId; return await this._serverDao.updateById(this._serverId, frpcServer); } - async getServerConfig(): Promise { + async getServerConfig(): Promise { return await this._serverDao.findById(this._serverId); } @@ -45,23 +45,19 @@ class ServerService extends BaseService { .filter(f => f.status === 1) .map(proxy => { const { _id, status, ...frpProxyConfig } = proxy; - return frpProxyConfig + return frpProxyConfig; }); const { frpcVersion, _id, system, ...commonConfig } = server; const frpcConfig = { ...commonConfig }; frpcConfig.log.to = PathUtils.getFrpcLogFilePath(); - const toml = TOML.stringify({ ...frpcConfig, enabledProxies }); - fs.writeFile( - PathUtils.getTomlConfigFilePath(), // 配置文件目录 + const toml = TOML.stringify({ ...frpcConfig, proxies: enabledProxies }); + const tomlPath = PathUtils.getTomlConfigFilePath(); + fs.writeFileSync( + tomlPath, // 配置文件目录 toml, // 配置文件内容 - { flag: "w" }, - err => { - if (err) { - } else { - // callback(filename); - } - } + { flag: "w" } ); + return tomlPath; } } diff --git a/electron/service/VersionService.ts b/electron/service/VersionService.ts index 7a76147..e57bac2 100644 --- a/electron/service/VersionService.ts +++ b/electron/service/VersionService.ts @@ -63,52 +63,13 @@ class VersionService extends BaseService { onProgress(progress); }, onCompleted: () => { - const ext = path.extname(version.assetName); - if (ext === GlobalConstant.ZIP_EXT) { - this._fileService.decompressZipFile( - downloadedFilePath, - versionFilePath - ); - // todo delete frps and other file. - } else if ( - ext === GlobalConstant.GZ_EXT && - version.assetName.includes(GlobalConstant.TAR_GZ_EXT) - ) { - this._fileService.decompressTarGzFile( - downloadedFilePath, - versionFilePath, - () => { - // rename frpc. - const frpcFilePath = path.join(versionFilePath, "frpc"); - if (fs.existsSync(frpcFilePath)) { - const newFrpcFilePath = path.join( - versionFilePath, - PathUtils.getFrpcFilename() - ); - fs.renameSync(frpcFilePath, newFrpcFilePath); - } - // delete downloaded file. - // todo has bug. - const downloadedFile = path.join( - PathUtils.getDownloadStoragePath(), - version.assetName - ); - if (fs.existsSync(downloadedFile)) { - fs.rmSync(downloadedFile, { recursive: true, force: true }); - } - } - ); - } - - // todo 2025-02-23 delete downloaded file. - version.localPath = versionFilePath; - version.downloaded = true; - this._versionDao - .insert(version) + this.decompressFrp(version, downloadedFilePath) .then(data => { resolve(data); }) - .catch(err => reject(err)); + .catch(err => { + reject(err); + }); } }); }); @@ -219,7 +180,18 @@ class VersionService extends BaseService { if (frpName) { if (this._currFrpArch.every(item => frpName.includes(item))) { const version = this.getFrpVersionByAssetName(frpName); + const existsVersion = await this._versionDao.findByGithubReleaseId( + version.githubReleaseId + ); + if (existsVersion) { + throw new Error("导入失败,版本已存在"); + } + return this.decompressFrp(version, filePath); + } else { + throw new Error(`导入失败,所选 frp 架构与操作系统不符`); } + } else { + throw new Error("导入失败,无法识别文件"); } } } @@ -227,6 +199,51 @@ class VersionService extends BaseService { getFrpVersionByAssetName(assetName: string) { return this._versions.find(f => f.assetName === assetName); } + + async decompressFrp(version: FrpcVersion, compressedPath: string) { + const versionFilePath = path.join( + PathUtils.getVersionStoragePath(), + SecureUtils.calculateMD5(version.name) + ); + const ext = path.extname(version.assetName); + if (ext === GlobalConstant.ZIP_EXT) { + this._fileService.decompressZipFile(compressedPath, versionFilePath); + // todo delete frps and other file. + } else if ( + ext === GlobalConstant.GZ_EXT && + version.assetName.includes(GlobalConstant.TAR_GZ_EXT) + ) { + this._fileService.decompressTarGzFile( + compressedPath, + versionFilePath, + () => { + // rename frpc. + const frpcFilePath = path.join(versionFilePath, "frpc"); + if (fs.existsSync(frpcFilePath)) { + const newFrpcFilePath = path.join( + versionFilePath, + PathUtils.getFrpcFilename() + ); + fs.renameSync(frpcFilePath, newFrpcFilePath); + } + // delete downloaded file. + // todo has bug. + const downloadedFile = path.join( + PathUtils.getDownloadStoragePath(), + version.assetName + ); + if (fs.existsSync(downloadedFile)) { + fs.rmSync(downloadedFile, { recursive: true, force: true }); + } + } + ); + } + + // todo 2025-02-23 delete downloaded file. + version.localPath = versionFilePath; + version.downloaded = true; + return await this._versionDao.insert(version); + } } export default VersionService; diff --git a/electron/utils/PathUtils.ts b/electron/utils/PathUtils.ts index c1feea9..745fcab 100644 --- a/electron/utils/PathUtils.ts +++ b/electron/utils/PathUtils.ts @@ -38,6 +38,12 @@ class PathUtils { return app.getPath("userData"); } + public static getDataBaseStoragePath() { + const result = path.join(PathUtils.getAppData(), "db"); + FileUtils.mkdir(result); + return result; + } + public static getTomlConfigFilePath() { return path.join( PathUtils.getConfigStoragePath(), diff --git a/src/views/config/index.vue b/src/views/config/index.vue index 7199b62..9ccfe42 100644 --- a/src/views/config/index.vue +++ b/src/views/config/index.vue @@ -7,7 +7,7 @@ import { useDebounceFn } from "@vueuse/core"; import { clone } from "@/utils/clone"; import { Base64 } from "js-base64"; import IconifyIconOffline from "@/components/IconifyIcon/src/iconifyIconOffline"; -import { on, send } from "@/utils/ipcUtils"; +import { on, removeRouterListeners, send } from "@/utils/ipcUtils"; import { ipcRouters } from "../../../electron/core/IpcRouter"; defineComponent({ @@ -25,7 +25,8 @@ type ShareLinkConfig = { metaToken: string; }; -const defaultFormData = ref({ +const defaultFormData: OpenSourceFrpcDesktopServer = { + _id: "", frpcVersion: null, loginFailExit: false, udpPacketSize: 1500, @@ -77,9 +78,9 @@ const defaultFormData = ref({ autoConnectOnStartup: false }, user: "" -}); +}; -const formData = ref(defaultFormData.value); +const formData = ref(defaultFormData); const loading = ref(1); @@ -201,7 +202,6 @@ const handleSubmit = useDebounceFn(() => { loading.value = 1; const data = clone(formData.value); send(ipcRouters.SERVER.saveConfig, data); - // ipcRenderer.send("server/saveConfig", data); } }); }, 300); @@ -222,12 +222,12 @@ const handleAuthMethodChange = e => { }; const checkAndResetVersion = () => { - const currentVersion = formData.value.currentVersion; + const currentVersion = formData.value.frpcVersion; if ( currentVersion && - !versions.value.some(item => item.id === currentVersion) + !versions.value.some(item => item.githubReleaseId === currentVersion) ) { - formData.value.currentVersion = null; + formData.value.frpcVersion = null; } }; @@ -239,14 +239,15 @@ const handleLoadSavedConfig = () => { send(ipcRouters.SERVER.getServerConfig); }; - onMounted(() => { handleLoadDownloadedVersion(); handleLoadSavedConfig(); on(ipcRouters.SERVER.getServerConfig, data => { console.log("data", data); - formData.value = data; + if (data) { + formData.value = data; + } loading.value--; }); @@ -263,8 +264,6 @@ onMounted(() => { }); loading.value--; }); - // ipcRenderer.send("config.getConfig"); - // handleLoadVersions(); // ipcRenderer.on("Config.getConfig.hook", (event, args) => { // const { err, data } = args; // if (!err) { @@ -448,7 +447,6 @@ const handlePasteServerConfigBase64 = useDebounceFn(() => { } const ciphertext = pasteServerConfigBase64.value.replace("frp://", ""); const plaintext = Base64.decode(ciphertext); - console.log("plain", plaintext); let serverConfig: ShareLinkConfig = null; try { serverConfig = JSON.parse(plaintext); @@ -512,12 +510,14 @@ const handleOpenDataFolder = useDebounceFn(() => { }, 1000); onUnmounted(() => { - ipcRenderer.removeAllListeners("Config.getConfig.hook"); - ipcRenderer.removeAllListeners("Config.saveConfig.hook"); - ipcRenderer.removeAllListeners("Config.versions.hook"); - ipcRenderer.removeAllListeners("Config.exportConfig.hook"); - ipcRenderer.removeAllListeners("Config.clearAll.hook"); - ipcRenderer.removeAllListeners("Config.openDataFolder.hook"); + removeRouterListeners(ipcRouters.SERVER.saveConfig); + removeRouterListeners(ipcRouters.VERSION.getDownloadedVersions); + removeRouterListeners(ipcRouters.SERVER.getServerConfig); + removeRouterListeners(ipcRouters.SERVER.saveConfig); + removeRouterListeners(ipcRouters.VERSION.getDownloadedVersions); + // ipcRenderer.removeAllListeners("Config.exportConfig.hook"); + // ipcRenderer.removeAllListeners("Config.clearAll.hook"); + // ipcRenderer.removeAllListeners("Config.openDataFolder.hook"); });