【uniapp】---- 在 uniapp 实现 app 的版本检查、下载最新版本、自动安装

【1024程序员节征文】以 1024 之名,写我与代码的「双向奔赴」 10w+人浏览 286人参与

1. 前言

很久之前就准备自己开发一个账单 app 出来自家使用,今年的事情比较少,先使用 uniapp 进行开发,因为没有太多的体验要求,而且 uniapp 的开发也是我比较熟悉的,因此就选择了 uniapp。由于需要家人和你关联使用,因此在数据存储上边不使用自己购买服务器的话就是一个问题!当时第一个解决方案想到的就是【坚果云】,这里不是打广告,而是我之前使用【计次郎】的时候,他需要你绑定一个云空间,然后将数据推送过去,但是在对接的时候,放弃了!然后又想到了 gitee ,所以本文章采用的就是 gitee 获取最新的版本,然后和当前版本进行对比,看看是否需要更新。

2. 实现步骤

  1. 检查版本更新,看看是否需要进行更新操作;
  2. 更新提示弹框,下载并更新应用;
  3. 确认创建下载任务,提示下载;
  4. 使用 plus.runtime.install 进行安装;
  5. 使用 plus.runtime.restart 进行重启。

输入图片说明

3. 检查版本

3.1 实现分析
  1. 获取当前版本信息:从 versionConfig 中获取应用程序的当前版本号;
  2. 扫描APK文件夹中的所有文件:调用 getFilesInFolder(‘release/apk’) 异步获取APK文件夹中的所有文件,如果未找到任何文件,记录日志并结束函数执行;
  3. 筛选符合版本格式的APK文件:使用正则表达式 /^\d+.\d+.\d+.apk$/ 过滤出标准格式的版本文件(如 1.0.1.apk ),如果没有符合格式的文件,记录日志并结束函数执行;
  4. 提取并排序版本号:从每个APK文件名中提取出版本号(移除 .apk 后缀),使用自定义的排序算法,按照语义化版本号规则(主版本.次版本.修订号)进行排序;
  5. 确定最新版本:通过排序后取最后一个元素的方式获取最新版本号,根据最新版本号查找对应的文件对象;
  6. 比较版本并更新状态:调用 compareVersions 方法比较最新版本与当前版本,根据比较结果设置 hasUpdate 标志(是否有更新可用);
  7. 更新组件状态:将最新版本号、更新标志和最新文件信息保存到组件的响应式数据中。
3.2 实现代码
// 检查版本更新
async checkForUpdates() {
  try {
    // 获取当前版本
    const currentVersion = versionConfig.currentVersion

    // 获取apk文件夹中的所有文件
    const files = await getFilesInFolder('release/apk')
    if (!files || files.length === 0) {
      console.log('未找到任何更新文件')
      return
    }

    // 过滤出版本号格式的apk文件(如1.0.1.apk)
    const versionFiles = files.filter(
      (file) => file.name && /^\d+\.\d+\.\d+\.apk$/.test(file.name)
    )

    if (versionFiles.length === 0) {
      console.log('未找到版本格式的更新文件')
      return
    }

    // 提取版本号并排序,找到最新版本
    const versionNumbers = versionFiles.map((file) => {
      // 从文件名中提取版本号(去掉.apk后缀)
      return file.name.replace('.apk', '')
    })

    // 按版本号排序,找到最大版本号
    const latestVersion = versionNumbers
      .sort((a, b) => {
        const aParts = a.split('.').map(Number)
        const bParts = b.split('.').map(Number)

        for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
          const aPart = aParts[i] || 0
          const bPart = bParts[i] || 0

          if (aPart > bPart) return 1
          if (aPart < bPart) return -1
        }

        return 0
      })
      .pop()

    // 查找最新版本 file.name 是 1.0.1.apk
    const latestFile = versionFiles.find((file) => file.name === `${latestVersion}.apk`)

    // 比较当前版本和最新版本
    const hasUpdate = this.compareVersions(latestVersion, currentVersion) > 0

    // 更新数据
    this.latestVersion = latestVersion
    this.hasUpdate = hasUpdate
    this.latestFile = latestFile
  } catch (error) {
    console.error('检查更新失败:', error)
  }
}

4. 获取所有版本号

// 查询gitee一个文件夹下有哪些文件
export async function getFilesInFolder(folderPath) {
  try {
    const apiUrl = `${basePath(folderPath)}/${folderPath}?${baseParams(folderPath)}`;
    const response = await httpRequest({
      url: apiUrl,
      method: 'GET'
    });
    if (response.ok) {
      const files = await response.json();
      return files;
    } else {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
  } catch (error) {
    console.error('查询Gitee文件夹下文件失败:', error);
    return false;
  }
}

5. 对比版本号

5.1 实现分析
  1. 版本号解析与转换:接收两个版本号字符串参数 v1 和 v2,使用 split(‘.’) 将版本号按点号分割成多个部分,使用 map(Number) 将每个部分转换为数字类型,便于数值比较,得到两个数字数组 v1Parts 和 v2Parts;
  2. 逐位比较版本号:确定最大迭代次数为两个版本号数组长度的较大值,使用 for 循环从左到右(主版本到次版本再到修订号)依次比较每个版本部分,对于长度不同的版本号,较短数组的缺少部分视为0(通过 || 0 处理);
  3. 返回比较结果:如果当前比较位 v1Part > v2Part ,返回 1 (表示v1版本更新),如果当前比较位 v1Part < v2Part ,返回 -1 (表示v2版本更新),如果所有对应位都相等,返回 0 (表示两个版本相同)。
5.2 实现代码
compareVersions(v1, v2) {
  const v1Parts = v1.split('.').map(Number)
  const v2Parts = v2.split('.').map(Number)

  for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
    const v1Part = v1Parts[i] || 0
    const v2Part = v2Parts[i] || 0

    if (v1Part > v2Part) return 1
    if (v1Part < v2Part) return -1
  }

  return 0
}

6. 更新提示

downloadUpdate() {
  if (!this.latestFile) {
    uni.showToast({
      title: '更新文件信息不完整',
      icon: 'none'
    })
    return
  }

  // 显示确认对话框
  uni.showModal({
    title: '版本更新',
    content: `发现新版本 ${this.latestVersion},是否立即更新?`,
    showCancel: true,
    confirmText: '立即更新',
    cancelText: '稍后更新',
    success: (res) => {
      if (res.confirm) {
        // 用户点击立即更新
        this.startDownload(this.latestFile.download_url)
      }
    }
  })
}

7. 下载最新版本

7.1 实现分析
  1. 显示加载提示:调用 uni.showLoading() 显示加载中的提示框,设置 mask: true 防止用户在下载期间进行其他操作;
  2. 创建下载任务:调用 uni.downloadFile() 创建下载任务,传入以下参数:
  • url : 要下载的APK文件URL;
  • header : 请求头,包含了Gitee访问令牌用于授权;
  • 定义了成功和失败的回调函数。
  1. 处理下载成功情况:在 success 回调中,首先隐藏加载提示;重置下载进度为0;检查HTTP状态码,若为200表示下载成功:
  • 显示"下载完成"的成功提示;
  • 调用 installApk() 方法安装下载好的APK文件,传入临时文件路径;
  • 若状态码不为200,表示下载失败,显示相应错误提示。
  1. 处理下载失败情况:在 fail 回调中,隐藏加载提示;重置下载进度为0;打印错误日志到控制台;显示"下载失败,请重试"的错误提示;
  2. 监听下载进度:由于部分字段没有返回,所以将效果屏蔽了。
7.2 实现代码
startDownload(apkUrl) {
  uni.showLoading({
    title: '加载中...',
    mask: true
  })
  // 创建下载任务
  const downloadTask = uni.downloadFile({
    url: apkUrl,
    header: {
      Authorization: 'Bearer ' + GITEE_CONFIG.accessToken
    },
    success: (downloadResult) => {
      uni.hideLoading()
      this.downloadProgress = 0 // 重置进度
      if (downloadResult.statusCode === 200) {
        uni.showToast({
          title: '下载完成',
          icon: 'success'
        })
        // 下载成功,安装APK
        this.installApk(downloadResult.tempFilePath)
      } else {
        uni.showToast({
          title: '下载失败',
          icon: 'none'
        })
      }
    },
    fail: (err) => {
      uni.hideLoading()
      this.downloadProgress = 0 // 重置进度
      console.error('下载失败', err)
      uni.showToast({
        title: '下载失败,请重试',
        icon: 'none'
      })
    }
  })

  // 监听下载进度
  downloadTask.onProgressUpdate((res) => {
    console.log('下载进度', res.progress)
    console.log('已经下载的数据长度' + res.totalBytesWritten)
    console.log('预期需要下载的数据总长度' + res.totalBytesExpectedToWrite)
    // 实现下载进度条弹窗效果
    // this.downloadProgress = res.progress
    // this.downloadedBytes = res.totalBytesWritten
    // this.totalBytes = res.totalBytesExpectedToWrite
  })
}

