Browse Source
- 修改 `.vscode/settings.json` 中的 TypeScript 默认格式化工具为 Prettier,统一代码风格 - 在 `config/index.ts` 中添加 `update.hoturl` 配置项,用于指定热更新包下载地址 - 在 `package.json` 中新增 `base` 和 `helper` 工作区依赖,实现模块化拆分 - 移除 `src/common/lib/_Base.ts` 并将单例基类迁移至 `packages/base` 独立模块 - 重构热更新模块到 `packages/helper/updater`,增加下载进度事件通知功能 - 清理废弃的更新相关事件定义文件和冗余代码,保持代码整洁 - 将日志模块从 debug 切换为 logger 命名空间,统一日志管理 - 添加调试初始化脚本 `src/main/debug.ts`,优化日志记录机制feat/优化
43 changed files with 572 additions and 429 deletions
@ -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 } |
@ -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" |
||||
|
} |
@ -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" |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
export const enum EventEnum { |
||||
|
UPDATE_PROGRESS = "update-progress", |
||||
|
} |
||||
|
|
||||
|
export type EventMaps = { |
||||
|
[EventEnum.UPDATE_PROGRESS]: () => void |
||||
|
} |
@ -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) |
||||
|
} |
@ -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)
|
||||
|
// },
|
||||
|
// })
|
@ -1,5 +1,5 @@ |
|||||
import { Snippet } from "." |
import { Snippet } from "." |
||||
|
|
||||
export function useSnippet() { |
export function useSnippet() { |
||||
return Snippet.getInstance<Snippet>() |
return Snippet.getInstance() |
||||
} |
} |
||||
|
@ -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 } |
@ -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() |
||||
|
} |
||||
|
} |
@ -1,5 +0,0 @@ |
|||||
const keys = ["hot-update-ready"] as const |
|
||||
|
|
||||
type AllKeys = (typeof keys)[number] |
|
||||
|
|
||||
export type { AllKeys } |
|
@ -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 } |
|
@ -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() |
|
||||
} |
|
||||
} |
|
@ -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 } |
|
@ -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 |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,7 @@ |
|||||
|
## event |
||||
|
|
||||
|
通用事件处理模块 |
||||
|
|
||||
|
- main/**/* 处理主进程的模块 |
||||
|
- main/command.ts 会通过ioc收集,进入依赖管理中 |
||||
|
- 其他 处理渲染进程的模块 |
@ -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 |
||||
|
} |
||||
|
}) |
Loading…
Reference in new issue