Compare commits

...

2 Commits

Author SHA1 Message Date
谢亚昕 e969ec2236 优化事件系统和配置管理,增强日志功能 1 day ago
谢亚昕 a9de1ec525 重构项目架构并优化热更新功能 3 days ago
  1. 2
      .vscode/settings.json
  2. 3
      config/index.ts
  3. 2
      package.json
  4. 123
      packages/base/event/main/index copy.ts
  5. 51
      packages/base/event/main/index.ts
  6. 27
      packages/base/index.ts
  7. 13
      packages/base/package.json
  8. 13
      packages/helper/package.json
  9. 7
      packages/helper/updater/common.ts
  10. 8
      packages/helper/updater/main/handler.ts
  11. 74
      packages/helper/updater/main/hot/download.ts
  12. 28
      packages/helper/updater/main/hot/index.ts
  13. 36
      packages/helper/updater/main/index.ts
  14. 0
      packages/helper/updater/renderer.ts
  15. 10
      packages/logger/main.ts
  16. 9
      packages/setting/main.ts
  17. 6
      packages/setting/main/event.ts
  18. 10
      pnpm-lock.yaml
  19. 7
      src/common/_ioc.main.ts
  20. 4
      src/common/event/PlatForm/index.ts
  21. 2
      src/common/event/Snippet/hook.ts
  22. 4
      src/common/event/Snippet/index.ts
  23. 4
      src/common/event/Tabs/index.ts
  24. 15
      src/common/event/Updater/index.ts
  25. 15
      src/common/event/Updater/main/command.ts
  26. 5
      src/common/event/common.ts
  27. 14
      src/common/event/update/index.ts
  28. 10
      src/common/event/update/main/command.ts
  29. 8
      src/common/event/update/main/index.ts
  30. 12
      src/common/lib/_Base.ts
  31. 7
      src/common/readme.md
  32. 14
      src/main/App.ts
  33. 3
      src/main/base/base.ts
  34. 60
      src/main/debug.ts
  35. 66
      src/main/index.ts
  36. 3
      src/main/modules/_ioc.ts
  37. 6
      src/main/modules/db/index.ts
  38. 8
      src/main/modules/tabs/Tab.ts
  39. 2
      src/main/modules/tabs/index.ts
  40. 3
      src/main/modules/window-manager/index.ts
  41. 35
      src/main/modules/zephyr/index.ts
  42. 2
      src/main/utils/index.ts
  43. 10
      tsconfig.node.json
  44. 9
      tsconfig.web.json

2
.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"

3
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,8 @@ 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,

2
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:^",

123
packages/base/event/main/index copy.ts

@ -0,0 +1,123 @@
type FireFN = (...argu: any[]) => void
// 监听器类型定义,支持优先级
interface Listener<F extends FireFN> {
fn: F
once: boolean
priority: number
}
class FireEvent<T extends Record<string | symbol, FireFN>> {
// 使用 Map 存储事件监听器,支持 symbol 键
private events = new Map<keyof T, Array<Listener<T[keyof T]>>>()
// 获取事件监听器列表,如果不存在则创建
private getListeners<S extends keyof T>(name: S): Array<Listener<T[S]>> {
if (!this.events.has(name)) {
this.events.set(name, [])
}
return this.events.get(name) as Array<Listener<T[S]>>
}
// 按优先级排序监听器
private sortListeners<S extends keyof T>(name: S) {
const listeners = this.getListeners(name)
listeners.sort((a, b) => b.priority - a.priority)
}
// 打印事件和监听器信息
print() {
console.log("Registered Events:")
this.events.forEach((listeners, name) => {
// 显式处理 symbol 类型
const keyType = typeof name === "symbol" ? `Symbol(${name.description || ""})` : String(name)
console.log(` ${keyType}: ${listeners.length} listeners`)
})
}
// 添加事件监听器,支持优先级
on<S extends keyof T>(name: S, fn: T[S], priority = 0): this {
const listeners = this.getListeners(name)
listeners.push({ fn, once: false, priority })
this.sortListeners(name)
return this // 支持链式调用
}
// 触发事件
emit<S extends keyof T>(name: S, ...args: Parameters<T[S]>): this {
const listeners = this.getListeners(name).slice() // 创建副本以避免移除时的问题
for (const { fn } of listeners) {
try {
fn(...args)
} catch (error) {
console.error(`Error in event handler for ${String(name)}:`, error)
}
}
// 移除一次性监听器
if (listeners.some(l => l.once)) {
this.events.set(
name,
this.getListeners(name).filter(l => !l.once),
)
}
return this
}
// 移除事件监听器
off<S extends keyof T>(name: S, fn?: T[S]): this {
if (!this.events.has(name)) return this
const listeners = this.getListeners(name)
if (!fn) {
// 移除所有监听器
this.events.delete(name)
} else {
// 移除特定监听器
const filtered = listeners.filter(l => l.fn !== fn)
if (filtered.length === 0) {
this.events.delete(name)
} else {
this.events.set(name, filtered)
}
}
return this
}
// 添加一次性事件监听器
once<S extends keyof T>(name: S, fn: T[S], priority = 0): this {
const listeners = this.getListeners(name)
listeners.push({ fn, once: true, priority })
this.sortListeners(name)
return this
}
// 清除所有事件监听器
clear(): this {
this.events.clear()
return this
}
// 获取指定事件的监听器数量
listenerCount<S extends keyof T>(name: S): number {
return this.events.get(name)?.length || 0
}
// 检查事件是否有监听器
hasListeners<S extends keyof T>(name: S): boolean {
return this.listenerCount(name) > 0
}
// 获取所有事件名称
eventNames(): Array<keyof T> {
return Array.from(this.events.keys())
}
}
export function buildEmitter<T extends Record<string | symbol, FireFN>>() {
return new FireEvent<T>()
}

