# 鸿蒙开发上传图片(文件)到服务器

本文介绍的是如何将文件上传到服务器,这里将用上传图片作为示例。

上传图片首先我们得先从相册中拿到图片,这里就用到了photoAccessHelper模块(官方文档)

首先,我们先从相册中选择图片。

使用photoAccessHelper.PhotoViewPicker()来获取相册选择器对象,并通过对象中的select方法来选择图片。

select方法有两个返回值,其中photoUris是我们需要用到的。

名称类型可读可写说明
photoUrisArray返回图库选择后的媒体文件的uri数组,此uri数组只能通过临时授权的方式调用photoAccessHelper.getAssets接口去使用,具体使用方式参见用户文件uri介绍中的媒体文件uri的使用方式
isOriginalPhotoboolean返回图库选择后的媒体文件是否为原图。
async selectImage(){
    let photoPicker = new photoAccessHelper.PhotoViewPicker()
	const res = await photoPicker.select({
        // 选择相册文件 的类型,这里为 IMAGE_TYPE 图片类型
      MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
        // 最多选几张
      maxSelectNumber: 3
    })
    AlertDialog.show({ message: JSON.stringify(res) })
}

拿到图片后,我们先把图片渲染到界面上

// 创建一个数组存uri ForEach渲染到UI上
interface ImageList {
  /** 图片url */
  url: string;
}
@State List: ImageList[] = []
// 将photoUris中的url通过map存到List里  [{ url: photoUris数组里的值 }]
this.List.push(...res.photoUris.map( url => {
    return { url } as ImageList
} ))
ForEach(this.List, (item: ImageList, i) => {
    Image(item.url)
	 .width(95)
     .height(95)
})

封装一个自定义弹窗,里边放上Swiper,将数组传进去,实现点击图片放大预览,并能左右滑动切换,完整代码:

import { photoAccessHelper } from '@kit.MediaLibraryKit'
interface ImageList {
  /** 图片url */
  url: string;
}
@Entry
@Component
struct Index {

@State List: ImageList[] = []
  index:number = 0
  diallor: CustomDialogController = new CustomDialogController({
    builder: HmPreview({urls: this.List , selectIndex: this.index}),
    customStyle: true
  })
  async selectImage(){
    let photoPicker = new photoAccessHelper.PhotoViewPicker()
    const res = await photoPicker.select({
      MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
      maxSelectNumber: 3
    })
    this.List.push(...res.photoUris.map( url => {
      return { url } as ImageList
    } ))
  }

  build() {
   Column(){
     Button('拉起相册')
       .onClick(() => {
          this.selectImage()
       })
     ForEach(this.List, (item: ImageList, i) => {
       Image(item.url)
         .width(95)
         .height(95)
         .onClick(() => {
           this.index = i
           this.diallor.open()
         })
     })
   }
    .height('100%')
    .width('100%')
  }
}

@CustomDialog
@Component
struct HmPreview {
  controller: CustomDialogController
  // 支持多张图片预览  给多张地址 给一个需要预览的索引
  urls: ImageList[] = [] // 多张地址
  selectIndex:number = -1 // 当前索引
  build() {
    Column() {
      Swiper() {
        ForEach(this.urls, (url: ImageList) => {
          Image(url.url)
            .width("100%") // 只给宽度 不给高度 让自己撑开
            .onClick(() => {
              this.controller.close()
            })
        })
      }
      .indicator(false) // 去掉点的显示
      .index(this.selectIndex) // 当前要看的是第几张图片

    }
    .justifyContent(FlexAlign.Center)
    .width("100%")
    .height("100%")
  }
}

因为上传文件只能从沙箱文件中拷贝,所以接下来封装一个API将传进来的图片拷贝到沙箱里。

获取与页面上下文组件关联的Context对象,并使用里边的cacheDir方法获取应用文件路径。

//list: ImageList[] 为传进来的
export const UploadFile = async (list: ImageList[]) => {
  const saveDir = getContext().cacheDir // 存储在沙箱的目录
  const fileParams: request.File[] = [] // 需要提交的参数
}

