继上一篇文章: 如何用Vue开发Electron桌面程序? 这篇就够了! - (jianshu.com)
这篇文章主要介绍
-
asar
包的简单保护 - 支持快捷键的使用
- 菜单的动态修改
- 日志功能
1. asar
的包的使用
从上篇我们知道, asar
包可以用7z的插件或者直接使用asar命令解压, 但是有时候我们不像让人解压直接看到我们的代码逻辑, 可以使用一个库来修改, 即asarmo
我也有幸贡献了代码, 将它的
write
方法修改成返回Promise对象, 方便进行同步操作, 比如打增量包
feat: make asarmor.write() return the Promise by klren0312 · Pull Request #10 · sleeyax/asarmor (github.com)
const { Asarmor, Trashify, FileCrash } = require('asarmor')
const { join } = require('path')
const AdmZip = require('adm-zip')
const pkg = require('./package.json')
exports.default = async ({ appOutDir, packager, outDir }) => {
try {
const asarPath = join(packager.getResourcesDir(appOutDir), 'app.asar')
console.log(`applying asarmor protections to ${asarPath}`)
const asarmor = new Asarmor(asarPath)
asarmor.applyProtection(new FileCrash('background.js.LICENSE.txt'))
asarmor.applyProtection(new Trashify(['.git', '.env']))
await asarmor.write(asarPath)
const targetPath = join(appOutDir, './resources')
const zip = new AdmZip()
zip.addLocalFolder(targetPath)
const partUpdateFile = `update-win-${pkg.version}.zip`
zip.writeZip(join(outDir, partUpdateFile))
} catch (err) {
console.error(err)
}
}
asarmo库有以下几个功能(使用7z插件进行解压, 虽然都会报错, 但是只有第一种时无法将文件解压出来, 其他其实都已经解压出来了)
- 对压缩包中的指定文件进行损坏(一定是不会被调用的文件, 不然会使
electron
也无法访问, 导致无法运行)
image.png
-
生成大量随机文件填充压缩包, 解压的时候阻塞解压(可以指定文件的体积, 例如10G, 则解压时会进行10G文件解压), 但是这样似乎不会导致文件无法解压, 取消解压后, 其实文件已经解压出来了
image.png
image.png
image.png -
往压缩包里添加不存在文件
image.png
image.png
image.png
asarmo实现这些的主要原理就是通过chromium-pickle来对asar
打包和解包的工具, 对包的header信息进行修改, 从而使解压出现错误
-
上面的第一种方法, 我们可以看到, 我们指定的文件size修改成了负值
image.png -
第二种方法, 我们可以看到, header信息里被添加了很多随机文件
image.png -
第三种方法, 我们可以看到, 添加了我们指定的不存在的文件
image.png
2. 支持快捷键的使用
electron
官方已经支持了快捷键的使用, 可以访问globalShortcut
了解
我们可以在窗口focus
的时候注册快捷键, 然后在blur
的时候注销快捷键
// 窗口聚焦
win.on('focus', () => {
globalShortcut.register('Alt+A', () => {
// 相关逻辑
})
})
// 窗口失焦
win.on('blur', () => {
globalShortcut.unregister('Alt+A')
})
3. 菜单的动态修改
获取const menuInstance = Menu.buildFromTemplate(this.template)
创建的菜单实例
通过menuInstance.items
获取菜单数组, 来修改相关菜单
4. 日志功能
使用winston来实现日志功能,
通过winston-daily-rotate-file: A transport for winston which logs to a rotating file each day. (github.com)对日志进行限制, 定期清除
封装日志组件
const { transports, createLogger, format } = require('winston')
const { combine, timestamp, printf } = format
require('winston-daily-rotate-file')
const path = require('path')
const dateFormat = date => {
const addZero = num => {
if (num < 10) {
return '0' + num
} else {
return num
}
}
const year = date.getFullYear()
const month = addZero(date.getMonth() + 1)
const day = addZero(date.getDate())
const hour = addZero(date.getHours())
const minute = addZero(date.getMinutes())
const second = addZero(date.getSeconds())
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
}
const myFormat = printf(({ level, message, timestamp }) => {
return `${dateFormat(new Date(timestamp))} - ${level}: ${message}`
})
const app =
process.type === 'browser'
? require('electron').app
: require('electron').remote.app
const logDir = path.resolve(app.getPath('userData'), 'logs')
// const logLevel = process.env.NODE_ENV === 'production' ? 'info' : 'debug';
const logLevel = 'debug'
const levels = {
debug: 0,
info: 1,
warn: 2,
error: 3
}
const d = (level, message) => {
if (levels[level] >= levels[logLevel]) {
const consoleLevel = level === 'debug' ? 'log' : level
console[consoleLevel](message)
}
}
// App进程
function AppLogger() {
const appLogFileName = path.resolve(logDir, 'appLogs%DATE%.log')
const transport = new transports.DailyRotateFile({
level: logLevel,
filename: appLogFileName,
maxSize: '5m',
maxFiles: '15d,100m' // 15天以前的自动删除,文件大小超过100m时将旧文件删
})
this.logger = createLogger({
format: combine(timestamp(), myFormat),
transports: [transport]
})
return this
}
AppLogger.prototype.debug = function debug(message) {
d('debug', message)
return this.logger.debug(message)
}
AppLogger.prototype.info = function info(message) {
d('info', message)
return this.logger.info(message)
}
AppLogger.prototype.warn = function warn(message) {
d('warn', message)
return this.logger.warn(message)
}
AppLogger.prototype.error = function error(message) {
d('error', message)
return this.logger.error(message)
}
const appLogger = new AppLogger()
module.exports = {
appLogger
}
使用日志组件
import { appLogger } from './Logger'
appLogger.info(`------ 日志 ------`)