🏗️ refactor FrpcProcessService and enhance process management

This commit is contained in:
刘嘉伟 2025-02-24 14:35:11 +08:00
parent 9f46ea781d
commit 6d9f9269b7
13 changed files with 214 additions and 136 deletions

View File

@ -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));
}
}

View File

@ -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());
});
}
}

View File

@ -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"
}
};

View File

@ -24,7 +24,7 @@ class BaseDao<T> {
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

View File

@ -1,6 +1,6 @@
import BaseDao from "./BaseDao";
class ServerDao extends BaseDao<FrpcDesktopServer> {
class ServerDao extends BaseDao<OpenSourceFrpcDesktopServer> {
constructor() {
super("server");
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -5,7 +5,7 @@ import fs from "fs";
import PathUtils from "../utils/PathUtils";
import ProxyDao from "../dao/ProxyDao";
class ServerService extends BaseService<FrpcDesktopServer> {
class ServerService extends BaseService<OpenSourceFrpcDesktopServer> {
private readonly _serverDao: ServerDao;
private readonly _proxyDao: ProxyDao;
private readonly _serverId: string = "1";
@ -17,13 +17,13 @@ class ServerService extends BaseService<FrpcDesktopServer> {
}
async saveServerConfig(
frpcServer: FrpcDesktopServer
): Promise<FrpcDesktopServer> {
frpcServer: OpenSourceFrpcDesktopServer
): Promise<OpenSourceFrpcDesktopServer> {
frpcServer._id = this._serverId;
return await this._serverDao.updateById(this._serverId, frpcServer);
}
async getServerConfig(): Promise<FrpcDesktopServer> {
async getServerConfig(): Promise<OpenSourceFrpcDesktopServer> {
return await this._serverDao.findById(this._serverId);
}
@ -45,23 +45,19 @@ class ServerService extends BaseService<FrpcDesktopServer> {
.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;
}
}

View File

@ -63,52 +63,13 @@ class VersionService extends BaseService<FrpcVersion> {
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<FrpcVersion> {
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<FrpcVersion> {
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;

View File

@ -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(),

View File

@ -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<OpenSourceFrpcDesktopServer>({
const defaultFormData: OpenSourceFrpcDesktopServer = {
_id: "",
frpcVersion: null,
loginFailExit: false,
udpPacketSize: 1500,
@ -77,9 +78,9 @@ const defaultFormData = ref<OpenSourceFrpcDesktopServer>({
autoConnectOnStartup: false
},
user: ""
});
};
const formData = ref<OpenSourceFrpcDesktopServer>(defaultFormData.value);
const formData = ref<OpenSourceFrpcDesktopServer>(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");
});
</script>
<template>

View File

@ -1,12 +1,9 @@
<script lang="ts" setup>
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
import Breadcrumb from "@/layout/compoenets/Breadcrumb.vue";
import { ipcRenderer } from "electron";
import { ElMessageBox } from "element-plus";
import router from "@/router";
import { useDebounceFn, useIntervalFn } from "@vueuse/core";
import { send } from "@/utils/ipcUtils";
import { ipcRouters } from "../../../electron/core/IpcRouter";
import { useDebounceFn } from "@vueuse/core";
import { on, onListener, removeRouterListeners2, send } from "@/utils/ipcUtils";
import { ipcRouters, listeners } from "../../../electron/core/IpcRouter";
defineComponent({
name: "Home"
@ -15,12 +12,11 @@ defineComponent({
const running = ref(false);
const handleStartFrpc = () => {
// ipcRenderer.send("frpc.start");
send(ipcRouters.LAUNCH.launch);
};
const handleStopFrpc = () => {
ipcRenderer.send("frpc.stop");
send(ipcRouters.LAUNCH.terminate);
};
const handleButtonClick = useDebounceFn(() => {
@ -32,30 +28,40 @@ const handleButtonClick = useDebounceFn(() => {
}, 300);
onMounted(() => {
useIntervalFn(() => {
ipcRenderer.invoke("frpc.running").then(data => {
running.value = data;
console.log("进程状态", data);
});
}, 500);
ipcRenderer.on("Home.frpc.start.error.hook", (event, args) => {
if (args) {
ElMessageBox.alert(args, "提示", {
showCancelButton: true,
cancelButtonText: "取消",
confirmButtonText: "去设置"
}).then(() => {
router.replace({
name: "Config"
});
});
}
onListener(listeners.watchFrpcProcess, data => {
console.log("watchFrpcProcess", data);
running.value = data;
});
on(ipcRouters.LAUNCH.getStatus, data => {
running.value = data;
});
on(ipcRouters.LAUNCH.launch, () => {
send(ipcRouters.LAUNCH.getStatus);
});
on(ipcRouters.LAUNCH.terminate, () => {
send(ipcRouters.LAUNCH.getStatus);
});
// ipcRenderer.on("Home.frpc.start.error.hook", (event, args) => {
// if (args) {
// ElMessageBox.alert(args, "", {
// showCancelButton: true,
// cancelButtonText: "",
// confirmButtonText: ""
// }).then(() => {
// router.replace({
// name: "Config"
// });
// });
// }
// });
});
onUnmounted(() => {
ipcRenderer.removeAllListeners("Home.frpc.start.error.hook");
// ipcRenderer.removeAllListeners("Home.frpc.start.error.hook");
removeRouterListeners2(listeners.watchFrpcProcess);
});
</script>

View File

@ -13,7 +13,7 @@ interface FrpcSystemConfiguration {
type FrpcDesktopServer = BaseEntity &
FrpcCommonConfig & {
frpcVersion: number;
system: any;
// system: any;
};
type FrpcVersion = BaseEntity & {