1. 前言
在开发的过程中,不可避免的会遇到需要上传图片资源的情况。比如用户的头像上传,用户反馈的图片内容上传等等。
如何实现图片上传呢?当我去翻看官文文档的时候,看到文档上一坨一坨的没有注释的代码,我整个人都不好了!
所以,这里在简化官网的案例后,对图片上传业务做一个比较详细的说明。
那么就来一步一步看看如何实现上传图片这个功能的吧~并且在最后还提供一个封装好可以通用的图片上传工具类
为了方便演示,这里用到的上传接口为图床接口:点击查看 API接口文档
2. 图片上传的步骤
首先,我们需要知道,在鸿蒙的系统中,应用是没有办法直接操作系统相册内的内容的。那么,我们需要怎么样才能实现上传图片的功能呢?
其实可以换个思路,既然我没办法去操作系统相册里的资源,那我可以不可以把图片资源复制到我自己应用内的缓存目录,然后再进行操作呢?
答案当然是可以的,同时,这也是鸿蒙开发中所推荐的方式
在上传图片的功能实现中,一共有3个核心步骤:
- 如何选择图片(使用
picker
模块) - 如何拷贝图片到缓存目录(使用
fileIo
模块) - 如何实现图片上传(使用
request
模块)
注意,使用图片上传需要对网络权限进行配置!!!如何配置网络权限,可以点击我的另一篇文章查看
-> (鸿蒙开发基础 - Http请求数据接口动态渲染首页)
2.1 如何选择图片?
这里需要使用 picker
模块来选择一张图片,下面是官网对picker
模块的介绍 传送门:
@ohos.file.picker (选择器)
选择器(Picker)是一个封装PhotoViewPicker、DocumentViewPicker、AudioViewPicker等系统应用选择与保存能力的模块。应用可以自行选择使用哪种picker实现文件选择和文件保存的功能。
PhotoViewPicker
图库选择器对象,用来支撑选择图片/视频和保存图片/视频等用户场景。在使用前,需要先创建PhotoViewPicker实例。
系统能力:SystemCapability.FileManagement.UserFileService
示例:
let photoPicker = new picker.PhotoViewPicker();
这里不一句一句解释官方文档的代码啦,我们直接上简化后的代码
这段代码主要内容就是拉起一个图片选择器,并且在用户选择完图片之后,返回图片的地址
// 进行模块导入
import { picker } from '@kit.CoreFileKit'
import { promptAction } from '@kit.ArkUI';
// 选择图片,返回选择图片的地址
async pickerAvatar() {
// 实例化选择参数的对象
const options = new picker.PhotoSelectOptions()
// 设定需要选择的文件类型, 这里只选择图片
options.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
// 设定选择的数量, 这里只需要选择一张
options.maxSelectNumber = 1
// 实例化选择器, 然后选入设定好的对象
const pickerView = new picker.PhotoViewPicker
// 调用选择器, 并且接受返回的结果
const ImgResults = await pickerView.select(options)
return ImgResults.photoUris
}
执行,并打印ImgResults效果图:
2.2 如何拷贝图片到缓存目录
这里需要使用到fileIo
文件操作模块进行图片的拷贝,下面是官网对fileIo
模块的介绍 传送门:
@ohos.file.fs (文件管理)
该模块为基础文件操作API,提供基础文件操作能力,包括文件基本管理、文件目录管理、文件信息统计、文件流式读写等常用功能。
fs.copyFileSync
copyFileSync(src: string|number, dest: string|number, mode?: number): void
以同步方法复制文件。
这里直接上代码,说明一下这段代码的作用:
使用fileIo
模块把图片复制到应用的缓存目录,然后返回一个图片上传需要用到的参数对象
// 导入模块
import { fileIo } from '@kit.CoreFileKit'
// 拿到地址, 拷贝到应用的缓存目录. 然后上传图片
// 注意, 这里处理单张图片路径! name的参数为后端规定的图片name格式!!! 常见为:img/image/file等...
fileCopies(ImagePath: string, name: string): request.File {
// 使用文件管理模块打开,拿到源文件
const file = fileIo.openSync(ImagePath, fileIo.OpenMode.READ_ONLY)
// 设置一下需要拷贝到缓存目录的图片名字
const filename = `${Date.now()}_${Math.floor(Math.random() * 1000)}`
// 动态取到文件名的格式后缀, 一般是jpg/png格式
const extension = ImagePath.slice(ImagePath.lastIndexOf(".") + 1)
// 把路径和文件名组合
const copyThePath = `${getContext().cacheDir}/${filename}.${extension}`
// 打印一下准备复制的路径做确认
console.log(copyThePath)
// 拿到源文件的唯一表示,和准备复制的路径,把相册中的图片复制到应用自身的沙箱中
fileIo.copyFileSync(file.fd, copyThePath)
// 需要用到的上传参数对象
return {
filename: `${filename}.${extension}`,
type: extension,
name: name,
uri: `internal://cache/${filename}.${extension}`
}
}
点击这里,查看return出来的request.File
对象的参数说明
如何查看图片是否复制到了应用的缓存目录呢?
点击Dev Eco Studio
左上角的 视图 -> 工具窗口 -> Device File Browser
按照这个路径一路展开:/data/app/el2/100/base/com.chiqingsan.harmonymodule/haps/entry/cache
到这里,我们终于完成了图片上传的所有准备工作!下面,让我们开始对图片进行上传!
2.3 如何实现图片上传
这里需要使用到request
对图片文件进行批量上传,下面是官网对request
模块的介绍 传送门:
request.uploadFile
uploadFile(context: BaseContext, config: UploadConfig): Promise
上传,异步方法,使用promise形式返回结果。
需要权限:ohos.permission.INTERNET
系统能力: SystemCapability.MiscServices.Upload
这里主要是拿到上面的参数条件,使用request
模块对图片进行上传
// 导入模块
import { request } from '@kit.BasicServicesKit';
import { http } from '@kit.NetworkKit';
// 上传图片
async uploadAnImage(url: string, fileArr: request.File[]) {
const files: Array<request.File> = fileArr
// 开始准备上传图片,需要两个参数,一个是上下文对象,一个是上传的配置
const res = await request.uploadFile(getContext(), {
url: url,
method: http.RequestMethod.POST,
header: {
//multipart/form-data为媒体内容上传的格式
"Content-Type": 'multipart/form-data',
// "Authorization": `Bearer ${"token"}` 看需求,是否需要携带token
},
files,
// data参数必填
data: []
})
// 这里是监听上传的进度
res.on("progress", async (progress, totalSize) => {
console.info("当前上传进度为:", `${progress / totalSize * 100}%`)
// 如果上传大小等于总大小,则说明上传完成
if (progress === totalSize) {
promptAction.showToast({ message: "图片上传成功" })
}
})
// 这里是监听上传响应的标头
res.on("headerReceive", (header) => {
// 打印标头信息, 这里包含上传的图片访问地址
console.log("FileUpload:", JSON.stringify(header))
})
}
上传图片演示:
3. 图片上传工具类封装
当然,如果我们每次上传图片都要把这么多代码写一次,那也太不优雅了。毫无疑问,这样繁琐的代码,把它封装起来是最好的选择!
图片上传工具类封装:
import { fileIo, picker } from '@kit.CoreFileKit'
import { request } from '@kit.BasicServicesKit';
import { http } from '@kit.NetworkKit';
import { promptAction } from '@kit.ArkUI';
// 图片上传的基地址,按需求更改
const Url_Base = "https://www.imgtp.com/api"
class ImageUploadModule {
private Url_Base: string
constructor(Url_Base: string) {
this.Url_Base = Url_Base
}
// 选择图片,返回选择图片的地址
async pickerAvatar(selectNum: number = 1) {
// 规范选择的合法数量
if (selectNum < 1) {
selectNum = 1
} else if (selectNum > 9) {
selectNum = 9
}
// 实例化选择参数的对象
const options = new picker.PhotoSelectOptions()
// 设定需要选择的文件类型, 这里只选择图片
options.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
// 设定选择的数量, 这里只需要选择一张
options.maxSelectNumber = selectNum
// 实例化选择器, 然后选入设定好的对象
const pickerView = new picker.PhotoViewPicker
// 调用选择器, 并且接受返回的结果
const ImgResults = await pickerView.select(options)
// 判断是否选择了图片,如果取消了图片选择,则使用Promise抛出异常
if (ImgResults.photoUris.length > 0) {
return Promise.resolve(ImgResults.photoUris)
} else {
// console.warn("取消了图片选择")
return Promise.reject("用户取消了图片选择")
}
}
// 拿到地址, 拷贝到应用的缓存目录. 然后上传图片
// 注意, 这里处理单张图片路径! name的参数为后端规定的图片name格式!!! 常见为:img/image/file等...
fileCopies(ImagePath: string, name: string): request.File {
// 使用文件管理模块打开,拿到源文件
const file = fileIo.openSync(ImagePath, fileIo.OpenMode.READ_ONLY)
// 设置一下需要拷贝到缓存目录的图片名字
const filename = `${Date.now()}_${Math.floor(Math.random() * 1000)}`
// 动态取到文件名的格式后缀, 一般是jpg/png格式
const extension = ImagePath.slice(ImagePath.lastIndexOf(".") + 1)
// 把路径和文件名组合
const copyThePath = `${getContext().cacheDir}/${filename}.${extension}`
// 打印一下准备复制的路径做确认
console.log(copyThePath)
// 拿到源文件的唯一表示,和准备复制的路径,把相册中的图片复制到应用自身的沙箱中
fileIo.copyFileSync(file.fd, copyThePath)
return {
filename: `${filename}.${extension}`,
type: extension,
name: name,
uri: `internal://cache/${filename}.${extension}`
}
}
// 上传图片
async uploadAnImage(url: string, fileArr: request.File[]) {
const files: Array<request.File> = fileArr
console.log("FileUpload:", JSON.stringify("开始准备文件上传"))
// 开始准备上传图片,需要两个参数,一个是上下文对象,一个是上传的配置
const res = await request.uploadFile(getContext(), {
url: this.Url_Base + url,
method: http.RequestMethod.POST,
header: {
"Content-Type": 'multipart/form-data',
// "Authorization": `Bearer ${"token"}` 看需求,是否需要携带token
},
files,
data: []
})
// 这里是监听上传的进度
res.on("progress", async (progress, totalSize) => {
console.info("当前上传进度为:", `${progress / totalSize * 100}%`)
// 如果上传大小等于总大小,则说明上传完成
if (progress === totalSize) {
promptAction.showToast({ message: "图片上传成功" })
}
})
// 这里是监听上传响应的标头
res.on("headerReceive", (header) => {
// 打印标头信息, 这里包含上传的图片访问地址
console.log("FileUpload:", JSON.stringify(header))
})
}
// 对上传图片的全部步骤完成函数封装
async imageUpload(url: string, name: string, selectNum = 1) {
try {
// 拉起图片选择器, 如果用户取消选择则进入catch,代码不会往下执行
// 默认选择图片为一张
const photoUris = await this.pickerAvatar(selectNum)
// 拿到拷贝到应用沙箱的文件配置对象列表
const files = photoUris.map(v => this.fileCopies(v, name))
// 开始准备上传图片
this.uploadAnImage(url, files)
} catch (e) {
// 打印错误
console.log("ImgUpload:", JSON.stringify(e))
}
}
}
// 实例化图片上传实例,然后导出
export const imageUploadModule = new ImageUploadModule(Url_Base)
页面使用参考:
import { imageUploadModule } from '../common/utils/ImgUpload';
@Entry
@Component
struct FileUpload {
build() {
Column() {
Button("上传图片")
.onClick(async () => {
imageUploadModule.imageUpload("/upload", "image", 3)
})
}.width("100%").height("100%").justifyContent(FlexAlign.Center)
}
}
多张图片上传演示: