• 【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
        }
     })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    app.on('ready', async () => {
    	// 在这里调用
    })
    
    • 1
    • 2
    • 3

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

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

    1. 方法一(不推荐)
      因为我是通过electron-builder构建的应用,可以在打包配置里面添加 如下代码,将打包的应用等级提升为管理员权限,这样打好包安装之后运行,默认是以管理员身份运行的。
    builderOptions: {
    	...
    	win: {
    		...
    		requestedExecutionLevel: 'highestAvailable'
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    但是这种方法有个缺陷,以管理员身份运行的程序,在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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 方法二(推荐!!!
      因为不能拖拽,会影响文件上传等功能,致使用户体验非常不好,之后又找到electron文档中,有一个方法 app.getPath(name) 可以使用。electron官方文档中对于文件位置的方法
    app.getPath('userData')
    
    • 1

    如官方文档所写:这个路径用于存储应用程序配置文件的目录,默认情况下是附加应用程序名称的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`)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    之后我们就可以将文件通过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}, '')
    	      }
    	    })
    	}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

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

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

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

    yarn add localforage
    // or
    npm install localforage
    
    • 1
    • 2
    • 3

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

    // main.js
    window.$ChatAvatarStore = localforage.createInstance({
      name: 'ChatAvatarStore'
    })
    
    • 1
    • 2
    • 3
    • 4

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

    // 【主进程】这里是上面调用request的end的回调之中,在图片保存完之后,再将数据传输给渲染进程
    req.on('end', () => {
      // ....
      win.webContents.send('callbackAvatarPath', {
        souceUrl: souceUrl,
        localPath: localPath,
        size: total
      })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    // 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)
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

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

    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)))
      })
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样我们就定义好了本地文件展示协议。
    之后我们就在图片渲染的地方,进行拦截加载。以下举例

    <img :src="getViewImgUrl(imgURl)" />
    
    • 1
    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    三、后记

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

    // 在主进程中
    // 监听获取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// 文件大小,以字节为单位
              obj.name = 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)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

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

  • 相关阅读:
    2022杭电多校第六场题解
    设计模式详解(十一)——组合模式
    麦芽糖-刀豆球蛋白A,maltose-ConcanavalinA,刀豆球蛋白A-PEG-麦芽糖
    力扣每日一题47:全排列 ||
    JdbcTemplate概述和测试
    双重差分模型(DID)论文写作指南与操作手册
    selinux的关闭
    vue.js 进行初始化遇到的关于core-js的错误
    37_ue4进阶末日生存游戏开发[显示物品列表]
    beego使用API自动化文档生成swagger时,routers目录下无法生成commentsRouter_controllers.go文件
  • 原文地址:https://blog.csdn.net/airdark_long/article/details/126509209