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