鸿蒙应用开发之通过uploadFile实现图片上传功能
文件上传是鸿蒙应用开发的核心场景(如头像上传、附件提交、图片分享),鸿蒙基于沙箱安全机制,要求上传文件必须先存入应用沙箱目录(cacheDir),再通过系统 API 提交。以下是图片上传、拍照上传、任意文件上传的完整实现,附企业级封装工具类。
思路
1、使用Picker选择媒体库的图片与视频 (返回一个临时的图片地址 file:// 咱们可以直接预览 也可以继续向后走 拿到服务器地址再预览)
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
...
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then().catch()
2、把图片拷贝到应用的缓存目录 (不支持直接通过相册本地路径请求接口,仅支持通过缓存目录上传 context.cacheDir)
- 2.1 通过 openSync 读取 媒体库文件
- 2.2 通过 fileIo.copyFileSync(file.fd媒体文件, copyFilePath沙箱文件)
细节:copyFilePath沙箱中文件生成规则必须唯一
fileType
fileName 时间戳 + 随机数1000~9999 + . 后缀 (别忘了含后缀)
copyFilePath
3、上传文件 request.uploadFile到服务器
request.uploadFile(上下文信息, 匹配对象{
请求方式method: 'POST'
请求地址url: '',
请求头
header: {},
请求参数
files: [
{ name: '参数名', uri: "internal://cache/沙箱目录下的文件名含后缀", type:'',filename:'文件名含后缀' }
]
})
.then(uploadTask => {
// uploadTask.on(类型, 回调函数)
// progress 订阅上传任务进度事件,使用callback异步回调。
// headerReceive 服务器返回的
// complete 订阅上传任务完成
// fail 订阅上传任务失败
})
- 使用Picker选择媒体库的图片与视频
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct Index {
@State localFilePath:string = ''
build() {
Column() {
Button('拉起媒体库·选择照片').onClick(() => {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 1; // 选择媒体文件的最大数目
let uris: Array<string> = [];
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
uris = photoSelectResult.photoUris; // 数组 里面是一个个file 也就是本地媒体库图片地址 咱们可以直接预览
this.localFilePath = photoSelectResult.photoUris[0]
console.info('photoViewPicker.select to file succeed and uris are:' + uris); // file://media/Photo/947/IMG_1742896185_814/IMG_814.jpg
}).catch((err: BusinessError) => {
console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
})
Image(this.localFilePath).width('100%')
}
}
}
- 把图片拷贝到应用的沙箱目录
当前上传应用文件功能,不支持直接上传本地相册的文件,仅支持上传应用缓存文件路径(cacheDir)下的文件。
原因:考虑到安全,程序不能直接访问相册所有图片 仅仅支持用户选择后拷贝到自己的沙箱目录下 隔离性
// 把相册路径拷贝到缓存路径下(沙箱路径下)
const fileType = 'jpg'
const fileName = Date.now() + '_' + (Math.floor(Math.random() * (99999 - 11111)) + 11111) + '.' + fileType
const copyFilePath = getContext(this).cacheDir + '/' + fileName
const file = fileIo.openSync(this.uris[0], fileIo.OpenMode.READ_ONLY) // file://media/Photo/2/IMG_1735796348_001/00.jpg
fileIo.copyFileSync(file.fd, copyFilePath)
console.info('cacheDir: ' + copyFilePath);
- 上传图片到服务器
准备好参数调用request.uploadFile()获得上传对象 uploader
给uploader对象注册progress事件,监听上传进度 requestRes.on("progress", (uploadedSize: number, totalSize: number)=>{})
request.uploadFile(上下文信息, 匹配对象{
请求方式method: 'POST'
请求地址url: '',
请求头
header: {},
请求参数
files: [
{ name: '参数名', uri: "internal://cache/沙箱目录下的文件名含后缀", type:'',filename:'文件名含后缀' }
]
})
.then(uploadTask => {
// uploadTask.on(类型, 回调函数)
// progress 订阅上传任务进度事件,使用callback异步回调。
// headerReceive 服务器返回的
// complete 订阅上传任务完成
// fail 订阅上传任务失败
})
- 示例代码
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo } from '@kit.CoreFileKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
export interface UploadFileType {
database: string;
url: string;
preview: string;
}
export interface PostUploadFileResType {
state: number;
msg: string;
data: UploadFileType;
}
@Entry
@Component
struct Index {
@State localFilePath:string = ''
build() {
Column() {
Button('选择的照片拷贝到沙箱').onClick(() => {
// 一、拉起媒体库选择照片
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 1; // 选择媒体文件的最大数目
let uris: Array<string> = [];
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
uris = photoSelectResult.photoUris; // 数组 里面是一个个file 也就是本地媒体库图片地址 咱们可以直接预览
this.localFilePath = photoSelectResult.photoUris[0]
console.info('photoViewPicker.select to file succeed and uris are:' + uris); // file://media/Photo/947/IMG_1742896185_814/IMG_814.jpg
// 二、拷贝到沙箱目录
const fileType = 'jpg'
const fileName = Date.now() + '_' + Math.floor( Math.random() * (9999-1000+1)+1000 ) + '.' + fileType
const copyFilePath = getContext(this).cacheDir + '/' + fileName
// - 2.1 通过 openSync 读取 媒体库文件
const file = fileIo.openSync(photoSelectResult.photoUris[0], fileIo.OpenMode.READ_ONLY)
// - 2.2 通过 fileIo.copyFileSync(file.fd, copyFilePath)
// fileIo.copyFileSync(原文件唯一标识, 沙箱目录及文件名)
fileIo.copyFileSync(file.fd, copyFilePath)
// 细节:copyFilePath沙箱中文件生成规则必须唯一
// fileType
// fileName 时间戳 + 随机数1000~9999 + . 后缀
// copyFilePath
console.log('最终沙箱目录:', copyFilePath)
// 三、上传图片到服务器
request.uploadFile(getContext(this), {
method: 'POST',
url: 'http://123.56.141.187:8001/upload/create',
header: {},
files: [
{name: 'file', uri:'internal://cache/'+fileName, type: fileType,filename:fileName }
],
data: []
})
.then((uploadTask) => {
uploadTask.on('progress', (uploadedSize: number, totalSize: number) => {
console.info("upload totalSize:" + totalSize + " uploadedSize:" + uploadedSize);
})
uploadTask.on('headerReceive', (headers:ESObject) => {
const serverData: PostUploadFileResType = JSON.parse(headers?.body)
console.log('服务器数据:', serverData.state)
console.log('服务器数据:', serverData.msg)
console.log('服务器数据:', serverData.data.database) // 图片名
console.log('服务器数据:', serverData.data.preview) // 完整网址+图片
})
uploadTask.on('complete', () => {
console.log('上传完成')
})
uploadTask.on('fail', () => {
console.log('上传失败')
})
})
// 三、上传图片到服务器 end
}).catch((err: BusinessError) => {
console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
})
Image(this.localFilePath).width('100%')
}
}
}

浙公网安备 33010602011771号