diff --git a/.vscode/settings.json b/.vscode/settings.json index 2571d5c..457ab0c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "[typescript]": { - "editor.defaultFormatter": "vscode.typescript-language-features" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" diff --git a/config/index.ts b/config/index.ts index f574a69..def92fb 100644 --- a/config/index.ts +++ b/config/index.ts @@ -12,6 +12,7 @@ export interface IDefaultConfig { "common.theme": ThemeType debug: LogLevel "desktop:wallpaper": string + "update.hoturl": string "update.repo"?: string "update.owner"?: string "update.allowDowngrade": boolean @@ -40,6 +41,7 @@ export default { "editor.bg": "", "editor.logoType": "logo", "editor.fontFamily": "Cascadia Mono, Consolas, 'Courier New', monospace", + "update.hoturl": "https://alist.xieyaxin.top/d/%E8%B5%84%E6%BA%90/%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6.zip?sign=eqy35CR-J1SOQZz0iUN2P3B0BiyZPdYH0362nLXbUhE=:1749085071", "update.repo": "wood-desktop", "update.owner": "npmrun", "update.allowDowngrade": false, diff --git a/package.json b/package.json index 0b1844d..000b82e 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@vue/eslint-config-prettier": "^9.0.0", "@vue/eslint-config-typescript": "^13.0.0", "@vueuse/core": "^12.7.0", + "base": "workspace:*", "debug": "^4.4.0", "electron": "^31.7.7", "electron-builder": "^24.13.3", @@ -57,6 +58,7 @@ "eslint": "^8.57.1", "eslint-plugin-vue": "^9.32.0", "extract-zip": "^2.0.1", + "helper": "workspace:*", "locales": "workspace:*", "lodash-es": "^4.17.21", "logger": "workspace:^", diff --git a/packages/base/index.ts b/packages/base/index.ts new file mode 100644 index 0000000..12cab57 --- /dev/null +++ b/packages/base/index.ts @@ -0,0 +1,27 @@ +// 抽象基类,使用泛型来正确推导子类类型 +abstract class BaseSingleton { + private static _instance: any + + public constructor() { + if (this.constructor === BaseSingleton) { + throw new Error("禁止直接实例化 BaseOne 抽象类") + } + + if ((this.constructor as any)._instance) { + throw new Error("构造函数私有化失败,禁止重复 new") + } + + // this.constructor 是子类,所以这里设为 instance + ;(this.constructor as any)._instance = this + } + + public static getInstance(this: new () => T): T { + const clazz = this as any as typeof BaseSingleton + if (!clazz._instance) { + clazz._instance = new this() + } + return clazz._instance as T + } +} + +export { BaseSingleton } diff --git a/packages/base/package.json b/packages/base/package.json new file mode 100644 index 0000000..6c535af --- /dev/null +++ b/packages/base/package.json @@ -0,0 +1,13 @@ +{ + "name": "base", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.4.1" +} diff --git a/packages/helper/package.json b/packages/helper/package.json new file mode 100644 index 0000000..b9f3e4e --- /dev/null +++ b/packages/helper/package.json @@ -0,0 +1,13 @@ +{ + "name": "helper", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.4.1" +} diff --git a/packages/helper/updater/common.ts b/packages/helper/updater/common.ts new file mode 100644 index 0000000..14f45df --- /dev/null +++ b/packages/helper/updater/common.ts @@ -0,0 +1,7 @@ +export const enum EventEnum { + UPDATE_PROGRESS = "update-progress", +} + +export type EventMaps = { + [EventEnum.UPDATE_PROGRESS]: () => void +} diff --git a/packages/helper/updater/main/handler.ts b/packages/helper/updater/main/handler.ts new file mode 100644 index 0000000..b01b5ed --- /dev/null +++ b/packages/helper/updater/main/handler.ts @@ -0,0 +1,8 @@ +import { broadcast } from "main/utils" +import { EventEnum } from "../common" + +export { EventEnum } + +export function emit(key: EventEnum, ...args: any[]) { + broadcast(key, ...args) +} diff --git a/packages/helper/updater/main/hot/download.ts b/packages/helper/updater/main/hot/download.ts new file mode 100644 index 0000000..fc87964 --- /dev/null +++ b/packages/helper/updater/main/hot/download.ts @@ -0,0 +1,74 @@ +type DownloadPercent = { + url: string + option?: object + onprocess?: (now: number, all: number) => void + onsuccess?: (data: any) => void + onerror?: (res: Response) => void +} +const RequestPercent = async ({ + url = "", + option = { + headers: { + responseType: "arraybuffer", + }, + }, + onsuccess, + onerror, + onprocess, +}: DownloadPercent) => { + const response = (await fetch(url, option)) as any + if (!response.ok) { + onerror?.(response) + throw new Error(`下载失败`) + } + const reader = response?.body.getReader() + + // 文件总长度 + const contentLength = +response.headers.get("content-length") + + let receivedLength = 0 + const chunks: any[] = [] + // eslint-disable-next-line no-constant-condition + while (true) { + const { done, value } = await reader.read() + + if (done) { + break + } + + chunks.push(value) + receivedLength += value.length + onprocess?.(receivedLength, contentLength) + } + // 这里的chunksAll 已经是ArrayBuffer的数据类型了,可以直接返回,也可以转为blob处理 + const chunksAll = new Uint8Array(receivedLength) + let position = 0 + for (const chunk of chunks) { + chunksAll.set(chunk, position) + position += chunk.length + } + + onsuccess?.(chunksAll) + + return chunksAll +} + +export { RequestPercent } +export default RequestPercent + +// RequestPercent({ +// url: "http://117.21.250.136:9812/ZxqyGateway/biz/file/downApk/%E6%98%93%E4%BC%81%E6%95%B0%E8%BD%AC%E5%B9%B3%E5%8F%B0app-1.2.7.apk", +// option: { +// headers: { +// responseType: "arraybuffer", +// }, +// }, +// onerror: () => {}, +// onsuccess: data => { +// fs.writeFileSync("./aaa.apk", Buffer.from(data)) +// console.log("success", data) +// }, +// onprocess: (receivedLength, contentLength) => { +// console.log(receivedLength, contentLength) +// }, +// }) diff --git a/src/main/modules/updater/hot/index.ts b/packages/helper/updater/main/hot/index.ts similarity index 85% rename from src/main/modules/updater/hot/index.ts rename to packages/helper/updater/main/hot/index.ts index 7e77cf6..fcf2e89 100644 --- a/src/main/modules/updater/hot/index.ts +++ b/packages/helper/updater/main/hot/index.ts @@ -3,11 +3,13 @@ import fs from "node:fs" import path from "node:path" import os from "node:os" import { app } from "electron" +import download from "./download" import extract from "extract-zip" -import { emitHotUpdateReady } from "common/event/Update/main" -import _debug from "debug" -const debug = _debug("app:hot-updater") +import _logger from "logger/main" +import { emit, EventEnum } from "../handler" + +const logger = _logger.createNamespace("hot-updater") function getUpdateScriptTemplate() { return process.platform === "win32" @@ -71,7 +73,7 @@ app.once("will-quit", event => { }) // 下载热更新包 -export async function fetchHotUpdatePackage(updatePackageUrl: string = "https://example.com/updates/latest.zip") { +export async function fetchHotUpdatePackage(updatePackageUrl: string) { if (isReadyUpdate) return // 清除临时目录 @@ -86,24 +88,22 @@ export async function fetchHotUpdatePackage(updatePackageUrl: string = "https:// try { // 使用 fetch 下载更新包 - const response = await fetch(updatePackageUrl) - if (!response.ok) { - throw new Error(`下载失败: ${response.status} ${response.statusText}`) - } - - // 将下载内容写入文件 - const arrayBuffer = await response.arrayBuffer() + const arrayBuffer = await download({ + url: updatePackageUrl, + onprocess(now, all) { + logger.debug(`下载进度: ${((now / all) * 100).toFixed(2)}%`) + emit(EventEnum.UPDATE_PROGRESS, { percent: (now / all) * 100, now, all }) + }, + }) fs.writeFileSync(downloadPath, Buffer.from(arrayBuffer)) - // 解压更新包 await extract(downloadPath, { dir: updateTempDirPath }) // 删除下载的zip文件 fs.unlinkSync(downloadPath) isReadyUpdate = true - emitHotUpdateReady() } catch (error) { - debug("热更新包下载失败:", error) + logger.debug("热更新包下载失败:", error) throw error } } diff --git a/src/main/modules/updater/index.ts b/packages/helper/updater/main/index.ts similarity index 78% rename from src/main/modules/updater/index.ts rename to packages/helper/updater/main/index.ts index 81d9924..c3f3fcf 100644 --- a/src/main/modules/updater/index.ts +++ b/packages/helper/updater/main/index.ts @@ -1,23 +1,22 @@ import pkg from "electron-updater" import { app, dialog } from "electron" -import { injectable } from "inversify" -import BaseClass from "main/base/base" -// import { Setting } from "../setting" -import _debug from "debug" +import Setting from "setting/main" import EventEmitter from "events" +import { BaseSingleton } from "base" import { fetchHotUpdatePackage, flagNeedUpdate } from "./hot" import Locales from "locales/main" +import _logger from "logger/main" -const debug = _debug("app:updater") +const logger = _logger.createNamespace("updater") const { autoUpdater } = pkg -@injectable() -export class Updater extends BaseClass { +class _Updater extends BaseSingleton { public events = new EventEmitter() private timer: ReturnType | null = null // autoReplace = false async triggerHotUpdate(autoReplace = false) { - await fetchHotUpdatePackage() + const url = Setting.values("update.hoturl") + await fetchHotUpdatePackage(url) flagNeedUpdate() if (!autoReplace) { dialog.showMessageBox({ @@ -31,42 +30,41 @@ export class Updater extends BaseClass { constructor() { super() - // 配置自动更新 autoUpdater.autoDownload = false autoUpdater.autoInstallOnAppQuit = true // 检查更新错误 autoUpdater.on("error", error => { - debug("Update error:", error) + logger.debug("Update error:", error) }) // 检查更新 autoUpdater.on("checking-for-update", () => { - debug("Checking for updates...") + logger.debug("Checking for updates...") }) // 有可用更新 autoUpdater.on("update-available", info => { - debug("Update available:", info) + logger.debug("Update available:", info) this.promptUserToUpdate() }) // 没有可用更新 autoUpdater.on("update-not-available", info => { - debug("Update not available:", info) + logger.debug("Update not available:", info) }) // 更新下载进度 autoUpdater.on("download-progress", progressObj => { - debug( + logger.debug( `Download speed: ${progressObj.bytesPerSecond} - Downloaded ${progressObj.percent}% (${progressObj.transferred}/${progressObj.total})`, ) }) // 更新下载完成 autoUpdater.on("update-downloaded", info => { - debug("Update downloaded:", info) + logger.debug("Update downloaded:", info) this.promptUserToInstall() }) } @@ -95,9 +93,12 @@ export class Updater extends BaseClass { if (app.isPackaged) { try { await autoUpdater.checkForUpdates() + logger.debug("Updater初始化检查成功.") } catch (error) { - debug("Failed to check for updates:", error) + logger.debug("Failed to check for updates:", error) } + } else { + logger.debug("正在开发模式,跳过更新检查.") } } @@ -130,4 +131,7 @@ export class Updater extends BaseClass { } } +const Updater = _Updater.getInstance() + +export { Updater } export default Updater diff --git a/packages/helper/updater/renderer.ts b/packages/helper/updater/renderer.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/logger/main.ts b/packages/logger/main.ts index 5c4da62..a271fce 100644 --- a/packages/logger/main.ts +++ b/packages/logger/main.ts @@ -5,7 +5,6 @@ import setting from "setting/main" import * as rfs from "rotating-file-stream" import { LogLevel, LogLevelColor, LogLevelName } from "./common" - // 重置颜色的ANSI代码 const RESET_COLOR = "\x1b[0m" diff --git a/packages/setting/main.ts b/packages/setting/main.ts index 6bd3425..ea35d8d 100644 --- a/packages/setting/main.ts +++ b/packages/setting/main.ts @@ -4,9 +4,9 @@ import path from "path" import { cloneDeep } from "lodash" import Config from "config" import type { IDefaultConfig } from "config" -import _debug from "debug" +import _logger from "logger/main" -const debug = _debug("app:setting") +const logger = _logger.createNamespace("setting") type IConfig = IDefaultConfig @@ -59,7 +59,7 @@ function isEmptyDir(fPath: string) { class SettingClass { constructor() { - debug(`Setting inited`) + logger.debug(`Setting inited`) this.init() } @@ -122,7 +122,7 @@ class SettingClass { } } init() { - debug(`位置:${this.#pathFile}`) + logger.debug(`位置:${this.#pathFile}`) if (fs.pathExistsSync(this.#pathFile)) { const confingPath = fs.readFileSync(this.#pathFile, { encoding: "utf8" }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfc77cd..e0607d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,9 @@ importers: '@vueuse/core': specifier: ^12.7.0 version: 12.7.0(typescript@5.7.3) + base: + specifier: workspace:* + version: link:packages/base debug: specifier: ^4.4.0 version: 4.4.0 @@ -99,6 +102,9 @@ importers: extract-zip: specifier: ^2.0.1 version: 2.0.1 + helper: + specifier: workspace:* + version: link:packages/helper locales: specifier: workspace:* version: link:packages/locales @@ -178,6 +184,10 @@ importers: specifier: ^2.1.10 version: 2.1.10(typescript@5.7.3) + packages/base: {} + + packages/helper: {} + packages/locales: {} packages/logger: {} diff --git a/src/common/_ioc.main.ts b/src/common/_ioc.main.ts index 7ba0ba9..067c1be 100644 --- a/src/common/_ioc.main.ts +++ b/src/common/_ioc.main.ts @@ -12,7 +12,12 @@ const modules = new ContainerModule(bind => { const CommandClass = (module as { default: any }).default if (CommandClass) { const className = CommandClass.name.replace("Command", "") - bind(className + "Command").to(CommandClass).inSingletonScope() + if (CommandClass["init"]) { + CommandClass["init"]() + } + bind(className + "Command") + .to(CommandClass) + .inSingletonScope() } }) }) diff --git a/src/common/event/PlatForm/index.ts b/src/common/event/PlatForm/index.ts index d03501a..423d0b9 100644 --- a/src/common/event/PlatForm/index.ts +++ b/src/common/event/PlatForm/index.ts @@ -1,8 +1,8 @@ -import { _Base } from "common/lib/_Base" import { ApiFactory } from "common/lib/abstract" +import { BaseSingleton } from "base" import { LogLevel } from "packages/logger/common" -class PlatForm extends _Base { +class PlatForm extends BaseSingleton { constructor() { super() } diff --git a/src/common/event/Snippet/hook.ts b/src/common/event/Snippet/hook.ts index e4a24bb..3a89407 100644 --- a/src/common/event/Snippet/hook.ts +++ b/src/common/event/Snippet/hook.ts @@ -1,5 +1,5 @@ import { Snippet } from "." export function useSnippet() { - return Snippet.getInstance() + return Snippet.getInstance() } diff --git a/src/common/event/Snippet/index.ts b/src/common/event/Snippet/index.ts index 18f789c..5cf9945 100644 --- a/src/common/event/Snippet/index.ts +++ b/src/common/event/Snippet/index.ts @@ -1,7 +1,7 @@ -import { _Base } from "common/lib/_Base" +import { BaseSingleton } from "base" import { ApiFactory } from "common/lib/abstract" -class Snippet extends _Base { +class Snippet extends BaseSingleton { constructor() { super() } diff --git a/src/common/event/Tabs/index.ts b/src/common/event/Tabs/index.ts index f36ca59..de0025b 100644 --- a/src/common/event/Tabs/index.ts +++ b/src/common/event/Tabs/index.ts @@ -1,6 +1,6 @@ -import { _Base } from "../../lib/_Base" +import { BaseSingleton } from "base" -export class Tabs extends _Base { +export class Tabs extends BaseSingleton { constructor() { super() } diff --git a/src/common/event/Updater/index.ts b/src/common/event/Updater/index.ts new file mode 100644 index 0000000..c31799e --- /dev/null +++ b/src/common/event/Updater/index.ts @@ -0,0 +1,15 @@ +import { EventEnum } from "helper/updater/common" + +const curProgress = ref(0) + +api.on(EventEnum.UPDATE_PROGRESS, ({ percent, now, all }) => { + curProgress.value = percent +}) + +function useUpdate() { + return { + curProgress, + } +} + +export { useUpdate } diff --git a/src/common/event/Updater/main/command.ts b/src/common/event/Updater/main/command.ts new file mode 100644 index 0000000..8ba7617 --- /dev/null +++ b/src/common/event/Updater/main/command.ts @@ -0,0 +1,15 @@ +import Updater from "helper/updater/main" +import _logger from "logger/main" + +const logger = _logger.createNamespace("UpdaterCommand") + +export default class UpdaterCommand { + static init() { + // 命令初始化 + logger.debug("UpdaterCommand init") + } + + async triggerHotUpdate() { + Updater.triggerHotUpdate() + } +} diff --git a/src/common/event/common.ts b/src/common/event/common.ts deleted file mode 100644 index a90403b..0000000 --- a/src/common/event/common.ts +++ /dev/null @@ -1,5 +0,0 @@ -const keys = ["hot-update-ready"] as const - -type AllKeys = (typeof keys)[number] - -export type { AllKeys } diff --git a/src/common/event/update/index.ts b/src/common/event/update/index.ts deleted file mode 100644 index 55676c4..0000000 --- a/src/common/event/update/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -// import type { AllKeys } from "../common" - -const curProgress = ref(0) -// api.on("progress", () => { -// curProgress.value = 10 -// }) - -function useUpdate() { - return { - curProgress, - } -} - -export { useUpdate } diff --git a/src/common/event/update/main/command.ts b/src/common/event/update/main/command.ts deleted file mode 100644 index 069ece6..0000000 --- a/src/common/event/update/main/command.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { inject } from "inversify" -import Updater from "main/modules/updater" - -export default class PlatFormCommand { - constructor(@inject(Updater) private _Updater: Updater) {} - - async triggerHotUpdate() { - await this._Updater.triggerHotUpdate() - } -} diff --git a/src/common/event/update/main/index.ts b/src/common/event/update/main/index.ts deleted file mode 100644 index 9aa935d..0000000 --- a/src/common/event/update/main/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { broadcast } from "main/utils" -import { AllKeys } from "common/event/common" - -function emitHotUpdateReady(...argu) { - broadcast("hot-update-ready", ...argu) -} - -export { emitHotUpdateReady } diff --git a/src/common/lib/_Base.ts b/src/common/lib/_Base.ts deleted file mode 100644 index 1638ca1..0000000 --- a/src/common/lib/_Base.ts +++ /dev/null @@ -1,12 +0,0 @@ -export abstract class _Base { - static instance - - static getInstance(): T { - if (!this.instance) { - // 如果实例不存在,则创建一个新的实例 - // @ts-ignore ... - this.instance = new this() - } - return this.instance - } -} diff --git a/src/common/readme.md b/src/common/readme.md new file mode 100644 index 0000000..51c0c10 --- /dev/null +++ b/src/common/readme.md @@ -0,0 +1,7 @@ +## event + +通用事件处理模块 + +- main/**/* 处理主进程的模块 +- main/command.ts 会通过ioc收集,进入依赖管理中 +- 其他 处理渲染进程的模块 \ No newline at end of file diff --git a/src/main/App.ts b/src/main/App.ts index 030f161..8976300 100644 --- a/src/main/App.ts +++ b/src/main/App.ts @@ -10,9 +10,7 @@ import BaseClass from "./base/base" import IOC from "./_ioc" import DB from "./modules/db" import Zephyr from "./modules/zephyr" -import Updater from "./modules/updater" import { crashHandler } from "logger/crash-handler" -import { eventbus } from "./event" protocol.registerSchemesAsPrivileged([ // { @@ -44,12 +42,6 @@ protocol.registerSchemesAsPrivileged([ @injectable() class App extends BaseClass { - - static events = { - AppInit: "App.init", - AppReady: "App.ready", - } - destroy() { this._IOC.destroy() // 这里是应用正常退出, 可以检测应用是不是非正常退出,比如应用启动时记录一个启动时间并删除上一次结束时间和开始时间,结束时记录一个结束时间, @@ -63,7 +55,6 @@ class App extends BaseClass { @inject(DB) private _DB: DB, @inject(WindowManager) private _WindowManager: WindowManager, @inject(Zephyr) private _Zephyr: Zephyr, - @inject(Updater) private _Updater: Updater, ) { super() } @@ -73,10 +64,8 @@ class App extends BaseClass { // 主进程中添加如下代码即可 app.commandLine.appendSwitch("wm-window-animations-disabled") // 开启硬件加速 - app.disableHardwareAcceleration(); - eventbus.emit(App.events.AppInit) + app.disableHardwareAcceleration() crashHandler.init() - this._Updater.init() this._DB.init() this._Command.init() this._WindowManager.init() @@ -91,7 +80,6 @@ class App extends BaseClass { color: "#F8F8F8", symbolColor: "#000000", }) - eventbus.emit(App.events.AppReady) }) app.on("will-quit", () => { this.destroy() diff --git a/src/main/base/base.ts b/src/main/base/base.ts index c4b2260..c04c2ac 100644 --- a/src/main/base/base.ts +++ b/src/main/base/base.ts @@ -49,10 +49,7 @@ // export { BaseClass } // export default BaseClass -import EventEmitter from "node:events" - abstract class BaseClass { - public _events = new EventEmitter() abstract init(...argus: any[]) abstract destroy() } diff --git a/src/main/debug.ts b/src/main/debug.ts new file mode 100644 index 0000000..f3c0e84 --- /dev/null +++ b/src/main/debug.ts @@ -0,0 +1,60 @@ +import debug from "debug" +import { app } from "electron" +import path from "node:path" +import logger from "logger/main" +import * as rfs from "rotating-file-stream" +import fs from "fs" + +// 配置根目录 +const logsPath = app.getPath("logs") +logger.debug(`日志地址:${logsPath}`) + +const LOG_ROOT = path.join(logsPath) + +// 缓存当前应用启动的日志文件流 +let currentLogStream: rfs.RotatingFileStream | null = null + +// 生成当前启动时的日志文件名 +const getLogFileName = () => { + const now = new Date() + const timestamp = now.toISOString().replace(/[:.]/g, "-") + return `app-${timestamp}.log` +} + +// 覆盖 debug.log 方法 +const originalLog = debug.log +debug.log = function (...args) { + // 保留原始控制台输出 + originalLog.apply(this, args) + + // 确保日志目录存在 + if (!fs.existsSync(LOG_ROOT)) { + fs.mkdirSync(LOG_ROOT, { recursive: true }) + } + + // 延迟初始化日志流,直到第一次写入 + if (!currentLogStream) { + const logFileName = getLogFileName() + currentLogStream = rfs.createStream(logFileName, { + path: LOG_ROOT, + size: "10M", // 单个文件最大 10MB + rotate: 10, // 保留最近 10 个文件 + }) + } + + // @ts-ignore 获取当前命名空间 + const namespace = this.namespace || "unknown" + + // 写入日志(添加时间戳和命名空间) + const timestamp = new Date().toISOString() + const message = args.join(" ") + currentLogStream.write(`[${timestamp}] [${namespace}] ${message}\n`) +} + +app.on("before-quit", () => { + if (currentLogStream) { + currentLogStream.end() + currentLogStream.destroy() + currentLogStream = null + } +}) diff --git a/src/main/index.ts b/src/main/index.ts index e3eae03..a59ced2 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,73 +1,11 @@ import "reflect-metadata" +import "./debug" +import "setting/main" import "logger/main" import "logger/main-error" -import "setting/main" import { _ioc } from "main/_ioc" import { App } from "main/App" -import debug from "debug" -import fs from "fs" -import path from "path" -import * as rfs from "rotating-file-stream" -import { app } from "electron" - -// 配置根目录 -const logsPath = app.getPath("logs") -console.log(`日志地址:${logsPath}`) - -const LOG_ROOT = path.join(logsPath) - -// 缓存当前应用启动的日志文件流 -let currentLogStream: rfs.RotatingFileStream | null = null - -// 生成当前启动时的日志文件名 -const getLogFileName = () => { - const now = new Date() - const timestamp = now.toISOString().replace(/[:.]/g, '-') - return `app-${timestamp}.log` -} - -// 覆盖 debug.log 方法 -const originalLog = debug.log -debug.log = function (...args) { - // 保留原始控制台输出 - originalLog.apply(this, args) - - // 确保日志目录存在 - if (!fs.existsSync(LOG_ROOT)) { - fs.mkdirSync(LOG_ROOT, { recursive: true }) - } - - // 延迟初始化日志流,直到第一次写入 - if (!currentLogStream) { - const logFileName = getLogFileName() - currentLogStream = rfs.createStream(logFileName, { - path: LOG_ROOT, - size: "10M", // 单个文件最大 10MB - rotate: 10, // 保留最近 10 个文件 - }) - } - - // 获取当前命名空间 - // @ts-ignore - const namespace = this.namespace || 'unknown' - - // 写入日志(添加时间戳和命名空间) - const timestamp = new Date().toISOString() - const message = args.join(" ") - currentLogStream.write(`[${timestamp}] [${namespace}] ${message}\n`) -} - const curApp = _ioc.get(App) curApp.init() - -const _debug = debug("app:app") -app.on("before-quit", () => { - _debug("应用关闭") - if (currentLogStream) { - currentLogStream.end() - currentLogStream.destroy() - currentLogStream = null - } -}) diff --git a/src/main/modules/_ioc.ts b/src/main/modules/_ioc.ts index 52cfab7..f46fcd1 100644 --- a/src/main/modules/_ioc.ts +++ b/src/main/modules/_ioc.ts @@ -5,11 +5,9 @@ import { WindowManager } from "./window-manager" import { Tabs } from "./tabs" import Commands from "./commands" import Zephyr from "./zephyr" -import Updater from "./updater" const modules = new ContainerModule(bind => { bind(Zephyr).toSelf().inSingletonScope() - bind(Updater).toSelf().inSingletonScope() bind(Api).toSelf().inSingletonScope() bind(WindowManager).toSelf().inSingletonScope() bind(Commands).toSelf().inSingletonScope() @@ -21,7 +19,6 @@ async function destroyAllModules(ioc: Container) { await Promise.all([ ioc.get(WindowManager).destroy(), ioc.get(Commands).destroy(), - ioc.get(Updater).destroy(), ioc.get(Zephyr).destroy(), ioc.get(Tabs).destroy(), ioc.get(Api).destroy(), diff --git a/src/main/modules/db/index.ts b/src/main/modules/db/index.ts index ce5db72..0d9d22e 100644 --- a/src/main/modules/db/index.ts +++ b/src/main/modules/db/index.ts @@ -3,14 +3,14 @@ import Setting from "setting/main" import { CustomAdapter, CustomLow } from "./custom" import path from "node:path" import BaseClass from "main/base/base" -import _debug from "debug" +import _logger from "logger/main" -const debug = _debug("app:db") +const logger = _logger.createNamespace("db") @injectable() class DB extends BaseClass { destroy() { - debug(`DB destroy`) + logger.debug(`DB destroy`) } Modules: Record> = {} diff --git a/src/main/modules/tabs/Tab.ts b/src/main/modules/tabs/Tab.ts index 9f7b1ab..007d322 100644 --- a/src/main/modules/tabs/Tab.ts +++ b/src/main/modules/tabs/Tab.ts @@ -5,8 +5,9 @@ import _debug from "debug" // import { Layout } from "./Constant" import FuckHTML from "@res/fuck.html?asset" import { fileURLToPath, pathToFileURL } from "node:url" +import EventEmitter from "node:events" -const debug = _debug("app:tab") +const debug = _debug("tab") interface IOption { url: string @@ -24,6 +25,7 @@ class Tab extends BaseClass { init() { // TODO } + public events = new EventEmitter() public url: string = "" public showUrl: string = "" public title: string = "" @@ -55,10 +57,6 @@ class Tab extends BaseClass { return this.active } - get events() { - return this._events - } - constructor(options = {}, window: BrowserWindow, curRect?: IRect) { super() this.listenResize = this.listenResize.bind(this) diff --git a/src/main/modules/tabs/index.ts b/src/main/modules/tabs/index.ts index c60643e..4be45d8 100644 --- a/src/main/modules/tabs/index.ts +++ b/src/main/modules/tabs/index.ts @@ -11,7 +11,7 @@ interface IRect { height: number } -const debug = _debug("app:tabs") +const debug = _debug("tabs") class Tabs extends BaseClass { destroy() { diff --git a/src/main/modules/window-manager/index.ts b/src/main/modules/window-manager/index.ts index 975d10b..f3fa8c4 100644 --- a/src/main/modules/window-manager/index.ts +++ b/src/main/modules/window-manager/index.ts @@ -3,7 +3,6 @@ import { cloneDeep, merge } from "lodash" import { defaultConfig, defaultWindowConfig, getWindowsMap, IConfig, Param } from "./windowsMap" import { optimizer } from "@electron-toolkit/utils" import BaseClass from "main/base/base" -import _debug from "debug" import _logger from "logger/main" const logger = _logger.createNamespace("modlue:window-manager") // _debug("app:window-manager") @@ -66,7 +65,7 @@ export default class WindowManager extends BaseClass { } createWindow(name: string, opts?: Partial){ - let info = opts as Param + const info = opts as Param info.name = name if (!info.ignoreEmptyUrl && !info.url) { dialog.showErrorBox("错误", name + "窗口未提供url") diff --git a/src/main/modules/zephyr/index.ts b/src/main/modules/zephyr/index.ts index b812250..a5e3389 100644 --- a/src/main/modules/zephyr/index.ts +++ b/src/main/modules/zephyr/index.ts @@ -1,12 +1,12 @@ import { session, net } from "electron" import { injectable } from "inversify" import BaseClass from "main/base/base" -import _debug from "debug" +import _logger from "logger/main" import fs from "fs" import path from "path" import { app } from "electron" -const debug = _debug("app:zephyr") +const logger = _logger.createNamespace("zephyr") /** * Zephyr 模块 - 安全的本地文件访问协议 @@ -108,7 +108,7 @@ class Zephyr extends BaseClass { super() this.interceptHandlerZephyr = this.interceptHandlerZephyr.bind(this) this.initLogFile() - debug("zephyr init") + logger.debug("zephyr init") } private async initLogFile() { @@ -135,7 +135,7 @@ class Zephyr extends BaseClass { const count = this.rateLimiter.get(filePath) || 0 if (count >= this.MAX_REQUESTS) { - debug("访问频率超限:", filePath) + logger.debug("访问频率超限:", filePath) return true } @@ -164,7 +164,7 @@ class Zephyr extends BaseClass { try { await fs.promises.appendFile(this.LOG_FILE, JSON.stringify(logEntry) + "\n", "utf8") } catch (error) { - debug("写入审计日志失败:", error) + logger.debug("写入审计日志失败:", error) } } @@ -197,18 +197,17 @@ class Zephyr extends BaseClass { ses.protocol.unhandle("zephyr") this.fileLocks.clear() this.rateLimiter.clear() - debug("zephyr destroyed") } init(partition?: string) { const ses = partition ? session.fromPartition(partition) : session.defaultSession ses.protocol.handle("zephyr", this.interceptHandlerZephyr) - debug("zephyr initialized with partition:", partition) + logger.debug("zephyr initialized with partition:", partition) } setAllowedPaths(config: Partial) { Object.assign(this.pathConfig, config) - debug("Updated allowed paths:", this.pathConfig) + logger.debug("Updated allowed paths:", this.pathConfig) } private isValidPath(filePath: string): boolean { @@ -233,32 +232,32 @@ class Zephyr extends BaseClass { try { // 1. 基本路径检查 if (!this.isValidPath(filePath)) { - debug("不安全的路径字符:", filePath) + logger.debug("不安全的路径字符:", filePath) return false } // 2. 检查是否包含 .. 路径 if (filePath.includes("..")) { - debug("检测到路径遍历尝试") + logger.debug("检测到路径遍历尝试") return false } // 3. 检查符号链接 if (await this.isSymlink(filePath)) { - debug("不允许访问符号链接") + logger.debug("不允许访问符号链接") return false } // 4. 检查文件大小 if (!(await this.checkFileSize(filePath))) { - debug("文件超出大小限制") + logger.debug("文件超出大小限制") return false } // 5. 文件类型检查 const ext = path.extname(filePath).toLowerCase() if (!this.ALLOWED_EXTENSIONS.includes(ext)) { - debug("不允许的文件类型:", ext) + logger.debug("不允许的文件类型:", ext) return false } @@ -274,7 +273,7 @@ class Zephyr extends BaseClass { }) if (!isInAllowedPath) { - debug("路径不在允许范围内") + logger.debug("路径不在允许范围内") return false } @@ -294,7 +293,7 @@ class Zephyr extends BaseClass { return true } catch (error: any) { await this.logAccess(operation, filePath, false, error.message) - debug("路径安全检查错误:", error) + logger.debug("路径安全检查错误:", error) return false } } @@ -314,7 +313,7 @@ class Zephyr extends BaseClass { } if (!(await this.isPathSafe(filePath, operation))) { - debug("访问被拒绝:", filePath) + logger.debug("访问被拒绝:", filePath) return new Response("Access Denied", { status: 403 }) } @@ -328,7 +327,7 @@ class Zephyr extends BaseClass { return new Response("Operation not supported", { status: 400 }) } } catch (error) { - debug("处理请求错误:", error) + logger.debug("处理请求错误:", error) return new Response("Internal Server Error", { status: 500 }) } } @@ -420,7 +419,7 @@ class Zephyr extends BaseClass { case this.OPERATIONS.WRITE: return this.pathConfig.write default: - debug("未知的操作类型:", operation) + logger.debug("未知的操作类型:", operation) return null } } diff --git a/src/main/utils/index.ts b/src/main/utils/index.ts index c485860..11aa880 100644 --- a/src/main/utils/index.ts +++ b/src/main/utils/index.ts @@ -12,7 +12,7 @@ export function getFileUrl(app: string) { return slash(winURL) } -export function getPreloadUrl(file){ +export function getPreloadUrl(file) { return join(__dirname, `../preload/${file}.mjs`) } diff --git a/tsconfig.node.json b/tsconfig.node.json index b15a15f..b85c63b 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -13,12 +13,17 @@ "packages/logger/preload.ts", "packages/logger/preload-error.ts", "packages/logger/common.ts", + "packages/**/*.main.ts", + "packages/**/main/**/*", + "packages/**/main.ts", + "packages/**/*.common.ts", + "packages/**/common.ts", "src/common/**/*.main.ts", "src/common/**/main/**/*", "src/common/**/main.ts", "src/common/**/*.common.ts", "src/common/**/common.ts" -, "src/common/event/_Base.ts" ], + ], "compilerOptions": { "composite": true, "emitDecoratorMetadata": true, @@ -59,6 +64,9 @@ "logger/*": [ "packages/logger/*" ], + "base/*": [ + "packages/base/*" + ], } } } diff --git a/tsconfig.web.json b/tsconfig.web.json index 4aa9783..54f273e 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -13,7 +13,8 @@ "src/types/**/*", "config/**/*", "./typed-router.d.ts", - "src/common/**/*" + "src/common/**/*", + "packages/**/*", ], "exclude": [ "packages/locales/main.ts", @@ -21,6 +22,9 @@ "packages/logger/main.ts", "packages/logger/main-error.ts", "packages/logger/crash-handler.ts", + "packages/**/main/**/*", + "packages/**/*.main.ts", + "packages/**/main.ts", "src/common/**/main/**/*", "src/common/**/*.main.ts", "src/common/**/main.ts" @@ -60,6 +64,9 @@ "@res/*": [ "resources/*" ], + "base/*": [ + "packages/base/*" + ], } } }