使用fileIo相册管理器中的openSync方法,打开并读取相册文件,将获取到的图片url传进方法里,返回一个File对象,其中的fd是图片文件的描述符(打开的文件描述符)我们需要传到copyFileSync(以同步方法复制文件)方法里。

之后添加到提交参数里,添加完后将文件关闭。

  list.forEach((item) => {
    // 读取相册文件
    const file = fileIo.openSync(item.url, fileIo.OpenMode.READ_ONLY)
    // 将文件拷贝到沙箱目录
    // 相册地址  使用UUID作为图片名
    const uniqueName = util.generateRandomUUID() + '.jpg'
    // 将相册文件拷贝到沙箱
    fileIo.copyFileSync(file.fd, saveDir + '/' + uniqueName)
    fileParams.push({
      filename: uniqueName, // 文件名称
      name: 'file', // 接口的参数名
      type: 'jpg', // 文件后缀
      uri: `internal://cache/${uniqueName}`// 应该是文件放到cache目录下 如果是cache协议 它会自动找这个文件
    })
    // 拷贝完关闭文件
    fileIo.closeSync(file.fd)
  })

完整代码:

// 将图片拷贝到沙箱目录
export const UploadFile = async (list: ImageList[]) => {
  // 因为上传文件只能从沙箱文件中拷贝,所以我们需要把传过来的所有图片拷贝到沙箱中
  const saveDir = getContext().cacheDir // 存储在沙箱的目录
  const fileParams: request.File[] = [] // 需要提交的参数
  list.forEach((item) => {
    // 读取相册文件  打开的File对象。
    const file = fileIo.openSync(item.url, fileIo.OpenMode.READ_ONLY)
    // 将文件拷贝到沙箱目录
    // 相册地址   使用UUID作为图片名
    const uniqueName = util.generateRandomUUID() + '.jpg'
    // 将相册文件拷贝到沙箱
    fileIo.copyFileSync(file.fd, saveDir + '/' + uniqueName)
    fileParams.push({
      filename: uniqueName, // 文件名称
      name: 'file', // 接口的参数名
      type: 'jpg', // 文件后缀
      uri: `internal://cache/${uniqueName}`// 应该是文件放到cache目录下 如果是cache协议 它会自动找这个文件
    })
    // 拷贝完关闭文件
    fileIo.closeSync(file.fd)
  })
  AlertDialog.show({ message: JSON.stringify(fileParams) })
}

接下来封装一个上传请求的API,将图片提交到服务器。

这个API需要两个参数,一个是应用上下文,一个是要上传的文件列表(官方文档)。

定义API将上传任务的配置信息配置一下(官方文档)

export const uploadImage = async (context: Context, files: request.File[]) => {
  let config: request.UploadConfig = {
    url:  '服务器地址', 
    method: 'POST',// 请求类型
    header: {
      Authorization: AppStorage.get(TOKEN_KEY) || "", // 应用中的token
      "Content-Type": "multipart/form-data" // 上传文件的参数类型
      // multipart/form-data  专门传文件的结构
    },
    files,
    data: [] // 批量上传需要携带的参数 用不上
  }
}

上传文件不一定会上传成功,也可能会上传失败,我们使用Promise管理一下成功状态和失败状态。 返回一个数组url,以便供其它接口使用。

return new Promise<ImageList[]>(async (resolve, reject) => {
      let arr: ImageList[] = []
      // 使用Promise方式,异步返回上传任务。 可通过 on方法订阅事件,获取任务状态
      const task = await request.uploadFile(context, config) // 成功执行并不意味着 上传成功 只是任务创建成功
})

订阅事件获取任务状态

      //订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
	task.on("fail", () => {
        // 上传失败
        AlertDialog.show({
          message: "上传失败"
        })
        reject(new Error("上传失败"))
      })
      // 每上传成功一次就会进来
      task.on("headerReceive", (headers: object) => {
          // 响应回来的数据
        if (headers["body"]) {
            // 转换为对象形式
          const result = JSON.parse(headers["body"]) as ResponseData<string>
            // result.code === 200为成功时的状态码
          if (result.code === 200) {
            arr.push({
              url: result.data as string
            })
          }
        }
      })
	 //订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
      task.on("complete", () => {
        // 上传完成
        // 也不能拿到上传成功的地址
        // 走到这里认为 要返回的结构已经ok了
        resolve(arr)
      })