8. 安装APK

8.1 实现分析
  1. 环境检查 :首先检查 plus 对象是否存在,如果不存在(表示当前环境不支持安装功能),则显示提示信息"当前环境不支持安装"并返回 false ;
  2. 执行安装 :使用 await plus.runtime.install() API执行APK安装,传入三个参数:filePath :APK文件的路径;配置对象:设置 force: false ,表示不强制安装。
  3. 安装成功处理 :当安装成功时:显示提示信息"安装成功,即将重启";使用 setTimeout 在1.5秒后调用 plus.runtime.restart() 重启应用。
8.2 实现代码
async installApk(filePath) {
  if (!plus) {
    uni.showToast({
      title: '当前环境不支持安装',
      icon: 'none'
    })
    return false
  }
  await plus.runtime.install(
    filePath,
    {
      force: false // 是否强制安装
    },
    () => {
      uni.showToast({
        title: '安装成功,即将重启',
        icon: 'none'
      })
      // 安装成功后重启应用
      setTimeout(() => {
        plus.runtime.restart()
      }, 1500)
    },
    (err) => {
      console.error('安装失败', err)
      uni.showToast({
        title: '安装失败,请重试',
        icon: 'none'
      })
    }
  )
}

9. 总结

  1. 版本检查:通过与服务器端存储的版本号进行对比,判断是否有新版本可用;
  2. 下载最新版本:若有新版本,提示用户是否立即更新;若用户同意,调用下载接口获取最新APK文件;
  3. 自动安装:下载完成后,自动调用安装方法,无需用户手动操作;
  4. 错误处理:在整个过程中,若遇到任何错误(如网络问题、权限问题等),均进行相应提示和处理,确保用户体验。
<think>好的,我现在需要帮助用户解决在UniApp中为安卓App实现人脸识别认证登录的纯前端方案。首先,用户强调是纯前端解决方案,所以可能需要依赖第三方服务或SDK,而不是原生开发。 首先,回顾一下用户提供的引用内容。引用[1]提到了在UniApp中使用人脸识别的错误处理,比如各种错误码对应的提示信息,这说明可能需要调用某个API,处理返回的错误码。引用[2]提到使用plus.webview.create来覆盖扫描框,这可能涉及到在UniApp中嵌入WebView来加载人脸识别界面。引用[3]则提到前端大厂面试宝典,可能用户对技术实现细节比较关注,需要详细步骤。 接下来,用户的需求是纯前端实现,所以可能需要使用H5的人脸识别方案,例如通过Web API调用摄像头,然后使用第三方服务(如腾讯云、阿里云的人脸识别API)进行验证。不过,纯前端处理可能涉及安全性问题,比如如何存储密钥,但用户可能希望尽可能减少后端依赖。 首先,访问摄像头需要使用浏览器的getUserMedia API,这在移动端的WebView中可能需要权限配置。UniApp中可以通过uni.getSystemInfo判断平台,然后调用相关方法。例如,使用HTML5的<input type="file">或者更直接的视频流捕获。 然后,捕获到人脸图像后,需要调用第三方API进行识别。例如,腾讯云的人脸核身服务,需要传递图片数据,通常是base64编码。但需要注意,前端直接调用API需要处理鉴权问题,比如使用临时密钥或者将密钥硬编码在前端,但这不安全,可能不推荐。不过用户要求纯前端,可能需要接受这种安全风险,或者建议结合简单后端进行鉴权。 步骤可能包括:1. 调用摄像头捕获图像;2. 将图像转换为base64或Blob;3. 调用第三方API进行人脸识别;4. 处理返回结果,如登录成功或错误提示。 另外,引用[2]中使用了plus.webview.create,这可能涉及到5+ Runtime的原生WebView,在UniApp中可能需要使用Native.js来处理。不过,纯前端方案可能更倾向于使用H5的API,而不是原生模块。 需要检查UniApp的API是否支持直接访问摄像头,或者是否需要插件。例如,uni.chooseImage可以选择图片,但实时视频流可能需要使用LivePusher组件或者WebView中的HTML5实现。 可能的问题:如何在UniApp中实时捕获摄像头视频?可能需要使用LivePusher组件,但该组件通常用于推流,而不是捕获帧。或者,在WebView中使用HTML5的video标签和getUserMedia来捕获实时视频,然后通过canvas截取图像帧。 另一个问题是,如何将捕获的图像发送到第三方API。需要处理跨域问题,以及API的鉴权参数,比如腾讯云需要的SecretId和SecretKey,但放在前端不安全。如果用户坚持纯前端,可能需要使用临时令牌或者简化流程,但需要提醒用户安全隐患。 总结可能的实现步骤: 1. 在页面中创建video元素用于显示摄像头流。 2. 使用getUserMedia获取视频流。 3. 使用canvas截取视频帧作为图片。 4. 将图片转换为base64格式。 5. 调用腾讯云人脸核身API,传递base64数据。 6. 处理API返回结果,进行登录跳转或错误提示。 同时,需要处理安卓权限问题,在manifest.json中配置摄像头权限。另外,可能需要使用uni-app的JSBridge或Native.js来调用原生WebView的相关功能,如引用[2]中的plus.webview.create。 最后,需要提醒用户纯前端方案的安全风险,建议结合后端进行鉴权,但根据用户需求,提供可行的前端方案。</think>以下是在UniApp实现Android端人脸识别登录的纯前端方案及技术实现步骤: ### 一、技术实现方案 1. **基础框架选择** - 使用UniApp的`<camera>`组件或HTML5的`getUserMedia`实现摄像头调用 - 通过`canvas`截取视频流中人脸图像 - 调用腾讯云/阿里云人脸核身API(需前端封装)[^1] 2. **核心代码示例** ```javascript // 启动摄像头 async function startCamera() { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "user" } }); videoElement.srcObject = stream; } // 捕获人脸图像 function captureFace() { const canvas = document.createElement('canvas'); canvas.width = videoElement.videoWidth; canvas.height = videoElement.videoHeight; canvas.getContext('2d').drawImage(videoElement, 0, 0); return canvas.toDataURL('image/jpeg', 0.8); } // 调用腾讯云API(示例) async function verifyFace(base64Data) { const res = await uni.request({ url: 'https://iai.tencentcloudapi.com', method: 'POST', header: { 'Authorization': `TC3-HMAC-SHA256 Credential=${SecretId}/...`, 'Content-Type': 'application/json' }, data: { "Image": base64Data.split(',')[1], "NeedRotateDetection": 1 } }); return res.data.Response.Score > 80; // 阈值判断 } ``` ### 二、关键配置项 1. **manifest.json配置** ```json { "app-plus": { "android": { "permissions": [ "CAMERA", "WRITE_EXTERNAL_STORAGE" ] } } } ``` 2. **界面布局方案** ```html <template> <view> <camera ref="camera" device-position="front" class="camera-box" /> <canvas hidden canvas-id="faceCanvas" /> <button @click="handleLogin">开始识别</button> </view> </template> ``` ### 三、优化策略 1. **性能优化** - 采用动态分辨率适配:根据设备性能自动选择640x480或320x240分辨率 - 设置节流检测机制(500ms/次) 2. **安全增强** ```javascript // 增加活体检测(基础版) function checkLiveness(imgData) { const motionThreshold = 15; const prevImg = getPrevFrame(); const diff = compareFrames(prevImg, currentImg); return diff > motionThreshold; } ``` ### 四、错误处理方案 ```javascript // 统一错误处理 function handleError(code) { const errorMap = { 'CAMERA_UNAVAILABLE': '摄像头不可用', 'MULTI_FACES': '检测到多张人脸', 'FACE_UNDETECTED': '未检测到人脸', 'LIVENESS_FAILED': '活体检测未通过' }; uni.showToast({ title: errorMap[code] || '识别失败', icon: 'none' }); } ``` ### 五、部署注意事项 1. **包体积优化** - 使用WebAssembly版人脸检测模型(可减小30%体积) - 动态加载第三方SDK 2. **兼容性处理** ```javascript // 设备兼容检测 function checkCompatibility() { return !!navigator.mediaDevices?.getUserMedia && !!window.WebAssembly && /android/i.test(navigator.userAgent); } ``` **注意事项:** 1. 纯前端方案需注意API密钥安全问题,建议: - 使用临时密钥方案 - 限制调用频率 - 启用HTTPS加密传输 2. 人脸识别准确率与设备性能正相关,建议最低配置: - Android 7.0+ - 2GB RAM - 500万像素前置摄像头
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rattenking

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值