【Electron】vue+electron实现图片视频本地缓存

一、背景

有一款electron开发的桌面应用,因为包含即时通讯的功能,所以在聊天消息中会有很多的图片视频消息以及会话的头像等,这些图片视频占用了大量的网络资源,领导要求优化一下,将图片和视频缓存至本地。

二、步骤

1.获取所有的图片视频请求

经过调研发现electron的session模块有关于本应用的所有的web请求的监听方法。可参考

electron官方文档链接-WebRequest

于是乎,我们可以用这个方法获取到所有的图片视频的请求了


// 这里filter参数是为了筛选过滤哪些url的请求,

session.defaultSession.webRequest.onCompleted(filter, (details) => {

 // 这里可以通过details.resourceType判断请求的是否为图片类型,这里也获取了other,是因为视频也会存在在other中

 if ((details.resourceType === 'image' || details.resourceType === 'other')) {

 // 获取请求地址

 const souceUrl = details.url

 }

 })

注意这个监听一定要在app.ready之后调用


app.on('ready', async () => {

 // 在这里调用

})

2.将图片和视频存储至本地

这里我使用的是node的request模块。在监听到请求后,将获取到的图片视频请求地址,通过request模块下载至本地。在这之前先创建本应用的本地文件缓存地址。这里有个坑,在打包之后,如果在应用内生成文件夹,windows系统会报错,因为应用没有访问权限,不能进行文件的操作。这里有两种解决方法:

  1. 方法一(不推荐)

因为我是通过electron-builder构建的应用,可以在打包配置里面添加 如下代码,将打包的应用等级提升为管理员权限,这样打好包安装之后运行,默认是以管理员身份运行的。


builderOptions: {

 ...

 win: {

 ...

 requestedExecutionLevel: 'highestAvailable'

 }

}

但是这种方法有个缺陷,以管理员身份运行的程序,在windows系统中,是不允许文件往里面拖拽的。

设置好之后,创建文件夹


import fs from 'fs'

const path = require('path')

const log = require('electron-log') // 记录日志(如有需要安装)

// 设置存放缓存文件的文件夹

const AVATARPATH = 'temp'

// 设置文件夹位置(在安装应用文件夹内)

const basePath = path.join(__dirname, AVATARPATH)