到这里,上传图片基本完成的差不多了,接着我们需要用try捕获上传时出现的一些错误

try{
      //订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
	task.on("fail", () => {
        // 上传失败
        AlertDialog.show({
          message: "上传失败"
        })
        reject(new Error("上传失败"))
      })
      // 每上传成功一次就会进来
      task.on("headerReceive", (headers: object) => {
          // 响应回来的数据
        if (headers["body"]) {
            // 转换为对象形式
          const result = JSON.parse(headers["body"]) as ResponseData<string>
            // result.code === 200为成功时的状态码
          if (result.code === 200) {
            arr.push({
              url: result.data as string
            })
          }
        }
      })
	 //订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
      task.on("complete", () => {
        // 上传完成
        // 也不能拿到上传成功的地址
        // 走到这里认为 要返回的结构已经ok了
        resolve(arr)
      })
}catch(error) {
      AlertDialog.show({
        message: error.message // 提示错误消息
      })
      reject(error)
    }

上传请求API完整代码:

export const uploadImage = async (context: Context, files: request.File[]) => {
  let config: request.UploadConfig = {
    url:  '服务器地址', 
    method: 'POST',// 请求类型
    header: {
      Authorization: AppStorage.get(TOKEN_KEY) || "", // 应用中的token
      "Content-Type": "multipart/form-data" // 上传文件的参数类型
      // multipart/form-data  专门传文件的结构
    },
    files,
    data: [] // 批量上传需要携带的参数 用不上
  }
  
  return new Promise<ImageList[]>(async (resolve, reject) => {
      let arr: ImageList[] = []
      // 使用Promise方式,异步返回上传任务。 可通过 on方法订阅事件,获取任务状态
      const task = await request.uploadFile(context, config) // 成功执行并不意味着 上传成功 只是任务创建成功
      
      try{
      //订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
	task.on("fail", () => {
        // 上传失败
        AlertDialog.show({
          message: "上传失败"
        })
        reject(new Error("上传失败"))
      })
      // 每上传成功一次就会进来
      task.on("headerReceive", (headers: object) => {
          // 响应回来的数据
        if (headers["body"]) {
            // 转换为对象形式
          const result = JSON.parse(headers["body"]) as ResponseData<string>
            // result.code === 200为成功时的状态码
          if (result.code === 200) {
            arr.push({
              url: result.data as string
            })
          }
        }
      })
	 //订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
      task.on("complete", () => {
        // 上传完成
        // 也不能拿到上传成功的地址
        // 走到这里认为 要返回的结构已经ok了
        resolve(arr)
      })
}catch(error) {
      AlertDialog.show({
        message: error.message // 提示错误消息
      })
      reject(error)
    }
})
  
}

最后我们将上传请求API放到拷贝沙箱API里

// 将图片拷贝到沙箱目录
export const UploadFile = async (list: ImageList[]) => {
  // 因为上传文件只能从沙箱文件中拷贝,所以我们需要把传过来的所有图片拷贝到沙箱中
  const saveDir = getContext().cacheDir // 存储在沙箱的目录
  const fileParams: request.File[] = [] // 需要提交的参数
  list.forEach((item) => {
    // 读取相册文件  打开的File对象。
    const file = fileIo.openSync(item.url, fileIo.OpenMode.READ_ONLY)
    // 将文件拷贝到沙箱目录
    // 相册地址   使用UUID作为图片名
    const uniqueName = util.generateRandomUUID() + '.jpg'
    // 将相册文件拷贝到沙箱
    fileIo.copyFileSync(file.fd, saveDir + '/' + uniqueName)
    fileParams.push({
      filename: uniqueName, // 文件名称
      name: 'file', // 接口的参数名
      type: 'jpg', // 文件后缀
      uri: `internal://cache/${uniqueName}`// 应该是文件放到cache目录下 如果是cache协议 它会自动找这个文件
    })
    // 拷贝完关闭文件
    fileIo.closeSync(file.fd)
  })
  // AlertDialog.show({ message: JSON.stringify(fileParams) })
    return await uploadImage(getContext(), fileParams)
}

有不足之处欢迎大家指点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值