HarmonyOS 鸿蒙Next图片上传

HarmonyOS 鸿蒙Next图片上传 HTTP上传图片失败如何解决

6 回复

【解决方案】

开发者您好,建议排查一下request.UploadConfig参数中的files文件列表,uri属性不正确会导致文件上传失败。uri为文件的本地存储路径,仅支持"internal://cache/",即调用方(传入的context)对应的缓存路径context.cacheDir。示例:internal://cache/path/to/file.txt。

若是不能解决您的问题,请提供下具体报错日志。

更多关于HarmonyOS 鸿蒙Next图片上传的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


以下是我之前测试使用系统提供http库进行文件上传时的代码:

  • 文件使用系统提供 DocumentViewPicker 选择
  • 支持随文件传入额外参数(混合参数)
  • 文件必须要先复制到应用沙箱才可以进行上传操作
import { picker } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo as fs } from '@kit.CoreFileKit'
import { http } from '@kit.NetworkKit';
import { JSON } from '@kit.ArkTS';
import { getMimeTypeByFileName } from '../utils/MimeUtil';

@Component
export struct HttpPage {
context = this.getUIContext().getHostContext() as common.UIAbilityContext;
// 请求URL
url = "http://192.168.3.33:8080/file/upload";
// 测试额外参数
@State extraTestParam: string = '';
// 已选择的用户文件名
@State selectedFileName: string = ''
// http响应
@State responseJson: string = ''

build() {
    Column({ space: 8 }) {
      Text(decodeURI(this.selectedFileName))

      Button('选择文件')
        .attributeModifier(this.commonButtonAttributeModifier)
        .onClick((event: ClickEvent) => {
          let documentPicker = new picker.DocumentViewPicker(this.context);
          documentPicker.select({
            maxSelectNumber: 1
          }).then((fileArray: Array<string>) => {
            // this.files = ''
            // fileArray.forEach((value: string) => this.files += value + ';')
            this.selectedFileName = fileArray[0] || ''
          })
        })

      Row({ space: 8 }) {
        Text('username:')
        TextInput({ placeholder: '额外数据,同文件一起传至后端', text: $$this.extraTestParam })
          .width('75%')
      }
      .width('100%')

      Button('上传')
        .attributeModifier(this.commonButtonAttributeModifier)
        .onClick(() => {
          /* 将用户选择的文件复制到应用沙箱内 */
          let cacheDir = this.context.cacheDir
          // 把文件名称拿出来 复制上传的的时候拼接
          const filename: string = this.selectedFileName.split("/").pop() as string
          try {
            // 需要先将用户文件复制到应用沙箱内
            let sourceFile = fs.openSync(this.selectedFileName);
            let targetFile = fs.openSync(cacheDir + "/" + filename, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
            fs.copyFileSync(sourceFile.fd, targetFile.fd);
            fs.closeSync(sourceFile);
            fs.closeSync(targetFile);
            console.info(`copy file successful, targetPath: ${cacheDir}/${decodeURI(filename)}`);
          } catch (e) {
            console.info(`copy file failed ${e.message}`);
            return;
          }

          /* *************************** http方式 START *************************** */
          let httpRequest = http.createHttp();
          // 数据大于5MB请使用[requestInStream](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-http#requestinstream10-2)
          httpRequest.request(this.url, {
            method: http.RequestMethod.POST,
            // 不支持传入Map对象
            header: {
              // Request 类型
              'Content-Type': 'multipart/form-data',
              // Response 类型
              'Accept': 'application/json',
            },
            // TODO 当使用POST请求时此字段用于传递请求体内容,具体格式请结合官方文档说明并与服务端协商确定。
            // extraData: { 'username': this.extraTestParam },
            // 可选,指定返回数据的类型。
            // expectDataType: http.HttpDataType.OBJECT,
            // 可选,仅当Header中,'content-Type'为'multipart/form-data'时生效,自API 11开始支持该属性。
            multiFormDataList: [
              {
                name: "file",
                filePath: `${cacheDir}/${filename}`,
                // TODO 这里可以写一个工具类,根据文件名获取MIME类型 getMimeTypeByFileName(filename)
                contentType: `application/pdf`
              },
              // 当使用`multipart/form-data`时,使用此方法传输额外参数
              {
                name: "username",
                data: this.extraTestParam,
                contentType: 'text/plain',
              }
            ],
            // 连接超时时间(ms)
            connectTimeout: 10000
          })
            .then((response: http.HttpResponse) => {
              this.responseJson = JSON.stringify(response)
              console.log('请求已完成,Response is: ' + JSON.stringify(response))
            })
            .catch((error: BusinessError) => {
              this.responseJson = JSON.stringify(error)
              console.error('请求失败:' + JSON.stringify(error))
            })
          /* *************************** http方式 END *************************** */


        })

      TextArea({
        placeholder: '这里是Response',
        text: $$this.responseJson
      })
        .focusable(false)
        .wordBreak(WordBreak.BREAK_ALL)
  }
}

注意:由于传文件的时候’Content-Type’类型为’multipart/form-data’,额外参数需要按照上述代码添加。使用extraData属性无效。

你好,你要贴出报错的信息才能定位!

不然,你复制下官网的示例程序,应该没啥问题,但意义不大,毕竟业务代码环境不同

上传应用文件

开发者可以使用上传下载模块(ohos.request)的上传接口将本地文件上传。文件上传过程通过系统服务代理完成。在api12中,request.agent.create接口增加了设置代理地址的参数,支持设置自定义代理地址。

说明:

· 当前上传应用文件功能。request.uploadFile方式仅支持上传应用缓存文件路径(cacheDir)下的文件,request.agent方式支持上传用户公共文件和应用缓存文件路径下的文件。

· 使用上传下载模块,需声明权限:ohos.permission.INTERNET。

· 上传下载模块不支持Charles、Fiddler等代理抓包工具。

· 上传下载模块接口目前暂不支持子线程调用场景,如TaskPool等。

以下示例代码展示了两种将缓存文件上传至服务器的方法:

// 方式一:request.uploadFile
// pages/xxx.ets
import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';
import { BusinessError, request } from '@kit.BasicServicesKit';
@Entry
@Component
struct Index {
  build() {
    Row() {
      Column() {
        Button("上传").onClick(() => {
          // 获取应用文件路径
          // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
          let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
          let cacheDir = context.cacheDir;
          // 新建一个本地应用文件
          try {
            let file = fs.openSync(cacheDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
            fs.writeSync(file.fd, 'upload file test');
            fs.closeSync(file);
          } catch (error) {
            let err: BusinessError = error as BusinessError;
            console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
          }
          // 上传任务配置项
          let files: Array<request.File> = [
          //uri前缀internal://cache 对应cacheDir目录
            { filename: 'test.txt', name: 'test', uri: 'internal://cache/test.txt', type: 'txt' }
          ]
          let data: Array<request.RequestData> = [{ name: 'name', value: 'value' }];
          let uploadConfig: request.UploadConfig = {
            url: 'https://xxx',
            header: {
              'key1':'value1',
              'key2':'value2'
            },
            method: 'POST',
            files: files,
            data: data
          }
          // 将本地应用文件上传至网络服务器
          try {
            request.uploadFile(context, uploadConfig)
              .then((uploadTask: request.UploadTask) => {
                uploadTask.on('complete', (taskStates: Array<request.TaskState>) => {
                  for (let i = 0; i < taskStates.length; i++) {
                    console.info(`upload complete taskState: ${JSON.stringify(taskStates[i])}`);
                  }
                });
              })
              .catch((err: BusinessError) => {
                console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
              })
          } catch (error) {
            let err: BusinessError = error as BusinessError;
            console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
          }
        })
      }
    }
  }
}
// 方式二:request.agent
// pages/xxx.ets
import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';
import { BusinessError, request } from '@kit.BasicServicesKit';
@Entry
@Component
struct Index {
  build() {
    Row() {
      Column() {
        Button("上传").onClick(() => {
          // 获取应用文件路径
          // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext
          let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
          let cacheDir = context.cacheDir;
          // 新建一个本地应用文件
          let file = fs.openSync(cacheDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
          fs.writeSync(file.fd, 'upload file test');
          fs.closeSync(file);
          let attachments: Array<request.agent.FormItem> = [{
            name: "test",
            value: [
              {
                filename: "test.txt",
                path: cacheDir + '/test.txt',
              },
            ]
          }];
          let config: request.agent.Config = {
            action: request.agent.Action.UPLOAD,
            url: 'http://xxx',
            mode: request.agent.Mode.FOREGROUND,
            overwrite: true,
            method: "POST",
            headers: {
              'key1':'value1',
              'key2':'value2'
            },
            data: attachments
          };
          request.agent.create(context, config).then((task: request.agent.Task) => {
            task.start((err: BusinessError) => {
              if (err) {
                console.error(`Failed to start the upload task, Code: ${err.code}  message: ${err.message}`);
                return;
              }
            });
            task.on('progress', async(progress) => {
              console.warn(`/Request upload status ${progress.state}, uploaded ${progress.processed}`);
            })
            task.on('completed', async() => {
              console.warn(`/Request upload completed`);
              //该方法需用户管理任务生命周期,任务结束后调用remove释放task对象
              request.agent.remove(task.tid);
            })
          }).catch((err: BusinessError) => {
            console.error(`Failed to create a upload task, Code: ${err.code}, message: ${err.message}`);
          });
        })
      }
    }
  }
}

HarmonyOS Next图片上传主要使用媒体库管理接口和上传服务。通过@ohos.file.photoAccessHelper获取图片资源,调用PhotoAsset的get接口读取文件数据。上传时使用@ohos.request.upload模块创建上传任务,配置服务器地址和文件参数。支持通过URLSession或HttpClient进行网络传输,可设置进度监听和任务管理。系统提供安全管控机制,确保用户授权后访问媒体文件。注意在module.json5中声明ohos.permission.READ_IMAGEVIDEO权限。

在HarmonyOS Next中处理HTTP图片上传失败时,建议按以下步骤排查:

  1. 网络权限检查:确保在module.json5中已配置网络权限:

    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
    
  2. URL与参数验证

    • 确认上传接口URL正确且服务端可用
    • 检查请求参数格式(如multipart/form-data)
    • 验证文件路径是否有效
  3. 请求头配置

    let headers = new Map<string, string>();
    headers.set('Content-Type', 'multipart/form-data');
    
  4. 文件路径处理: 使用@ohos.file.fs模块获取有效的文件URI:

    let file = fs.openSync(path, fs.OpenMode.READ_ONLY);
    
  5. 错误信息捕获: 通过try-catch捕获具体错误码,常见问题包括:

    • 网络不可用(错误码200)
    • 文件不存在(错误码13900001)
    • 权限拒绝(错误码201)
  6. 服务端兼容性

    • 确认服务端支持选择的HTTP方法(POST/PUT)
    • 验证服务端对文件大小和格式的限制

建议使用@ohos.net.http模块进行上传操作,并通过日志输出详细错误信息定位问题。

回到顶部