51
packages/base/event/main/index.ts

@ -0,0 +1,51 @@
// type FireKey = string
type FireFN = (...argu: any[]) => void
class FireEvent<T extends Record<string | symbol, FireFN>> {
#events: Record<keyof T, FireFN[]> = {} as any
print() {
Object.keys(this.#events).forEach(key => {
console.log(`${key}: ${this.#events[key]}\n`)
})
}
on<S extends keyof T>(name: S, fn: T[S]) {
if (!this.#events[name]) {
this.#events[name] = []
}
this.#events[name].push(fn)
}
emit<S extends keyof T>(name: S, ...argu: Parameters<T[S]>) {
if (this.#events[name]) {
this.#events[name].forEach(fn => {
fn(...argu)
})
}
}
off<S extends keyof T>(name: S, fn?: T[S]) {
const len = this.#events[name].length
if (!len) {
return
}
if (!fn) {
this.#events[name] = []
} else {
for (let i = len - 1; i >= 0; i--) {
const _fn = this.#events[name][i]
if (_fn === fn) {
this.#events[name].splice(i, 1)
}
}
}
}
once<S extends keyof T>(name: S, fn: T[S]) {
const _fn: any = (...argu: any[]) => {
fn(...argu)
this.off<S>(name, _fn)
}
this.on(name, _fn)
}
}
export function buildEmitter<T extends Record<string | symbol, FireFN>>() {
return new FireEvent<T>()
}

27
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<T extends BaseSingleton>(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 }

13
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"
}

13
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"
}

7
packages/helper/updater/common.ts

@ -0,0 +1,7 @@
export const enum EventEnum {
UPDATE_PROGRESS = "update-progress",
}
export type EventMaps = {
[EventEnum.UPDATE_PROGRESS]: () => void
}

8
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)
}

74
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)
// },
// })

28
src/main/modules/updater/hot/index.ts → 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
}
}

36
src/main/modules/updater/index.ts → 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<typeof setInterval> | 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

0
packages/helper/updater/renderer.ts

10
packages/logger/main.ts

@ -1,10 +1,10 @@
import { app, ipcMain } from "electron"
import fs from "fs"
import path from "path"
import setting from "setting/main"
import config from "config"
import * as rfs from "rotating-file-stream"
import { LogLevel, LogLevelColor, LogLevelName } from "./common"
import { emitter } from "setting/main/event"
// 重置颜色的ANSI代码
const RESET_COLOR = "\x1b[0m"
@ -21,7 +21,7 @@ export interface LoggerOptions {
// 默认配置
const DEFAULT_OPTIONS: LoggerOptions = {
level: setting.values("debug"),
level: config.default_config.debug,
namespace: "app",
console: true,
file: true,
@ -260,8 +260,8 @@ export class Logger {
// 默认实例
const logger = Logger.getInstance()
logger.init()
setting.onChange("debug", function (n) {
logger.setLevel(n.debug)
emitter.on("update", setting => {
logger.setLevel(setting.debug)
})
// 应用退出时关闭日志流

9
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" })
@ -228,3 +228,4 @@ const Setting = new SettingClass()
export default Setting
export { Setting }
export type { IConfig, IOnFunc }

6
packages/setting/main/event.ts

@ -0,0 +1,6 @@
import { buildEmitter } from "base/event/main"
import type { IOnFunc } from "setting/main"
export const emitter = buildEmitter<{
update: IOnFunc
}>()

10
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: {}

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

4
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()
}

2
src/common/event/Snippet/hook.ts

@ -1,5 +1,5 @@
import { Snippet } from "."
export function useSnippet() {
return Snippet.getInstance<Snippet>()
return Snippet.getInstance()
}

4
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()
}

4
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()
}

15
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 }

15
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()
}
}

5
src/common/event/common.ts

@ -1,5 +0,0 @@
const keys = ["hot-update-ready"] as const
type AllKeys = (typeof keys)[number]
export type { AllKeys }

14
src/common/event/update/index.ts

@ -1,14 +0,0 @@
// import type { AllKeys } from "../common"
const curProgress = ref(0)
// api.on<AllKeys>("progress", () => {
// curProgress.value = 10
// })
function useUpdate() {
return {
curProgress,
}
}
export { useUpdate }

10
src/common/event/update/main/command.ts

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

8
src/common/event/update/main/index.ts

@ -1,8 +0,0 @@
import { broadcast } from "main/utils"
import { AllKeys } from "common/event/common"
function emitHotUpdateReady(...argu) {
broadcast<AllKeys>("hot-update-ready", ...argu)
}
export { emitHotUpdateReady }

12
src/common/lib/_Base.ts

@ -1,12 +0,0 @@
export abstract class _Base {
static instance
static getInstance<T>(): T {
if (!this.instance) {
// 如果实例不存在,则创建一个新的实例
// @ts-ignore ...
this.instance = new this()
}
return this.instance
}
}

7
src/common/readme.md

@ -0,0 +1,7 @@
## event
通用事件处理模块
- main/**/* 处理主进程的模块
- main/command.ts 会通过ioc收集,进入依赖管理中
- 其他 处理渲染进程的模块

14
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()

3
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()
}

60
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
}
})

66
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
}
})

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

6
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<string, CustomLow<any>> = {}

8
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)

2
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() {

3
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<IConfig>){
let info = opts as Param
const info = opts as Param
info.name = name
if (!info.ignoreEmptyUrl && !info.url) {
dialog.showErrorBox("错误", name + "窗口未提供url")

35
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<typeof this.pathConfig>) {
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
}
}

2
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`)
}

10
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/*"
],
}
}
}

9
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/*"
],
}
}
}

Loading…
Cancel
Save