fs.mkdir(basePath, { recursive: true }, err => {

 if (err) log.warn(`mkdir path: ${basePath} err`)

})

  1. 方法二(推荐!!!

因为不能拖拽,会影响文件上传等功能,致使用户体验非常不好,之后又找到electron文档中,有一个方法 app.getPath(name) 可以使用。electron官方文档中对于文件位置的方法


app.getPath('userData')

如官方文档所写:这个路径用于存储应用程序配置文件的目录,默认情况下是附加应用程序名称的appData目录。按照惯例,存储用户数据的文件应写入此目录,不建议在此写入大文件,因为某些环境可能会将此目录备份到云存储。

因此我们将文件夹创建在这个目录下,就不需要担心windows系统的文件权限问题了, 也就不需要requestedExecutionLevel: 'highestAvailable'这个配置了。


import fs from 'fs'

const path = require('path')

const log = require('electron-log') // 记录日志(如有需要安装)

// 设置存放缓存文件的文件夹

const AVATARPATH = 'temp'

// 设置文件夹位置(在安装应用文件夹内)

const basePath = path.join(app.getPath('userData'), AVATARPATH)

fs.mkdir(basePath, { recursive: true }, err => {

 if (err) log.warn(`mkdir path: ${basePath} err`)

})

这里我们就将第一步创建图片视频缓存文件夹做好了。

之后我们就可以将文件通过request下载至本地了


const request = require('request')

session.defaultSession.webRequest.onCompleted(filter, (details) => {

 // 这里可以通过details.resourceType判断请求的是否为图片类型,这里也获取了other,是因为视频也会存在在other中

 if ((details.resourceType === 'image' || details.resourceType === 'other')) {

 // 获取请求地址

 const souceUrl = details.url

 let ext = '' // 文件类型

 // 设置存储的文件的类型

 const filterArr = ['webp', 'jpg', 'jpeg', 'png', 'bmp', 'gif', 'svg', 'mp4', 'wmv']

 // ======= 获取文件类型start (根据不同的souceUrl,类型获取方式可能不同) ========

 const index = souceUrl.lastIndexOf('.')

 ext = souceUrl.substr(index + 1)

 // ======= 获取文件类型end (根据不同的souceUrl,类型获取方式可能不同) ========

 // 若不是需要存储的文件类型,则不进行以后得步骤

 if (!filterArr.includes(ext.toLowerCase())) return

 // 这里的uuid是随机生成的字符串(可以自己另寻方法,不做展示)

 let filename = uuid(8, 16) + '.' + ext

 const req = request({ method: 'GET', uri: souceUrl })

 req.pipe(fs.createWriteStream(path.join(basePath, filename)))

 // 文件大小

 var total = 0

 req.on('response', (data) => {

 total = parseInt(data.headers['content-length'])

 })

 req.on('data', (chunk) => {})

 req.on('error', (error) => {

 log.warn('error====req', error)

 })

 req.on('end', () => {

 // log.warn(uuid(8, 16), 'end', path.join(basePath, filename))

 // 获取存储成功后本地路径

 let localPath = path.join(basePath, filename)

 if (process.platform !== 'darwin') { // 这里判断是否为windows系统,windows系统需要//这种反斜杠才能展示

 const arr = localPath.split(path.sep)

 localPath = arr.reduce((pre, cue) => {return pre ? `${pre}//${cue}` : cue}, '')

 }

 })

 }

})

这样 我们就已经将图片和视频存储至本地了。

3.将文件信息存入本地数据库

我们在上一步已经将图片视频的文件缓存至本地了, 现在我们怎么使用他们呢?首先我们要将这些图片和视频存至本地数据库中,这里我用的是localforage。首先引入localforage插件。


yarn add localforage

// or

npm install localforage

之后在入口文件创建本地数据库实例。


// main.js

window.$ChatAvatarStore = localforage.createInstance({

 name: 'ChatAvatarStore'

})

配置好数据库之后,我们将媒体文件的源路径,本地路径以及文件大小存进去,这里我们用到了electron的进程间的通讯,通过ipc将这些信息,从主进程传输给渲染进程,之后存进本地数据库。


// 【主进程】这里是上面调用request的end的回调之中,在图片保存完之后,再将数据传输给渲染进程

req.on('end', () => {

 // ....

 win.webContents.send('callbackAvatarPath', {

 souceUrl: souceUrl,

 localPath: localPath,

 size: total

 })

})


// main.js 这里我们现将sharedObject这个通用在渲染进程和主进程的存储工具放在入口文件定义,

// 还有ipcRenderer也全局定义在window上

window.ipcRenderer = window.require('electron').ipcRenderer

window.$_SO = window.$remote.getGlobal('sharedObject')

// home.vue【渲染进程】

// 存储远程图片至本地

window.ipcRenderer.on('callbackAvatarPath', (event, localObj) => {

 // 存储至数据库,以souceUrl为key

 window.$ChatAvatarStore.setItem(localObj.souceUrl, localObj).then(value => {

 // 当值被存储后,可执行其他操作

 // 存储至全局变量loaclImgs中

 window.$_SO.loaclImgs.set(localObj.souceUrl, localObj)

 }).catch(function (err) {

 // 当出错时,此处代码运行

 console.log(err)

 })

})

// 每一次进入程序之后先同步本地图片信息

mounted() {

 // 存储本地图片同步信息

 window.$ChatAvatarStore.iterate((value, key, iterationNumber) => {

 // 此回调函数将对所有 key/value 键值对运行

 window.$_SO.loaclImgs.set(key, value)

 })

}

这里我们就完成了在系统中的文件数据的存储

4.在渲染进程展示本地图片

首先在electron应用中展示本地的图片或视频,我们需要定义一种协议去加载本地图片。这里我们用到了protocol这个模块electron官方文档中protocol模块说明


// background.js

app.whenReady().then(() => {

 // 这个需要在app.ready触发之后使用

 protocol.registerFileProtocol('item', (request, callback) => {

 const url = request.url.substr(7)

 callback(decodeURI(path.normalize(url)))

 })

})

这样我们就定义好了本地文件展示协议。

之后我们就在图片渲染的地方,进行拦截加载。以下举例


<img :src="getViewImgUrl(imgURl)" />


const fs = require('fs')

// util.js 放到工具文件中的通用方法

export const getViewImgUrl = (url) => {

 // 查看本地数据库是否有缓存这个文件

 const result = window.$_SO.loaclImgs.get(url)

 if (result) { // 如果有

 try {

 // 判断图片是否还存在本地文件中

 fs.accessSync(result.localPath, fs.constants.F_OK)

 console.log('File does exist')

 url = 'item:///' + result.localPath

 } catch (err) {

 // 若不存在,(有可能被人为删除),则清除这条记录

 window.$_SO.loaclImgs.delete(url)

 window.$ChatAvatarStore.removeItem(url)

 console.error('File does not exist')

 }

 }

 return url

}

这样 我们就是现实了electron的图片视频本地化缓存以及展示功能。

[图片上传失败...(image-edb80f-1661399069657)]

三、后记

有了缓存之后,可能还需要清除缓存的功能。


// 在主进程中

// 监听获取temp文件大小

ipcMain.on('getTempSize', (event, arg) => {

 // 遍历文件夹,获取所有文件夹里面的文件信息

 const geFileList = (path) => {

 var filesList = []

 readFile(path, filesList)

 let totalSize = 0

 for (var i = 0; i < filesList.length; i++) {

 var item = filesList[i]

 totalSize += item.size

 }

 return totalSize

 }

 // 遍历读取文件

 const readFile = (paths, filesList) => {

 var files = fs.readdirSync(paths)// 需要用到同步读取

 files.forEach(walk)

 function walk (file) {

 try {

 var states = fs.statSync(path.join(paths, file))

 if (states.isDirectory()) {

 readFile(paths + '/' + file, filesList)

 } else {

 // 创建一个对象保存信息

 // eslint-disable-next-line no-new-object

 var obj = new Object()

 obj.size = states.size// 文件大小,以字节为单位

Orville's Ideas and Interests = file// 文件名

 obj.path = paths + '/' + file // 文件绝对路径

 filesList.push(obj)

 }

 } catch (error) {

 log.error('监听获取temp文件大小--被占用文件-----', error)

 }

 }

 }

 const AVATARPATH = 'temp'

 const basePath = path.join(app.getPath('userData'), AVATARPATH)

 console.log(1234, geFileList(basePath))

 const size = (geFileList(basePath) / 1024 / 1024).toFixed(2)

 // 回调给渲染进程结果

 win.webContents.send('callbackTempSize', size)

})

// 清除缓存, 若不传 则全部清空, 若传 则清除次数目以上的大小的文件

ipcMain.on('delAllOrBigtemp', (e, arg) => {

 // let cun = 0

 const emptyDir = (paths) => {

 const files = fs.readdirSync(paths)

 files.forEach(file => {

 const filePath = path.join(paths, file)

 // const filePath = `${paths}/${file}`

 try {

 const stats = fs.statSync(filePath)

 const fileSize = stats.size / 1024 / 1024

 // console.log(1111, cun, file, fileSize.toFixed(2))

 // cun++

 if (fileSize > arg) {

 console.log(file)

 }

 if (arg && fileSize < arg) return

 if (stats.isDirectory()) {

 emptyDir(filePath)

 } else {

 fs.unlinkSync(filePath)

 console.log(`删除${file}文件成功`)

 }

 } catch (error) {

 log.error('删除文件-被占用文件-----', error, filePath)

 }

 })

 // console.log(222, cun)

 }

 const AVATARPATH = 'temp'

 const basePath = path.join(app.getPath('userData'), AVATARPATH)

 emptyDir(basePath)

})

以上功能就补全了, 形成了闭环,完结撒花。如有不足之处,请指出,谢谢。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容