diff --git a/backend/src/Dex.ts b/backend/src/Dex.ts index b8ee79f..22bfecc 100644 --- a/backend/src/Dex.ts +++ b/backend/src/Dex.ts @@ -9,7 +9,7 @@ import { dinstall, mlsetup } from "./modloader/index.js"; import config from "./utils/config.js"; import { execPromise } from "./utils/utils.js"; import { MessageWS } from "./utils/ws.js"; -import { debug } from "./utils/logger.js"; +import { logger } from "./utils/logger.js"; export class Dex { wsx!: WebSocketServer; @@ -24,136 +24,118 @@ export class Dex { public async Main(buffer: Buffer, dser: boolean) { try { const first = Date.now(); - const { contain, info } = await this._getinfo(buffer).catch((e) => { - throw new Error(e); - }); + const { contain, info } = await this._getinfo(buffer); const plat = what_platform(contain); - debug(plat); - debug(info); + logger.debug("Platform detected", plat); + logger.debug("Modpack info", info); const mpname = info.name; const unpath = `./instance/${mpname}`; + // 解压和下载(并行处理) await Promise.all([ this._unzip(buffer, mpname), - await platform(plat).downloadfile(info, unpath, this.message), - ]).catch((e) => { - throw new Error(e); - }); // 解压和下载 + platform(plat).downloadfile(info, unpath, this.message) + ]); this.message.statusChange(); //改变状态 - await new DeEarth(`${unpath}/mods`, `./.rubbish/${mpname}`) - .Main() - .catch((e) => { - throw new Error(e); - }); + await new DeEarth(`${unpath}/mods`, `./.rubbish/${mpname}`).Main(); this.message.statusChange(); //改变状态(DeEarth筛选模组完毕) - const mlinfo = await platform(plat) - .getinfo(info) - .catch((e) => { - throw new Error(e); - }); + const mlinfo = await platform(plat).getinfo(info); if (dser) { await mlsetup( mlinfo.loader, mlinfo.minecraft, mlinfo.loader_version, unpath - ).catch((e) => { - throw new Error(e); - }); //安装服务端 - } - if (!dser) { + ); // 安装服务端 + } else { dinstall( mlinfo.loader, mlinfo.minecraft, mlinfo.loader_version, unpath - ).catch((e) => { - throw new Error(e); - }); + ); } const latest = Date.now(); this.message.finish(first, latest); //完成 if (config.oaf) { - await execPromise(`start ${p.join("./instance")}`).catch((e) => { - throw new Error(e); - }); + await execPromise(`start ${p.join("./instance")}`); } - //await this._unzip(buffer); + + logger.info(`Task completed in ${latest - first}ms`); } catch (e) { const err = e as Error; + logger.error("Main process failed", err); this.message.handleError(err); } } private async _getinfo(buffer: Buffer) { - const important_name = ["manifest.json", "modrinth.index.json"]; - let contain: string = ""; - let info: any = {}; + const importantFiles = ["manifest.json", "modrinth.index.json"]; const zip = await yauzl_promise(buffer); + for await (const entry of zip) { - if (important_name.includes(entry.fileName)) { - contain = entry.fileName; - info = JSON.parse((await entry.ReadEntry).toString()); - break; + if (importantFiles.includes(entry.fileName)) { + const content = await entry.ReadEntry; + const info = JSON.parse(content.toString()); + logger.debug("Found important file", { fileName: entry.fileName, info }); + return { contain: entry.fileName, info }; } } - return { contain, info }; + + throw new Error("No manifest file found in modpack"); } private async _unzip(buffer: Buffer, instancename: string) { - /* 解压Zip */ + logger.info("Starting unzip process", { instancename }); const zip = await yauzl_promise(buffer); + const instancePath = `./instance/${instancename}`; let index = 1; + for await (const entry of zip) { - const ew = entry.fileName.endsWith("/"); - if (ew) { - await fs.promises.mkdir( - `./instance/${instancename}/${entry.fileName}`, - { - recursive: true, - } - ); - } - if (!ew && entry.fileName.startsWith("overrides/")) { - const dirPath = `./instance/${instancename}/${entry.fileName - .substring(0, entry.fileName.lastIndexOf("/")) - .replace("overrides/", "")}`; + const isDir = entry.fileName.endsWith("/"); + + if (isDir) { + await fs.promises.mkdir(`${instancePath}/${entry.fileName}`, { + recursive: true, + }); + } else if (entry.fileName.startsWith("overrides/")) { + // 跳过黑名单文件 if (this._ublack(entry.fileName)) { index++; continue; } + + // 创建目标目录 + const targetPath = entry.fileName.replace("overrides/", ""); + const dirPath = `${instancePath}/${targetPath.substring(0, targetPath.lastIndexOf("/"))}`; await fs.promises.mkdir(dirPath, { recursive: true }); + + // 解压文件 const stream = await entry.openReadStream; - const write = fs.createWriteStream( - `./instance/${instancename}/${entry.fileName.replace( - "overrides/", - "" - )}` - ); + const write = fs.createWriteStream(`${instancePath}/${targetPath}`); await pipeline(stream, write); } + this.message.unzip(entry.fileName, zip.length, index); - // this.ws.send( - // JSON.stringify({ - // status: "unzip", - // result: { name: entry.fileName, total: zip.length, current: index }, - // }) - // ); index++; } - /* 解压完成 */ + + logger.info("Unzip process completed", { instancename, totalFiles: zip.length }); } - private _ublack(filename: string) { - if (filename === "overrides/") { - return true; - } - if ( - filename.includes("shaderpacks") || - filename.includes("essential") || - filename.includes("resourcepacks") || - filename === "overrides/options.txt" - ) { - return true; - } + /** + * 检查文件是否在解压黑名单中 + * @param filename 文件名 + * @returns 是否在黑名单中 + */ + private _ublack(filename: string): boolean { + const blacklist = [ + "overrides/", + "overrides/options.txt", + "shaderpacks", + "essential", + "resourcepacks" + ]; + + return blacklist.some(item => filename.includes(item)); } } diff --git a/backend/src/core.ts b/backend/src/core.ts index 26f7feb..824bc1c 100644 --- a/backend/src/core.ts +++ b/backend/src/core.ts @@ -5,7 +5,8 @@ import websocket, { WebSocketServer } from "ws" import { createServer, Server } from "node:http"; import { Config, IConfig } from "./utils/config.js"; import { Dex } from "./Dex.js"; -import { exec } from "node:child_process"; +import { logger } from "./utils/logger.js"; +import { checkJava, JavaCheckResult } from "./utils/utils.js"; export class Core { private config: IConfig; private readonly app: Application; @@ -27,56 +28,119 @@ export class Core { this.dex = new Dex(this.ws) } - javachecker(){ - exec("java -version",(err,stdout,stderr)=>{ - if(err){ - this.wsx.send(JSON.stringify({ - type:"error", - message:"jini" - })) + private async javachecker() { + try { + const result: JavaCheckResult = await checkJava(); + + if (result.exists && result.version) { + logger.info(`Java detected: ${result.version.fullVersion} (${result.version.vendor})`); + + if (this.wsx) { + this.wsx.send(JSON.stringify({ + type: "info", + message: `Java detected: ${result.version.fullVersion} (${result.version.vendor})`, + data: result.version + })); + } + } else { + logger.error("Java check failed", result.error); + + if (this.wsx) { + this.wsx.send(JSON.stringify({ + type: "error", + message: result.error || "Java not found or version check failed", + data: result + })); + } } - }) + } catch (error) { + logger.error("Java check exception", error as Error); + + if (this.wsx) { + this.wsx.send(JSON.stringify({ + type: "error", + message: "Java check encountered an exception" + })); + } + } } - express() { + private express() { this.app.use(cors()); this.app.use(express.json()); - this.app.get('/',(req,res)=>{ + + // 健康检查路由 + this.app.get('/', (req, res) => { res.json({ - status:200, - by:"DeEarthX.Core", - qqg:"559349662", - bilibili:"https://space.bilibili.com/1728953419" - }) - }) + status: 200, + by: "DeEarthX.Core", + qqg: "559349662", + bilibili: "https://space.bilibili.com/1728953419" + }); + }); + + // 启动任务路由 this.app.post("/start", this.upload.single("file"), (req, res) => { - if (!req.file) { - return; + try { + if (!req.file) { + return res.status(400).json({ status: 400, message: "No file uploaded" }); + } + if (!req.query.mode) { + return res.status(400).json({ status: 400, message: "Mode parameter missing" }); + } + + const isServerMode = req.query.mode === "server"; + logger.info("Starting task", { isServerMode }); + + // 非阻塞执行主要任务 + this.dex.Main(req.file.buffer, isServerMode).catch(err => { + logger.error("Task execution failed", err); + }); + + res.json({ status: 200, message: "Task is pending" }); + } catch (err) { + const error = err as Error; + logger.error("/start route error", error); + res.status(500).json({ status: 500, message: "Internal server error" }); } - if (!req.query.mode){ - return; - } - this.dex.Main(req.file.buffer,req.query.mode == "server") //Dex - //this.dex.Main(req.file.buffer) - res.json({ - status:200, - message:"task is peding" - }) - }) + }); + + // 获取配置路由 this.app.get('/config/get', (req, res) => { - res.json(this.config) - }) + try { + res.json(this.config); + } catch (err) { + const error = err as Error; + logger.error("/config/get route error", error); + res.status(500).json({ status: 500, message: "Failed to get config" }); + } + }); + // 更新配置路由 this.app.post('/config/post', (req, res) => { - Config.write_config(req.body) - res.json({ status: 200 }) - }) + try { + Config.writeConfig(req.body); + this.config = req.body; // 更新内存中的配置 + logger.info("Config updated"); + res.json({ status: 200 }); + } catch (err) { + const error = err as Error; + logger.error("/config/post route error", error); + res.status(500).json({ status: 500, message: "Failed to update config" }); + } + }); } - start() { - this.express() - this.server.listen(37019, () => { - console.log("Server is running on http://localhost:37019") - }) + public async start() { + this.express(); + this.server.listen(37019, async () => { + logger.info("Server is running on http://localhost:37019"); + await this.javachecker(); // 启动时检查Java + }); + + // 处理服务器错误 + this.server.on('error', (err) => { + logger.error("Server error", err); + }); } } \ No newline at end of file diff --git a/backend/src/modloader/index.ts b/backend/src/modloader/index.ts index 1aeed3d..fb01e10 100644 --- a/backend/src/modloader/index.ts +++ b/backend/src/modloader/index.ts @@ -3,39 +3,55 @@ import { Forge } from "./forge.js"; import { Minecraft } from "./minecraft.js"; import { NeoForge } from "./neoforge.js"; +/** + * 模组加载器接口 + */ interface XModloader { setup(): Promise; installer(): Promise; } -export function modloader(ml:string,mcv:string,mlv:string,path:string){ - let modloader:XModloader - switch (ml) { +/** + * 创建模组加载器实例 + * @param ml 加载器类型 + * @param mcv Minecraft版本 + * @param mlv 加载器版本 + * @param path 安装路径 + * @returns 加载器实例 + */ +export function modloader(ml: string, mcv: string, mlv: string, path: string): XModloader { + switch (ml) { case "fabric": - modloader = new Fabric(mcv,mlv,path) - break; case "fabric-loader": - modloader = new Fabric(mcv,mlv,path) - break; + return new Fabric(mcv, mlv, path); case "forge": - modloader = new Forge(mcv,mlv,path) - break; + return new Forge(mcv, mlv, path); case "neoforge": - modloader = new NeoForge(mcv,mlv,path) - break; + return new NeoForge(mcv, mlv, path); default: - modloader = new Minecraft(ml,mcv,mlv,path) - break; - } - return modloader + return new Minecraft(ml, mcv, mlv, path); + } } -export async function mlsetup(ml:string,mcv:string,mlv:string,path:string){ - const minecraft = new Minecraft(ml,mcv,mlv,path); - //console.log(ml) - await modloader(ml,mcv,mlv,path).setup() - await minecraft.setup() +/** + * 设置模组加载器 + * @param ml 加载器类型 + * @param mcv Minecraft版本 + * @param mlv 加载器版本 + * @param path 安装路径 + */ +export async function mlsetup(ml: string, mcv: string, mlv: string, path: string): Promise { + const minecraft = new Minecraft(ml, mcv, mlv, path); + await modloader(ml, mcv, mlv, path).setup(); + await minecraft.setup(); } -export async function dinstall(ml:string,mcv:string,mlv:string,path:string){ - await modloader(ml,mcv,mlv,path).installer(); +/** + * 安装模组加载器 + * @param ml 加载器类型 + * @param mcv Minecraft版本 + * @param mlv 加载器版本 + * @param path 安装路径 + */ +export async function dinstall(ml: string, mcv: string, mlv: string, path: string): Promise { + await modloader(ml, mcv, mlv, path).installer(); } \ No newline at end of file diff --git a/backend/src/utils/config.ts b/backend/src/utils/config.ts index c2b68ee..9d1a9db 100644 --- a/backend/src/utils/config.ts +++ b/backend/src/utils/config.ts @@ -1,4 +1,8 @@ import fs from "node:fs"; + +/** + * 应用配置接口 + */ export interface IConfig { mirror: { bmclapi: boolean; @@ -9,32 +13,65 @@ export interface IConfig { dexpub: boolean; mixins: boolean; }; - oaf: boolean + oaf: boolean; } +/** + * 默认配置 + */ +const DEFAULT_CONFIG: IConfig = { + mirror: { + bmclapi: true, + mcimirror: true, + }, + filter: { + hashes: true, + dexpub: false, + mixins: true, + }, + oaf: true +}; + +/** + * 配置文件路径 + */ +const CONFIG_PATH = "./config.json"; + +/** + * 配置管理器 + */ export class Config { - private readonly default_config: IConfig = { - mirror: { - bmclapi: true, - mcimirror: true, - }, - filter: { - hashes: true, - dexpub: false, - mixins: true, - }, - oaf:true - }; - config(): IConfig { - if (!fs.existsSync("./config.json")) { - fs.writeFileSync("./config.json", JSON.stringify(this.default_config)); - return this.default_config; + /** + * 获取配置 + * @returns 配置对象 + */ + public static getConfig(): IConfig { + if (!fs.existsSync(CONFIG_PATH)) { + fs.writeFileSync(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2)); + return DEFAULT_CONFIG; + } + + try { + const content = fs.readFileSync(CONFIG_PATH, "utf-8"); + return JSON.parse(content); + } catch (err) { + console.error("Failed to read config file, using defaults", err); + return DEFAULT_CONFIG; } - return JSON.parse(fs.readFileSync("./config.json", "utf-8")); } - static write_config(config: IConfig) { - fs.writeFileSync("./config.json", JSON.stringify(config)); + + /** + * 写入配置 + * @param config 配置对象 + */ + public static writeConfig(config: IConfig): void { + try { + fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2)); + } catch (err) { + console.error("Failed to write config file", err); + } } } -export default new Config().config(); +// 默认导出配置实例 +export default Config.getConfig(); diff --git a/backend/src/utils/logger.ts b/backend/src/utils/logger.ts index 9c19540..a8f3a30 100644 --- a/backend/src/utils/logger.ts +++ b/backend/src/utils/logger.ts @@ -1,17 +1,42 @@ const env = process.env.DEBUG; +const isDebug = env === "true"; -export function debug(msg: any){ - if (env === "true"){ - if(msg instanceof Error){ - console.log(`[ERROR] [${new Date().toLocaleString()}] `); - console.log(msg); - } - if (typeof msg === "string"){ - console.log(`[DEBUG] [${new Date().toLocaleString()}] ` + msg); - } - if (typeof msg === "object"){ - console.log(`[OBJ] [${new Date().toLocaleString()}] `); - console.log(msg); - } - } +// 日志级别枚举 +type LogLevel = "debug" | "info" | "warn" | "error"; + +/** + * 日志记录器 + * @param level 日志级别 + * @param msg 日志内容 + * @param data 附加数据(可选) + */ +function log(level: LogLevel, msg: string | Error, data?: any) { + const timestamp = new Date().toLocaleString(); + const prefix = `[${level.toUpperCase()}] [${timestamp}]`; + + // 确保只在调试模式下输出debug日志 + if (level === "debug" && !isDebug) return; + + if (msg instanceof Error) { + console.error(`${prefix} ${msg.message}`); + console.error(msg.stack); + if (data) console.error(`${prefix} Data:`, data); + } else { + const logFunc = level === "error" ? console.error : + level === "warn" ? console.warn : + console.log; + + logFunc(`${prefix} ${msg}`); + if (data) logFunc(`${prefix} Data:`, data); + } } + +export const logger = { + debug: (msg: string | Error, data?: any) => log("debug", msg, data), + info: (msg: string | Error, data?: any) => log("info", msg, data), + warn: (msg: string | Error, data?: any) => log("warn", msg, data), + error: (msg: string | Error, data?: any) => log("error", msg, data) +}; + +// 保持向后兼容 +export const debug = logger.debug; diff --git a/backend/src/utils/utils.ts b/backend/src/utils/utils.ts index 85206d4..22633ef 100644 --- a/backend/src/utils/utils.ts +++ b/backend/src/utils/utils.ts @@ -6,7 +6,29 @@ import fs from "node:fs"; import fse from "fs-extra"; import { WebSocket } from "ws"; import { ExecOptions, exec} from "node:child_process"; + +/** + * Java版本信息接口 + */ +export interface JavaVersion { + major: number; + minor: number; + patch: number; + fullVersion: string; + vendor: string; + runtimeVersion?: string; +} + +/** + * Java检测结果接口 + */ +export interface JavaCheckResult { + exists: boolean; + version?: JavaVersion; + error?: string; +} import { MessageWS } from "./ws.js"; +import { logger } from "./logger.js"; export class Utils { public modrinth_url: string; @@ -50,90 +72,157 @@ export function version_compare(v1: string, v2: string) { return 0; } +/** + * 检测Java是否安装并获取版本信息 + * @returns Java检测结果 + */ +export async function checkJava(): Promise { + try { + const output = await new Promise((resolve, reject) => { + exec("java -version", (err, stdout, stderr) => { + if (err) { + logger.error("Java check failed", err); + reject(new Error("Java not found")); + return; + } + // Java版本信息输出在stderr中 + resolve(stderr); + }); + }); + + logger.debug(`Java version output: ${output}`); + + // 解析Java版本信息 + const versionRegex = /version "(\d+)(\.(\d+))?(\.(\d+))?/; + const vendorRegex = /(Java\(TM\)|OpenJDK).*Runtime Environment.*by (.*)/; + + const versionMatch = output.match(versionRegex); + const vendorMatch = output.match(vendorRegex); + + if (!versionMatch) { + return { + exists: true, + error: "Failed to parse Java version" + }; + } + + const major = parseInt(versionMatch[1], 10); + const minor = versionMatch[3] ? parseInt(versionMatch[3], 10) : 0; + const patch = versionMatch[5] ? parseInt(versionMatch[5], 10) : 0; + + const versionInfo: JavaVersion = { + major, + minor, + patch, + fullVersion: versionMatch[0].replace("version ", ""), + vendor: vendorMatch ? vendorMatch[2] : "Unknown" + }; + + logger.info(`Java detected: ${JSON.stringify(versionInfo)}`); + + return { + exists: true, + version: versionInfo + }; + } catch (error) { + logger.error("Java check error", error as Error); + return { + exists: false, + error: (error as Error).message + }; + } +} + export function execPromise(cmd:string,options?:ExecOptions){ + logger.debug(`Executing command: ${cmd}`); return new Promise((resolve,reject)=>{ exec(cmd,options,(err,stdout,stderr)=>{ if(err){ + logger.error(`Command execution failed: ${cmd}`, err); + logger.debug(`Stderr: ${stderr}`); reject(err) return; } + if (stdout) logger.debug(`Command stdout: ${stdout}`); + if (stderr) logger.debug(`Command stderr: ${stderr}`); }).on('exit',(code)=>{ + logger.debug(`Command completed with exit code: ${code}`); resolve(code) }) }) } export async function fastdownload(data: [string, string]|string[][]) { - let _data = undefined; - if(Array.isArray(data[0])){ - _data = data - }else{ - _data = [data] - } + // 确保downloadList始终是[string, string][]类型 + const downloadList: [string, string][] = Array.isArray(data[0]) + ? (data as string[][]).map(item => item as [string, string]) + : [data as [string, string]]; + logger.info(`Starting fast download of ${downloadList.length} files`); + return await pMap( - _data, - async (e:any) => { + downloadList, + async (item: [string, string]) => { + const [url, filePath] = item; try { await pRetry( async () => { - if (!fs.existsSync(e[1])) { - await got - .get(e[0], { - responseType: "buffer", - headers: { - "user-agent": "DeEarthX", - }, - }) - .then((res) => { - fse.outputFileSync(e[1], res.rawBody); - }); + if (!fs.existsSync(filePath)) { + logger.debug(`Downloading ${url} to ${filePath}`); + const res = await got.get(url, { + responseType: "buffer", + headers: { "user-agent": "DeEarthX" }, + }); + fse.outputFileSync(filePath, res.rawBody); + logger.debug(`Downloaded ${url} successfully`); + } else { + logger.debug(`File already exists, skipping: ${filePath}`); } }, - { retries: 3 } + { retries: 3, onFailedAttempt: (error) => { + logger.warn(`Download attempt failed for ${url}, retrying (${error.attemptNumber}/3)`); + }} ); - } catch (e) { - //LOGGER.error({ err: e }); + } catch (error) { + logger.error(`Failed to download ${url} after 3 attempts`, error); + throw error; } }, { concurrency: 16 } ); } -export async function Wfastdownload(data: string[][],ws:MessageWS) { - let index = 1; +export async function Wfastdownload(data: string[][], ws: MessageWS) { + logger.info(`Starting web download of ${data.length} files`); + return await pMap( data, - async (e:any) => { + async (item: string[], idx: number) => { + const [url, filePath] = item; try { await pRetry( async () => { - if (!fs.existsSync(e[1])) { - await got - .get(e[0], { - responseType: "buffer", - headers: { - "user-agent": "DeEarthX", - }, - }) - .then((res) => { - fse.outputFileSync(e[1], res.rawBody); - }); + if (!fs.existsSync(filePath)) { + logger.debug(`Downloading ${url} to ${filePath}`); + const res = await got.get(url, { + responseType: "buffer", + headers: { "user-agent": "DeEarthX" }, + }); + fse.outputFileSync(filePath, res.rawBody); + logger.debug(`Downloaded ${url} successfully`); + } else { + logger.debug(`File already exists, skipping: ${filePath}`); } - ws.download(data.length,index,e[1]) - // ws.send(JSON.stringify({ - // status:"downloading", - // result:{ - // total:data.length, - // index:index, - // name:e[1] - // } - // })) - index++ + + // 更新下载进度 + ws.download(data.length, idx + 1, filePath); }, - { retries: 3 } + { retries: 3, onFailedAttempt: (error) => { + logger.warn(`Download attempt failed for ${url}, retrying (${error.attemptNumber}/3)`); + }} ); - } catch (e) { - //LOGGER.error({ err: e }); + } catch (error) { + logger.error(`Failed to download ${url} after 3 attempts`, error); + throw error; } }, { concurrency: 16 } diff --git a/backend/src/utils/ws.ts b/backend/src/utils/ws.ts index a58c1fb..1bb981f 100644 --- a/backend/src/utils/ws.ts +++ b/backend/src/utils/ws.ts @@ -1,56 +1,83 @@ import websocket, { WebSocketServer } from "ws"; +import { logger } from "./logger.js"; + export class MessageWS { - ws!: websocket; + private ws!: websocket; + constructor(ws: websocket) { this.ws = ws; + + // 监听WebSocket错误 + this.ws.on('error', (err) => { + logger.error("WebSocket error", err); + }); + + // 监听连接关闭 + this.ws.on('close', (code, reason) => { + logger.info("WebSocket connection closed", { code, reason: reason.toString() }); + }); } - finish(first: number, latest: number) { - this.ws.send( - JSON.stringify({ - status: "finish", - result: latest - first, - }) - ); + /** + * 发送完成消息 + * @param startTime 开始时间 + * @param endTime 结束时间 + */ + finish(startTime: number, endTime: number) { + this.send("finish", endTime - startTime); } + /** + * 发送解压进度消息 + * @param entryName 文件名 + * @param total 总文件数 + * @param current 当前文件索引 + */ unzip(entryName: string, total: number, current: number) { - this.ws.send( - JSON.stringify({ - status: "unzip", - result: { name: entryName, total, current }, - }) - ); + this.send("unzip", { name: entryName, total, current }); } + /** + * 发送下载进度消息 + * @param total 总文件数 + * @param index 当前文件索引 + * @param name 文件名 + */ download(total: number, index: number, name: string) { - this.ws.send( - JSON.stringify({ - status: "downloading", - result: { - total, - index, - name, - }, - }) - ); + this.send("downloading", { total, index, name }); } + /** + * 发送状态变更消息 + */ statusChange() { - this.ws.send( - JSON.stringify({ - status: "changed", - result: undefined, - }) - ); + this.send("changed", undefined); } + /** + * 发送错误消息 + * @param error 错误对象 + */ handleError(error: Error) { - this.ws.send( - JSON.stringify({ - status: "error", - result: error.message, - }) - ); + this.send("error", error.message); + } + + /** + * 通用消息发送方法 + * @param status 消息状态 + * @param result 消息内容 + */ + private send(status: string, result: any) { + try { + if (this.ws.readyState === websocket.OPEN) { + const message = JSON.stringify({ status, result }); + logger.debug("Sending WebSocket message", { status, result }); + this.ws.send(message); + } else { + logger.warn(`WebSocket not open, cannot send message: ${status}`); + } + } catch (err) { + logger.error("Failed to send WebSocket message", err); + } } }